mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 13:17:56 +00:00
bb8ddc46c1
I started this investigation/issue based on @liamaharon question [here](https://github.com/paritytech/polkadot-sdk/pull/1801#discussion_r1410452499). ## Problem The `pallet_balances` integrity test should correctly detect that the runtime has correct distinct `HoldReasons` variant count. I assume the same situation exists for RuntimeFreezeReason. It is not a critical problem, if we set `MaxHolds` with a sufficiently large value, everything should be ok. However, in this case, the integrity_test check becomes less useful. **Situation for "any" runtime:** - `HoldReason` enums from different pallets: ```rust /// from pallet_nis #[pallet::composite_enum] pub enum HoldReason { NftReceipt, } /// from pallet_preimage #[pallet::composite_enum] pub enum HoldReason { Preimage, } // from pallet_state-trie-migration #[pallet::composite_enum] pub enum HoldReason { SlashForContinueMigrate, SlashForMigrateCustomTop, SlashForMigrateCustomChild, } ``` - generated `RuntimeHoldReason` enum looks like: ```rust pub enum RuntimeHoldReason { #[codec(index = 32u8)] Preimage(pallet_preimage::HoldReason), #[codec(index = 38u8)] Nis(pallet_nis::HoldReason), #[codec(index = 42u8)] StateTrieMigration(pallet_state_trie_migration::HoldReason), } ``` - composite enum `RuntimeHoldReason` variant count is detected as `3` - we set `type MaxHolds = ConstU32<3>` - `pallet_balances::integrity_test` is ok with `3`(at least 3) However, the real problem can occur in a live runtime where some functionality might stop working. This is due to a total of 5 distinct hold reasons (for pallets with multi-instance support, it is even more), and not all of them can be used because of an incorrect `MaxHolds`, which is deemed acceptable according to the `integrity_test`: ``` // pseudo-code - if we try to call all of these: T::Currency::hold(&pallet_nis::HoldReason::NftReceipt.into(), &nft_owner, deposit)?; T::Currency::hold(&pallet_preimage::HoldReason::Preimage.into(), &nft_owner, deposit)?; T::Currency::hold(&pallet_state_trie_migration::HoldReason::SlashForContinueMigrate.into(), &nft_owner, deposit)?; // With `type MaxHolds = ConstU32<3>` these two will fail T::Currency::hold(&pallet_state_trie_migration::HoldReason::SlashForMigrateCustomTop.into(), &nft_owner, deposit)?; T::Currency::hold(&pallet_state_trie_migration::HoldReason::SlashForMigrateCustomChild.into(), &nft_owner, deposit)?; ``` ## Solutions A macro `#[pallet::*]` expansion is extended of `VariantCount` implementation for the `#[pallet::composite_enum]` enum type. This expansion generates the `VariantCount` implementation for pallets' `HoldReason`, `FreezeReason`, `LockId`, and `SlashReason`. Enum variants must be plain enum values without fields to ensure a deterministic count. The composite runtime enum, `RuntimeHoldReason` and `RuntimeFreezeReason`, now sets `VariantCount::VARIANT_COUNT` as the sum of pallets' enum `VariantCount::VARIANT_COUNT`: ```rust #[frame_support::pallet(dev_mode)] mod module_single_instance { #[pallet::composite_enum] pub enum HoldReason { ModuleSingleInstanceReason1, ModuleSingleInstanceReason2, } ... } #[frame_support::pallet(dev_mode)] mod module_multi_instance { #[pallet::composite_enum] pub enum HoldReason<I: 'static = ()> { ModuleMultiInstanceReason1, ModuleMultiInstanceReason2, ModuleMultiInstanceReason3, } ... } impl self::sp_api_hidden_includes_construct_runtime::hidden_include::traits::VariantCount for RuntimeHoldReason { const VARIANT_COUNT: u32 = 0 + module_single_instance::HoldReason::VARIANT_COUNT + module_multi_instance::HoldReason::<module_multi_instance::Instance1>::VARIANT_COUNT + module_multi_instance::HoldReason::<module_multi_instance::Instance2>::VARIANT_COUNT + module_multi_instance::HoldReason::<module_multi_instance::Instance3>::VARIANT_COUNT; } ``` In addition, `MaxHolds` is removed (as suggested [here](https://github.com/paritytech/polkadot-sdk/pull/2657#discussion_r1443324573)) from `pallet_balances`, and its `Holds` are now bounded to `RuntimeHoldReason::VARIANT_COUNT`. Therefore, there is no need to let the runtime specify `MaxHolds`. ## For reviewers Relevant changes can be found here: - `substrate/frame/support/procedural/src/lib.rs` - `substrate/frame/support/procedural/src/pallet/parse/composite.rs` - `substrate/frame/support/procedural/src/pallet/expand/composite.rs` - `substrate/frame/support/procedural/src/construct_runtime/expand/composite_helper.rs` - `substrate/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs` - `substrate/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs` - `substrate/frame/support/src/traits/misc.rs` And the rest of the files is just about removed `MaxHolds` from `pallet_balances` ## Next steps Do the same for `MaxFreezes` https://github.com/paritytech/polkadot-sdk/issues/2997. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Dónal Murray <donal.murray@parity.io> Co-authored-by: gupnik <nikhilgupta.iitk@gmail.com>
523 lines
18 KiB
Rust
523 lines
18 KiB
Rust
// 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.
|
|
|
|
//! # Scheduler tests.
|
|
|
|
#![cfg(test)]
|
|
|
|
use super::*;
|
|
use crate::mock::*;
|
|
|
|
use frame_support::{
|
|
assert_err, assert_noop, assert_ok, assert_storage_noop,
|
|
traits::{fungible::InspectHold, Bounded, BoundedInline},
|
|
StorageNoopGuard,
|
|
};
|
|
use sp_runtime::{bounded_vec, TokenError};
|
|
|
|
/// Returns one `Inline`, `Lookup` and `Legacy` item each with different data and hash.
|
|
pub fn make_bounded_values() -> (
|
|
Bounded<Vec<u8>, <Test as frame_system::Config>::Hashing>,
|
|
Bounded<Vec<u8>, <Test as frame_system::Config>::Hashing>,
|
|
Bounded<Vec<u8>, <Test as frame_system::Config>::Hashing>,
|
|
) {
|
|
let data: BoundedInline = bounded_vec![1];
|
|
let inline = Bounded::<Vec<u8>, <Test as frame_system::Config>::Hashing>::Inline(data);
|
|
|
|
let data = vec![1, 2];
|
|
let hash = <Test as frame_system::Config>::Hashing::hash(&data[..]).into();
|
|
let len = data.len() as u32;
|
|
let lookup =
|
|
Bounded::<Vec<u8>, <Test as frame_system::Config>::Hashing>::unrequested(hash, len);
|
|
|
|
let data = vec![1, 2, 3];
|
|
let hash = <Test as frame_system::Config>::Hashing::hash(&data[..]).into();
|
|
let legacy = Bounded::<Vec<u8>, <Test as frame_system::Config>::Hashing>::Legacy {
|
|
hash,
|
|
dummy: Default::default(),
|
|
};
|
|
|
|
(inline, lookup, legacy)
|
|
}
|
|
|
|
#[test]
|
|
fn user_note_preimage_works() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_eq!(Balances::balance_on_hold(&PreimageHoldReason::get(), &2), 3);
|
|
assert_eq!(Balances::free_balance(2), 97);
|
|
|
|
let h = hashed([1]);
|
|
assert!(Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), Some(vec![1]));
|
|
|
|
assert_noop!(
|
|
Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]),
|
|
Error::<Test>::AlreadyNoted,
|
|
);
|
|
assert_noop!(
|
|
Preimage::note_preimage(RuntimeOrigin::signed(0), vec![2]),
|
|
TokenError::FundsUnavailable,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn manager_note_preimage_works() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1]));
|
|
assert_eq!(Balances::reserved_balance(1), 0);
|
|
assert_eq!(Balances::free_balance(1), 100);
|
|
|
|
let h = hashed([1]);
|
|
assert!(Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), Some(vec![1]));
|
|
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1]));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn user_unnote_preimage_works() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_noop!(
|
|
Preimage::unnote_preimage(RuntimeOrigin::signed(3), hashed([1])),
|
|
Error::<Test>::NotAuthorized
|
|
);
|
|
assert_noop!(
|
|
Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([2])),
|
|
Error::<Test>::NotNoted
|
|
);
|
|
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])));
|
|
assert_noop!(
|
|
Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])),
|
|
Error::<Test>::NotNoted
|
|
);
|
|
|
|
let h = hashed([1]);
|
|
assert!(!Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), None);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn manager_unnote_preimage_works() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1]));
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_noop!(
|
|
Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1])),
|
|
Error::<Test>::NotNoted
|
|
);
|
|
|
|
let h = hashed([1]);
|
|
assert!(!Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), None);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn manager_unnote_user_preimage_works() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_noop!(
|
|
Preimage::unnote_preimage(RuntimeOrigin::signed(3), hashed([1])),
|
|
Error::<Test>::NotAuthorized
|
|
);
|
|
assert_noop!(
|
|
Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([2])),
|
|
Error::<Test>::NotNoted
|
|
);
|
|
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
|
|
let h = hashed([1]);
|
|
assert!(!Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), None);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn requested_then_noted_preimage_cannot_be_unnoted() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![1]));
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
// it's still here.
|
|
|
|
let h = hashed([1]);
|
|
assert!(Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), Some(vec![1]));
|
|
|
|
// now it's gone
|
|
assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert!(!Preimage::have_preimage(&hashed([1])));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn request_note_order_makes_no_difference() {
|
|
let one_way = new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
(
|
|
RequestStatusFor::<Test>::iter().collect::<Vec<_>>(),
|
|
PreimageFor::<Test>::iter().collect::<Vec<_>>(),
|
|
)
|
|
});
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])));
|
|
let other_way = (
|
|
RequestStatusFor::<Test>::iter().collect::<Vec<_>>(),
|
|
PreimageFor::<Test>::iter().collect::<Vec<_>>(),
|
|
);
|
|
assert_eq!(one_way, other_way);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn requested_then_user_noted_preimage_is_free() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_eq!(Balances::reserved_balance(2), 0);
|
|
assert_eq!(Balances::free_balance(2), 100);
|
|
|
|
let h = hashed([1]);
|
|
assert!(Preimage::have_preimage(&h));
|
|
assert_eq!(Preimage::get_preimage(&h), Some(vec![1]));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn request_user_note_order_makes_no_difference() {
|
|
let one_way = new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
(
|
|
RequestStatusFor::<Test>::iter().collect::<Vec<_>>(),
|
|
PreimageFor::<Test>::iter().collect::<Vec<_>>(),
|
|
)
|
|
});
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])));
|
|
let other_way = (
|
|
RequestStatusFor::<Test>::iter().collect::<Vec<_>>(),
|
|
PreimageFor::<Test>::iter().collect::<Vec<_>>(),
|
|
);
|
|
assert_eq!(one_way, other_way);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn unrequest_preimage_works() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_noop!(
|
|
Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([2])),
|
|
Error::<Test>::NotRequested
|
|
);
|
|
|
|
assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert!(Preimage::have_preimage(&hashed([1])));
|
|
|
|
assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_noop!(
|
|
Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1])),
|
|
Error::<Test>::NotRequested
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn user_noted_then_requested_preimage_is_refunded_once_only() {
|
|
new_test_ext().execute_with(|| {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1; 3]));
|
|
assert_eq!(Balances::balance_on_hold(&PreimageHoldReason::get(), &2), 5);
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(2), vec![1]));
|
|
assert_eq!(Balances::balance_on_hold(&PreimageHoldReason::get(), &2), 8);
|
|
assert_ok!(Preimage::request_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::unrequest_preimage(RuntimeOrigin::signed(1), hashed([1])));
|
|
assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])));
|
|
// Still have hold from `vec[1; 3]`.
|
|
assert_eq!(Balances::balance_on_hold(&PreimageHoldReason::get(), &2), 5);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn noted_preimage_use_correct_map() {
|
|
new_test_ext().execute_with(|| {
|
|
// Add one preimage per bucket...
|
|
for i in 0..7 {
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![0; 128 << (i * 2)]));
|
|
}
|
|
assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), vec![0; MAX_SIZE as usize]));
|
|
assert_eq!(PreimageFor::<Test>::iter().count(), 8);
|
|
|
|
// All are present
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), 8);
|
|
|
|
// Now start removing them again...
|
|
for i in 0..7 {
|
|
assert_ok!(Preimage::unnote_preimage(
|
|
RuntimeOrigin::signed(1),
|
|
hashed(vec![0; 128 << (i * 2)])
|
|
));
|
|
}
|
|
assert_eq!(PreimageFor::<Test>::iter().count(), 1);
|
|
assert_ok!(Preimage::unnote_preimage(
|
|
RuntimeOrigin::signed(1),
|
|
hashed(vec![0; MAX_SIZE as usize])
|
|
));
|
|
assert_eq!(PreimageFor::<Test>::iter().count(), 0);
|
|
|
|
// All are gone
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), 0);
|
|
});
|
|
}
|
|
|
|
/// The `StorePreimage` and `QueryPreimage` traits work together.
|
|
#[test]
|
|
fn query_and_store_preimage_workflow() {
|
|
new_test_ext().execute_with(|| {
|
|
let _guard = StorageNoopGuard::default();
|
|
let data: Vec<u8> = vec![1; 512];
|
|
let encoded = data.encode();
|
|
|
|
// Bound an unbound value.
|
|
let bound = Preimage::bound(data.clone()).unwrap();
|
|
let (len, hash) = (bound.len().unwrap(), bound.hash());
|
|
|
|
assert_eq!(hash, <Test as frame_system::Config>::Hashing::hash(&encoded).into());
|
|
assert_eq!(bound.len(), Some(len));
|
|
assert!(bound.lookup_needed(), "Should not be Inlined");
|
|
assert_eq!(bound.lookup_len(), Some(len));
|
|
|
|
// The value is requested and available.
|
|
assert!(Preimage::is_requested(&hash));
|
|
assert!(<Preimage as QueryPreimage>::have(&bound));
|
|
assert_eq!(Preimage::len(&hash), Some(len));
|
|
|
|
// It can be fetched with length.
|
|
assert_eq!(Preimage::fetch(&hash, Some(len)).unwrap(), encoded);
|
|
// ... and without length.
|
|
assert_eq!(Preimage::fetch(&hash, None).unwrap(), encoded);
|
|
// ... but not with wrong length.
|
|
assert_err!(Preimage::fetch(&hash, Some(0)), DispatchError::Unavailable);
|
|
|
|
// It can be peeked and decoded correctly.
|
|
assert_eq!(Preimage::peek::<Vec<u8>>(&bound).unwrap(), (data.clone(), Some(len)));
|
|
// Request it two more times.
|
|
assert_eq!(Preimage::pick::<Vec<u8>>(hash, len), bound);
|
|
Preimage::request(&hash);
|
|
// It is requested thrice.
|
|
assert!(matches!(
|
|
RequestStatusFor::<Test>::get(&hash).unwrap(),
|
|
RequestStatus::Requested { count: 3, .. }
|
|
));
|
|
|
|
// It can be realized and decoded correctly.
|
|
assert_eq!(Preimage::realize::<Vec<u8>>(&bound).unwrap(), (data.clone(), Some(len)));
|
|
assert!(matches!(
|
|
RequestStatusFor::<Test>::get(&hash).unwrap(),
|
|
RequestStatus::Requested { count: 2, .. }
|
|
));
|
|
// Dropping should unrequest.
|
|
Preimage::drop(&bound);
|
|
assert!(matches!(
|
|
RequestStatusFor::<Test>::get(&hash).unwrap(),
|
|
RequestStatus::Requested { count: 1, .. }
|
|
));
|
|
|
|
// Is still available.
|
|
assert!(<Preimage as QueryPreimage>::have(&bound));
|
|
// Manually unnote it.
|
|
Preimage::unnote(&hash);
|
|
// Is not available anymore.
|
|
assert!(!<Preimage as QueryPreimage>::have(&bound));
|
|
assert_err!(Preimage::fetch(&hash, Some(len)), DispatchError::Unavailable);
|
|
// And not requested since the traits assume permissioned origin.
|
|
assert!(!Preimage::is_requested(&hash));
|
|
|
|
// No storage changes remain. Checked by `StorageNoopGuard`.
|
|
});
|
|
}
|
|
|
|
/// The request function behaves as expected.
|
|
#[test]
|
|
fn query_preimage_request_works() {
|
|
new_test_ext().execute_with(|| {
|
|
let _guard = StorageNoopGuard::default();
|
|
let data: Vec<u8> = vec![1; 10];
|
|
let hash = <Test as frame_system::Config>::Hashing::hash(&data[..]).into();
|
|
|
|
// Request the preimage.
|
|
<Preimage as QueryPreimage>::request(&hash);
|
|
|
|
// The preimage is requested with unknown length and cannot be fetched.
|
|
assert!(<Preimage as QueryPreimage>::is_requested(&hash));
|
|
assert!(<Preimage as QueryPreimage>::len(&hash).is_none());
|
|
assert_noop!(<Preimage as QueryPreimage>::fetch(&hash, None), DispatchError::Unavailable);
|
|
|
|
// Request again.
|
|
<Preimage as QueryPreimage>::request(&hash);
|
|
// The preimage is still requested.
|
|
assert!(<Preimage as QueryPreimage>::is_requested(&hash));
|
|
assert!(<Preimage as QueryPreimage>::len(&hash).is_none());
|
|
assert_noop!(<Preimage as QueryPreimage>::fetch(&hash, None), DispatchError::Unavailable);
|
|
// But there is only one entry in the map.
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), 1);
|
|
|
|
// Un-request the preimage.
|
|
<Preimage as QueryPreimage>::unrequest(&hash);
|
|
// It is still requested.
|
|
assert!(<Preimage as QueryPreimage>::is_requested(&hash));
|
|
// Un-request twice.
|
|
<Preimage as QueryPreimage>::unrequest(&hash);
|
|
// It is not requested anymore.
|
|
assert!(!<Preimage as QueryPreimage>::is_requested(&hash));
|
|
// And there is no entry in the map.
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), 0);
|
|
});
|
|
}
|
|
|
|
/// The `QueryPreimage` functions can be used together with `Bounded` values.
|
|
#[test]
|
|
fn query_preimage_hold_and_drop_work() {
|
|
new_test_ext().execute_with(|| {
|
|
let _guard = StorageNoopGuard::default();
|
|
let (inline, lookup, legacy) = make_bounded_values();
|
|
|
|
// `hold` does nothing for `Inline` values.
|
|
assert_storage_noop!(<Preimage as QueryPreimage>::hold(&inline));
|
|
// `hold` requests `Lookup` values.
|
|
<Preimage as QueryPreimage>::hold(&lookup);
|
|
assert!(<Preimage as QueryPreimage>::is_requested(&lookup.hash()));
|
|
// `hold` requests `Legacy` values.
|
|
<Preimage as QueryPreimage>::hold(&legacy);
|
|
assert!(<Preimage as QueryPreimage>::is_requested(&legacy.hash()));
|
|
|
|
// There are two values requested in total.
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), 2);
|
|
|
|
// Cleanup by dropping both.
|
|
<Preimage as QueryPreimage>::drop(&lookup);
|
|
assert!(!<Preimage as QueryPreimage>::is_requested(&lookup.hash()));
|
|
<Preimage as QueryPreimage>::drop(&legacy);
|
|
assert!(!<Preimage as QueryPreimage>::is_requested(&legacy.hash()));
|
|
|
|
// There are no values requested anymore.
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), 0);
|
|
});
|
|
}
|
|
|
|
/// The `StorePreimage` trait works as expected.
|
|
#[test]
|
|
fn store_preimage_basic_works() {
|
|
new_test_ext().execute_with(|| {
|
|
let _guard = StorageNoopGuard::default();
|
|
let data: Vec<u8> = vec![1; 512]; // Too large to inline.
|
|
let encoded = Cow::from(data.encode());
|
|
|
|
// Bound the data.
|
|
let bound = <Preimage as StorePreimage>::bound(data.clone()).unwrap();
|
|
// The preimage can be peeked.
|
|
assert_ok!(<Preimage as QueryPreimage>::peek(&bound));
|
|
// Un-note the preimage.
|
|
<Preimage as StorePreimage>::unnote(&bound.hash());
|
|
// The preimage cannot be peeked anymore.
|
|
assert_err!(<Preimage as QueryPreimage>::peek(&bound), DispatchError::Unavailable);
|
|
// Noting the wrong pre-image does not make it peek-able.
|
|
assert_ok!(<Preimage as StorePreimage>::note(Cow::Borrowed(&data)));
|
|
assert_err!(<Preimage as QueryPreimage>::peek(&bound), DispatchError::Unavailable);
|
|
|
|
// Manually note the preimage makes it peek-able again.
|
|
assert_ok!(<Preimage as StorePreimage>::note(encoded.clone()));
|
|
// Noting again works.
|
|
assert_ok!(<Preimage as StorePreimage>::note(encoded));
|
|
assert_ok!(<Preimage as QueryPreimage>::peek(&bound));
|
|
|
|
// Cleanup.
|
|
<Preimage as StorePreimage>::unnote(&bound.hash());
|
|
let data_hash = <Test as frame_system::Config>::Hashing::hash(&data);
|
|
<Preimage as StorePreimage>::unnote(&data_hash.into());
|
|
|
|
// No storage changes remain. Checked by `StorageNoopGuard`.
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn store_preimage_note_too_large_errors() {
|
|
new_test_ext().execute_with(|| {
|
|
// Works with `MAX_LENGTH`.
|
|
let len = <Preimage as StorePreimage>::MAX_LENGTH;
|
|
let data = vec![0u8; len];
|
|
assert_ok!(<Preimage as StorePreimage>::note(data.into()));
|
|
|
|
// Errors with `MAX_LENGTH+1`.
|
|
let data = vec![0u8; len + 1];
|
|
assert_err!(<Preimage as StorePreimage>::note(data.into()), DispatchError::Exhausted);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn store_preimage_bound_too_large_errors() {
|
|
new_test_ext().execute_with(|| {
|
|
// Using `MAX_LENGTH` number of bytes in a vector does not work
|
|
// since SCALE prepends the length.
|
|
let len = <Preimage as StorePreimage>::MAX_LENGTH;
|
|
let data: Vec<u8> = vec![0; len];
|
|
assert_err!(<Preimage as StorePreimage>::bound(data.clone()), DispatchError::Exhausted);
|
|
|
|
// Works with `MAX_LENGTH-4`.
|
|
let data: Vec<u8> = vec![0; len - 4];
|
|
assert_ok!(<Preimage as StorePreimage>::bound(data.clone()));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn ensure_updated_works() {
|
|
#![allow(deprecated)]
|
|
new_test_ext().execute_with(|| {
|
|
let alice = 2;
|
|
|
|
for i in 0..100 {
|
|
let hashes =
|
|
(0..100).map(|j| insert_old_unrequested::<Test>(j, alice)).collect::<Vec<_>>();
|
|
let old = hashes.iter().take(i).cloned().collect::<Vec<_>>();
|
|
let bad = vec![hashed([0; 32]); 100 - i];
|
|
|
|
let hashes = [old.as_slice(), bad.as_slice()].concat();
|
|
let res = Preimage::ensure_updated(RuntimeOrigin::signed(alice), hashes).unwrap();
|
|
|
|
// Alice pays a fee when less than 90% of the hashes are new.
|
|
assert_eq!(res.pays_fee, (i < 90).into());
|
|
|
|
assert_eq!(RequestStatusFor::<Test>::iter().count(), i);
|
|
assert_eq!(StatusFor::<Test>::iter().count(), 100 - i);
|
|
}
|
|
});
|
|
}
|