referrerpolicy=no-referrer-when-downgrade

pallet_utility/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Utility Pallet
19//! A stateless pallet with helpers for dispatch management which does no re-authentication.
20//!
21//! - [`Config`]
22//! - [`Call`]
23//!
24//! ## Overview
25//!
26//! This pallet contains two basic pieces of functionality:
27//! - Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a
28//!   single dispatch. This can be useful to amalgamate proposals, combining `set_code` with
29//!   corresponding `set_storage`s, for efficient multiple payouts with just a single signature
30//!   verify, or in combination with one of the other two dispatch functionality.
31//! - Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from
32//!   an alternative signed origin. Each account has 2 * 2**16 possible "pseudonyms" (alternative
33//!   account IDs) and these can be stacked. This can be useful as a key management tool, where you
34//!   need multiple distinct accounts (e.g. as controllers for many staking accounts), but where
35//!   it's perfectly fine to have each of them controlled by the same underlying keypair. Derivative
36//!   accounts are, for the purposes of proxy filtering considered exactly the same as the origin
37//!   and are thus hampered with the origin's filters.
38//!
39//! Since proxy filters are respected in all dispatches of this pallet, it should never need to be
40//! filtered by any proxy.
41//!
42//! ## Interface
43//!
44//! ### Dispatchable Functions
45//!
46//! #### For batch dispatch
47//! * `batch` - Dispatch multiple calls from the sender's origin.
48//!
49//! #### For pseudonymal dispatch
50//! * `as_derivative` - Dispatch a call from a derivative signed origin.
51
52// Ensure we're `no_std` when compiling for Wasm.
53#![cfg_attr(not(feature = "std"), no_std)]
54
55mod benchmarking;
56mod tests;
57pub mod weights;
58
59extern crate alloc;
60
61use alloc::{boxed::Box, vec::Vec};
62use codec::{Decode, Encode};
63use frame_support::{
64	dispatch::{
65		extract_actual_weight,
66		DispatchClass::{Normal, Operational},
67		GetDispatchInfo, PostDispatchInfo,
68	},
69	traits::{IsSubType, OriginTrait, UnfilteredDispatchable},
70};
71use sp_core::TypeId;
72use sp_io::hashing::blake2_256;
73use sp_runtime::traits::{BadOrigin, Dispatchable, TrailingZeroInput};
74pub use weights::WeightInfo;
75
76pub use pallet::*;
77
78#[frame_support::pallet]
79pub mod pallet {
80	use super::*;
81	use frame_support::{dispatch::DispatchClass, pallet_prelude::*};
82	use frame_system::pallet_prelude::*;
83
84	#[pallet::pallet]
85	pub struct Pallet<T>(_);
86
87	/// Configuration trait.
88	#[pallet::config]
89	pub trait Config: frame_system::Config {
90		/// The overarching event type.
91		#[allow(deprecated)]
92		type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
93
94		/// The overarching call type.
95		type RuntimeCall: Parameter
96			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
97			+ GetDispatchInfo
98			+ From<frame_system::Call<Self>>
99			+ UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
100			+ IsSubType<Call<Self>>
101			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
102
103		/// The caller origin, overarching type of all pallets origins.
104		type PalletsOrigin: Parameter +
105			Into<<Self as frame_system::Config>::RuntimeOrigin> +
106			IsType<<<Self as frame_system::Config>::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin>;
107
108		/// Weight information for extrinsics in this pallet.
109		type WeightInfo: WeightInfo;
110	}
111
112	#[pallet::event]
113	#[pallet::generate_deposit(pub(super) fn deposit_event)]
114	pub enum Event {
115		/// Batch of dispatches did not complete fully. Index of first failing dispatch given, as
116		/// well as the error.
117		BatchInterrupted { index: u32, error: DispatchError },
118		/// Batch of dispatches completed fully with no error.
119		BatchCompleted,
120		/// Batch of dispatches completed but has errors.
121		BatchCompletedWithErrors,
122		/// A single item within a Batch of dispatches has completed with no error.
123		ItemCompleted,
124		/// A single item within a Batch of dispatches has completed with error.
125		ItemFailed { error: DispatchError },
126		/// A call was dispatched.
127		DispatchedAs { result: DispatchResult },
128		/// Main call was dispatched.
129		IfElseMainSuccess,
130		/// The fallback call was dispatched.
131		IfElseFallbackCalled { main_error: DispatchError },
132	}
133
134	// Align the call size to 1KB. As we are currently compiling the runtime for native/wasm
135	// the `size_of` of the `Call` can be different. To ensure that this don't leads to
136	// mismatches between native/wasm or to different metadata for the same runtime, we
137	// algin the call size. The value is chosen big enough to hopefully never reach it.
138	const CALL_ALIGN: u32 = 1024;
139
140	#[pallet::extra_constants]
141	impl<T: Config> Pallet<T> {
142		/// The limit on the number of batched calls.
143		fn batched_calls_limit() -> u32 {
144			let allocator_limit = sp_core::MAX_POSSIBLE_ALLOCATION;
145			let call_size = (core::mem::size_of::<<T as Config>::RuntimeCall>() as u32)
146				.div_ceil(CALL_ALIGN) *
147				CALL_ALIGN;
148			// The margin to take into account vec doubling capacity.
149			let margin_factor = 3;
150
151			allocator_limit / margin_factor / call_size
152		}
153	}
154
155	#[pallet::hooks]
156	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
157		fn integrity_test() {
158			// If you hit this error, you need to try to `Box` big dispatchable parameters.
159			assert!(
160				core::mem::size_of::<<T as Config>::RuntimeCall>() as u32 <= CALL_ALIGN,
161				"Call enum size should be smaller than {} bytes.",
162				CALL_ALIGN,
163			);
164		}
165	}
166
167	#[pallet::error]
168	pub enum Error<T> {
169		/// Too many calls batched.
170		TooManyCalls,
171	}
172
173	#[pallet::call]
174	impl<T: Config> Pallet<T> {
175		/// Send a batch of dispatch calls.
176		///
177		/// May be called from any origin except `None`.
178		///
179		/// - `calls`: The calls to be dispatched from the same origin. The number of call must not
180		///   exceed the constant: `batched_calls_limit` (available in constant metadata).
181		///
182		/// If origin is root then the calls are dispatched without checking origin filter. (This
183		/// includes bypassing `frame_system::Config::BaseCallFilter`).
184		///
185		/// ## Complexity
186		/// - O(C) where C is the number of calls to be batched.
187		///
188		/// This will return `Ok` in all circumstances. To determine the success of the batch, an
189		/// event is deposited. If a call failed and the batch was interrupted, then the
190		/// `BatchInterrupted` event is deposited, along with the number of successful calls made
191		/// and the error of the failed call. If all were successful, then the `BatchCompleted`
192		/// event is deposited.
193		#[pallet::call_index(0)]
194		#[pallet::weight({
195			let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
196			let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32));
197			(dispatch_weight, dispatch_class)
198		})]
199		pub fn batch(
200			origin: OriginFor<T>,
201			calls: Vec<<T as Config>::RuntimeCall>,
202		) -> DispatchResultWithPostInfo {
203			// Do not allow the `None` origin.
204			if ensure_none(origin.clone()).is_ok() {
205				return Err(BadOrigin.into())
206			}
207
208			let is_root = ensure_root(origin.clone()).is_ok();
209			let calls_len = calls.len();
210			ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
211
212			// Track the actual weight of each of the batch calls.
213			let mut weight = Weight::zero();
214			for (index, call) in calls.into_iter().enumerate() {
215				let info = call.get_dispatch_info();
216				// If origin is root, don't apply any dispatch filters; root can call anything.
217				let result = if is_root {
218					call.dispatch_bypass_filter(origin.clone())
219				} else {
220					call.dispatch(origin.clone())
221				};
222				// Add the weight of this call.
223				weight = weight.saturating_add(extract_actual_weight(&result, &info));
224				if let Err(e) = result {
225					Self::deposit_event(Event::BatchInterrupted {
226						index: index as u32,
227						error: e.error,
228					});
229					// Take the weight of this function itself into account.
230					let base_weight = T::WeightInfo::batch(index.saturating_add(1) as u32);
231					// Return the actual used weight + base_weight of this call.
232					return Ok(Some(base_weight.saturating_add(weight)).into())
233				}
234				Self::deposit_event(Event::ItemCompleted);
235			}
236			Self::deposit_event(Event::BatchCompleted);
237			let base_weight = T::WeightInfo::batch(calls_len as u32);
238			Ok(Some(base_weight.saturating_add(weight)).into())
239		}
240
241		/// Send a call through an indexed pseudonym of the sender.
242		///
243		/// Filter from origin are passed along. The call will be dispatched with an origin which
244		/// use the same filter as the origin of this call.
245		///
246		/// NOTE: If you need to ensure that any account-based filtering is not honored (i.e.
247		/// because you expect `proxy` to have been used prior in the call stack and you do not want
248		/// the call restrictions to apply to any sub-accounts), then use `as_multi_threshold_1`
249		/// in the Multisig pallet instead.
250		///
251		/// NOTE: Prior to version *12, this was called `as_limited_sub`.
252		///
253		/// The dispatch origin for this call must be _Signed_.
254		#[pallet::call_index(1)]
255		#[pallet::weight({
256			let dispatch_info = call.get_dispatch_info();
257			(
258				T::WeightInfo::as_derivative()
259					// AccountData for inner call origin accountdata.
260					.saturating_add(T::DbWeight::get().reads_writes(1, 1))
261					.saturating_add(dispatch_info.call_weight),
262				dispatch_info.class,
263			)
264		})]
265		pub fn as_derivative(
266			origin: OriginFor<T>,
267			index: u16,
268			call: Box<<T as Config>::RuntimeCall>,
269		) -> DispatchResultWithPostInfo {
270			let mut origin = origin;
271			let who = ensure_signed(origin.clone())?;
272			let pseudonym = derivative_account_id(who, index);
273			origin.set_caller_from(frame_system::RawOrigin::Signed(pseudonym));
274			let info = call.get_dispatch_info();
275			let result = call.dispatch(origin);
276			// Always take into account the base weight of this call.
277			let mut weight = T::WeightInfo::as_derivative()
278				.saturating_add(T::DbWeight::get().reads_writes(1, 1));
279			// Add the real weight of the dispatch.
280			weight = weight.saturating_add(extract_actual_weight(&result, &info));
281			result
282				.map_err(|mut err| {
283					err.post_info = Some(weight).into();
284					err
285				})
286				.map(|_| Some(weight).into())
287		}
288
289		/// Send a batch of dispatch calls and atomically execute them.
290		/// The whole transaction will rollback and fail if any of the calls failed.
291		///
292		/// May be called from any origin except `None`.
293		///
294		/// - `calls`: The calls to be dispatched from the same origin. The number of call must not
295		///   exceed the constant: `batched_calls_limit` (available in constant metadata).
296		///
297		/// If origin is root then the calls are dispatched without checking origin filter. (This
298		/// includes bypassing `frame_system::Config::BaseCallFilter`).
299		///
300		/// ## Complexity
301		/// - O(C) where C is the number of calls to be batched.
302		#[pallet::call_index(2)]
303		#[pallet::weight({
304			let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
305			let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32));
306			(dispatch_weight, dispatch_class)
307		})]
308		pub fn batch_all(
309			origin: OriginFor<T>,
310			calls: Vec<<T as Config>::RuntimeCall>,
311		) -> DispatchResultWithPostInfo {
312			// Do not allow the `None` origin.
313			if ensure_none(origin.clone()).is_ok() {
314				return Err(BadOrigin.into())
315			}
316
317			let is_root = ensure_root(origin.clone()).is_ok();
318			let calls_len = calls.len();
319			ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
320
321			// Track the actual weight of each of the batch calls.
322			let mut weight = Weight::zero();
323			for (index, call) in calls.into_iter().enumerate() {
324				let info = call.get_dispatch_info();
325				// If origin is root, bypass any dispatch filter; root can call anything.
326				let result = if is_root {
327					call.dispatch_bypass_filter(origin.clone())
328				} else {
329					let mut filtered_origin = origin.clone();
330					// Don't allow users to nest `batch_all` calls.
331					filtered_origin.add_filter(
332						move |c: &<T as frame_system::Config>::RuntimeCall| {
333							let c = <T as Config>::RuntimeCall::from_ref(c);
334							!matches!(c.is_sub_type(), Some(Call::batch_all { .. }))
335						},
336					);
337					call.dispatch(filtered_origin)
338				};
339				// Add the weight of this call.
340				weight = weight.saturating_add(extract_actual_weight(&result, &info));
341				result.map_err(|mut err| {
342					// Take the weight of this function itself into account.
343					let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32);
344					// Return the actual used weight + base_weight of this call.
345					err.post_info = Some(base_weight.saturating_add(weight)).into();
346					err
347				})?;
348				Self::deposit_event(Event::ItemCompleted);
349			}
350			Self::deposit_event(Event::BatchCompleted);
351			let base_weight = T::WeightInfo::batch_all(calls_len as u32);
352			Ok(Some(base_weight.saturating_add(weight)).into())
353		}
354
355		/// Dispatches a function call with a provided origin.
356		///
357		/// The dispatch origin for this call must be _Root_.
358		///
359		/// ## Complexity
360		/// - O(1).
361		#[pallet::call_index(3)]
362		#[pallet::weight({
363			let dispatch_info = call.get_dispatch_info();
364			(
365				T::WeightInfo::dispatch_as()
366					.saturating_add(dispatch_info.call_weight),
367				dispatch_info.class,
368			)
369		})]
370		pub fn dispatch_as(
371			origin: OriginFor<T>,
372			as_origin: Box<T::PalletsOrigin>,
373			call: Box<<T as Config>::RuntimeCall>,
374		) -> DispatchResult {
375			ensure_root(origin)?;
376
377			let res = call.dispatch_bypass_filter((*as_origin).into());
378
379			Self::deposit_event(Event::DispatchedAs {
380				result: res.map(|_| ()).map_err(|e| e.error),
381			});
382			Ok(())
383		}
384
385		/// Send a batch of dispatch calls.
386		/// Unlike `batch`, it allows errors and won't interrupt.
387		///
388		/// May be called from any origin except `None`.
389		///
390		/// - `calls`: The calls to be dispatched from the same origin. The number of call must not
391		///   exceed the constant: `batched_calls_limit` (available in constant metadata).
392		///
393		/// If origin is root then the calls are dispatch without checking origin filter. (This
394		/// includes bypassing `frame_system::Config::BaseCallFilter`).
395		///
396		/// ## Complexity
397		/// - O(C) where C is the number of calls to be batched.
398		#[pallet::call_index(4)]
399		#[pallet::weight({
400			let (dispatch_weight, dispatch_class) = Pallet::<T>::weight_and_dispatch_class(&calls);
401			let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32));
402			(dispatch_weight, dispatch_class)
403		})]
404		pub fn force_batch(
405			origin: OriginFor<T>,
406			calls: Vec<<T as Config>::RuntimeCall>,
407		) -> DispatchResultWithPostInfo {
408			// Do not allow the `None` origin.
409			if ensure_none(origin.clone()).is_ok() {
410				return Err(BadOrigin.into())
411			}
412
413			let is_root = ensure_root(origin.clone()).is_ok();
414			let calls_len = calls.len();
415			ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::<T>::TooManyCalls);
416
417			// Track the actual weight of each of the batch calls.
418			let mut weight = Weight::zero();
419			// Track failed dispatch occur.
420			let mut has_error: bool = false;
421			for call in calls.into_iter() {
422				let info = call.get_dispatch_info();
423				// If origin is root, don't apply any dispatch filters; root can call anything.
424				let result = if is_root {
425					call.dispatch_bypass_filter(origin.clone())
426				} else {
427					call.dispatch(origin.clone())
428				};
429				// Add the weight of this call.
430				weight = weight.saturating_add(extract_actual_weight(&result, &info));
431				if let Err(e) = result {
432					has_error = true;
433					Self::deposit_event(Event::ItemFailed { error: e.error });
434				} else {
435					Self::deposit_event(Event::ItemCompleted);
436				}
437			}
438			if has_error {
439				Self::deposit_event(Event::BatchCompletedWithErrors);
440			} else {
441				Self::deposit_event(Event::BatchCompleted);
442			}
443			let base_weight = T::WeightInfo::batch(calls_len as u32);
444			Ok(Some(base_weight.saturating_add(weight)).into())
445		}
446
447		/// Dispatch a function call with a specified weight.
448		///
449		/// This function does not check the weight of the call, and instead allows the
450		/// Root origin to specify the weight of the call.
451		///
452		/// The dispatch origin for this call must be _Root_.
453		#[pallet::call_index(5)]
454		#[pallet::weight((*weight, call.get_dispatch_info().class))]
455		pub fn with_weight(
456			origin: OriginFor<T>,
457			call: Box<<T as Config>::RuntimeCall>,
458			weight: Weight,
459		) -> DispatchResult {
460			ensure_root(origin)?;
461			let _ = weight; // Explicitly don't check the the weight witness.
462
463			let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
464			res.map(|_| ()).map_err(|e| e.error)
465		}
466
467		/// Dispatch a fallback call in the event the main call fails to execute.
468		/// May be called from any origin except `None`.
469		///
470		/// This function first attempts to dispatch the `main` call.
471		/// If the `main` call fails, the `fallback` is attemted.
472		/// if the fallback is successfully dispatched, the weights of both calls
473		/// are accumulated and an event containing the main call error is deposited.
474		///
475		/// In the event of a fallback failure the whole call fails
476		/// with the weights returned.
477		///
478		/// - `main`: The main call to be dispatched. This is the primary action to execute.
479		/// - `fallback`: The fallback call to be dispatched in case the `main` call fails.
480		///
481		/// ## Dispatch Logic
482		/// - If the origin is `root`, both the main and fallback calls are executed without
483		///   applying any origin filters.
484		/// - If the origin is not `root`, the origin filter is applied to both the `main` and
485		///   `fallback` calls.
486		///
487		/// ## Use Case
488		/// - Some use cases might involve submitting a `batch` type call in either main, fallback
489		///   or both.
490		#[pallet::call_index(6)]
491		#[pallet::weight({
492			let main = main.get_dispatch_info();
493			let fallback = fallback.get_dispatch_info();
494			(
495				T::WeightInfo::if_else()
496					.saturating_add(main.call_weight)
497					.saturating_add(fallback.call_weight),
498				if main.class == Operational && fallback.class == Operational { Operational } else { Normal },
499			)
500		})]
501		pub fn if_else(
502			origin: OriginFor<T>,
503			main: Box<<T as Config>::RuntimeCall>,
504			fallback: Box<<T as Config>::RuntimeCall>,
505		) -> DispatchResultWithPostInfo {
506			// Do not allow the `None` origin.
507			if ensure_none(origin.clone()).is_ok() {
508				return Err(BadOrigin.into());
509			}
510
511			let is_root = ensure_root(origin.clone()).is_ok();
512
513			// Track the weights
514			let mut weight = T::WeightInfo::if_else();
515
516			let main_info = main.get_dispatch_info();
517
518			// Execute the main call first
519			let main_result = if is_root {
520				main.dispatch_bypass_filter(origin.clone())
521			} else {
522				main.dispatch(origin.clone())
523			};
524
525			// Add weight of the main call
526			weight = weight.saturating_add(extract_actual_weight(&main_result, &main_info));
527
528			let Err(main_error) = main_result else {
529				// If the main result is Ok, we skip the fallback logic entirely
530				Self::deposit_event(Event::IfElseMainSuccess);
531				return Ok(Some(weight).into());
532			};
533
534			// If the main call failed, execute the fallback call
535			let fallback_info = fallback.get_dispatch_info();
536
537			let fallback_result = if is_root {
538				fallback.dispatch_bypass_filter(origin.clone())
539			} else {
540				fallback.dispatch(origin)
541			};
542
543			// Add weight of the fallback call
544			weight = weight.saturating_add(extract_actual_weight(&fallback_result, &fallback_info));
545
546			let Err(fallback_error) = fallback_result else {
547				// Fallback succeeded.
548				Self::deposit_event(Event::IfElseFallbackCalled { main_error: main_error.error });
549				return Ok(Some(weight).into());
550			};
551
552			// Both calls have failed, return fallback error
553			Err(sp_runtime::DispatchErrorWithPostInfo {
554				error: fallback_error.error,
555				post_info: Some(weight).into(),
556			})
557		}
558
559		/// Dispatches a function call with a provided origin.
560		///
561		/// Almost the same as [`Pallet::dispatch_as`] but forwards any error of the inner call.
562		///
563		/// The dispatch origin for this call must be _Root_.
564		#[pallet::call_index(7)]
565		#[pallet::weight({
566			let dispatch_info = call.get_dispatch_info();
567			(
568				T::WeightInfo::dispatch_as_fallible()
569					.saturating_add(dispatch_info.call_weight),
570				dispatch_info.class,
571			)
572		})]
573		pub fn dispatch_as_fallible(
574			origin: OriginFor<T>,
575			as_origin: Box<T::PalletsOrigin>,
576			call: Box<<T as Config>::RuntimeCall>,
577		) -> DispatchResult {
578			ensure_root(origin)?;
579
580			call.dispatch_bypass_filter((*as_origin).into()).map_err(|e| e.error)?;
581
582			Self::deposit_event(Event::DispatchedAs { result: Ok(()) });
583
584			Ok(())
585		}
586	}
587
588	impl<T: Config> Pallet<T> {
589		/// Get the accumulated `weight` and the dispatch class for the given `calls`.
590		fn weight_and_dispatch_class(
591			calls: &[<T as Config>::RuntimeCall],
592		) -> (Weight, DispatchClass) {
593			let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info());
594			let (dispatch_weight, dispatch_class) = dispatch_infos.fold(
595				(Weight::zero(), DispatchClass::Operational),
596				|(total_weight, dispatch_class): (Weight, DispatchClass), di| {
597					(
598						total_weight.saturating_add(di.call_weight),
599						// If not all are `Operational`, we want to use `DispatchClass::Normal`.
600						if di.class == DispatchClass::Normal { di.class } else { dispatch_class },
601					)
602				},
603			);
604
605			(dispatch_weight, dispatch_class)
606		}
607	}
608}
609
610/// A pallet identifier. These are per pallet and should be stored in a registry somewhere.
611#[derive(Clone, Copy, Eq, PartialEq, Encode, Decode)]
612struct IndexedUtilityPalletId(u16);
613
614impl TypeId for IndexedUtilityPalletId {
615	const TYPE_ID: [u8; 4] = *b"suba";
616}
617
618impl<T: Config> Pallet<T> {
619	#[deprecated(
620		note = "`Pallet::derivative_account_id` will be removed after August 2025. Please instead use the freestanding module function `derivative_account_id`."
621	)]
622	pub fn derivative_account_id(who: T::AccountId, index: u16) -> T::AccountId {
623		derivative_account_id(who, index)
624	}
625}
626
627/// Derive a derivative account ID from the owner account and the sub-account index.
628///
629/// The derived account with `index` of `who` is defined as:
630/// `b2b256("modlpy/utilisuba" ++ who ++ index)` where index is encoded as fixed size SCALE u16, the
631/// prefix string as SCALE u8 vector and `who` by its canonical SCALE encoding. The resulting
632/// account ID is then decoded from the hash with trailing zero bytes in case that the AccountId
633/// type is longer than 32 bytes. Note that this *could* lead to collisions when using AccountId
634/// types that are shorter than 32 bytes, especially in testing environments that are using u64.
635pub fn derivative_account_id<AccountId: Encode + Decode>(who: AccountId, index: u16) -> AccountId {
636	let entropy = (b"modlpy/utilisuba", who, index).using_encoded(blake2_256);
637	Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
638		.expect("infinite length input; no invalid inputs for type; qed")
639}