// 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, ::Hashing>, Bounded, ::Hashing>, Bounded, ::Hashing>, ) { let data: BoundedInline = bounded_vec![1]; let inline = Bounded::, ::Hashing>::Inline(data); let data = vec![1, 2]; let hash = ::Hashing::hash(&data[..]).into(); let len = data.len() as u32; let lookup = Bounded::, ::Hashing>::unrequested(hash, len); let data = vec![1, 2, 3]; let hash = ::Hashing::hash(&data[..]).into(); let legacy = Bounded::, ::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::::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::::NotAuthorized ); assert_noop!( Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([2])), Error::::NotNoted ); assert_ok!(Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1]))); assert_noop!( Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([1])), Error::::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::::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::::NotAuthorized ); assert_noop!( Preimage::unnote_preimage(RuntimeOrigin::signed(2), hashed([2])), Error::::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::::iter().collect::>(), PreimageFor::::iter().collect::>(), ) }); 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::::iter().collect::>(), PreimageFor::::iter().collect::>(), ); 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::::iter().collect::>(), PreimageFor::::iter().collect::>(), ) }); 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::::iter().collect::>(), PreimageFor::::iter().collect::>(), ); 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::::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::::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::::iter().count(), 8); // All are present assert_eq!(RequestStatusFor::::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::::iter().count(), 1); assert_ok!(Preimage::unnote_preimage( RuntimeOrigin::signed(1), hashed(vec![0; MAX_SIZE as usize]) )); assert_eq!(PreimageFor::::iter().count(), 0); // All are gone assert_eq!(RequestStatusFor::::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 = 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, ::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!(::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::>(&bound).unwrap(), (data.clone(), Some(len))); // Request it two more times. assert_eq!(Preimage::pick::>(hash, len), bound); Preimage::request(&hash); // It is requested thrice. assert!(matches!( RequestStatusFor::::get(&hash).unwrap(), RequestStatus::Requested { count: 3, .. } )); // It can be realized and decoded correctly. assert_eq!(Preimage::realize::>(&bound).unwrap(), (data.clone(), Some(len))); assert!(matches!( RequestStatusFor::::get(&hash).unwrap(), RequestStatus::Requested { count: 2, .. } )); // Dropping should unrequest. Preimage::drop(&bound); assert!(matches!( RequestStatusFor::::get(&hash).unwrap(), RequestStatus::Requested { count: 1, .. } )); // Is still available. assert!(::have(&bound)); // Manually unnote it. Preimage::unnote(&hash); // Is not available anymore. assert!(!::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 = vec![1; 10]; let hash = ::Hashing::hash(&data[..]).into(); // Request the preimage. ::request(&hash); // The preimage is requested with unknown length and cannot be fetched. assert!(::is_requested(&hash)); assert!(::len(&hash).is_none()); assert_noop!(::fetch(&hash, None), DispatchError::Unavailable); // Request again. ::request(&hash); // The preimage is still requested. assert!(::is_requested(&hash)); assert!(::len(&hash).is_none()); assert_noop!(::fetch(&hash, None), DispatchError::Unavailable); // But there is only one entry in the map. assert_eq!(RequestStatusFor::::iter().count(), 1); // Un-request the preimage. ::unrequest(&hash); // It is still requested. assert!(::is_requested(&hash)); // Un-request twice. ::unrequest(&hash); // It is not requested anymore. assert!(!::is_requested(&hash)); // And there is no entry in the map. assert_eq!(RequestStatusFor::::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!(::hold(&inline)); // `hold` requests `Lookup` values. ::hold(&lookup); assert!(::is_requested(&lookup.hash())); // `hold` requests `Legacy` values. ::hold(&legacy); assert!(::is_requested(&legacy.hash())); // There are two values requested in total. assert_eq!(RequestStatusFor::::iter().count(), 2); // Cleanup by dropping both. ::drop(&lookup); assert!(!::is_requested(&lookup.hash())); ::drop(&legacy); assert!(!::is_requested(&legacy.hash())); // There are no values requested anymore. assert_eq!(RequestStatusFor::::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 = vec![1; 512]; // Too large to inline. let encoded = Cow::from(data.encode()); // Bound the data. let bound = ::bound(data.clone()).unwrap(); // The preimage can be peeked. assert_ok!(::peek(&bound)); // Un-note the preimage. ::unnote(&bound.hash()); // The preimage cannot be peeked anymore. assert_err!(::peek(&bound), DispatchError::Unavailable); // Noting the wrong pre-image does not make it peek-able. assert_ok!(::note(Cow::Borrowed(&data))); assert_err!(::peek(&bound), DispatchError::Unavailable); // Manually note the preimage makes it peek-able again. assert_ok!(::note(encoded.clone())); // Noting again works. assert_ok!(::note(encoded)); assert_ok!(::peek(&bound)); // Cleanup. ::unnote(&bound.hash()); let data_hash = ::Hashing::hash(&data); ::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 = ::MAX_LENGTH; let data = vec![0u8; len]; assert_ok!(::note(data.into())); // Errors with `MAX_LENGTH+1`. let data = vec![0u8; len + 1]; assert_err!(::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 = ::MAX_LENGTH; let data: Vec = vec![0; len]; assert_err!(::bound(data.clone()), DispatchError::Exhausted); // Works with `MAX_LENGTH-4`. let data: Vec = vec![0; len - 4]; assert_ok!(::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::(j, alice)).collect::>(); let old = hashes.iter().take(i).cloned().collect::>(); 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::::iter().count(), i); assert_eq!(StatusFor::::iter().count(), 100 - i); } }); }