referrerpolicy=no-referrer-when-downgrade

pallet_multisig/
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//! # Multisig pallet
19//! A pallet for doing multisig dispatch.
20//!
21//! - [`Config`]
22//! - [`Call`]
23//!
24//! ## Overview
25//!
26//! This pallet contains functionality for multi-signature dispatch, a (potentially) stateful
27//! operation, allowing multiple signed
28//! origins (accounts) to coordinate and dispatch a call from a well-known origin, derivable
29//! deterministically from the set of account IDs and the threshold number of accounts from the
30//! set that must approve it. In the case that the threshold is just one then this is a stateless
31//! operation. This is useful for multisig wallets where cryptographic threshold signatures are
32//! not available or desired.
33//!
34//! ## Interface
35//!
36//! ### Dispatchable Functions
37//!
38//! * `as_multi` - Approve and if possible dispatch a call from a composite origin formed from a
39//!   number of signed origins.
40//! * `approve_as_multi` - Approve a call from a composite origin.
41//! * `cancel_as_multi` - Cancel a call from a composite origin.
42
43// Ensure we're `no_std` when compiling for Wasm.
44#![cfg_attr(not(feature = "std"), no_std)]
45
46mod benchmarking;
47pub mod migrations;
48mod tests;
49pub mod weights;
50
51extern crate alloc;
52use alloc::{boxed::Box, vec, vec::Vec};
53use frame::{
54	prelude::*,
55	traits::{Currency, ReservableCurrency},
56};
57use frame_system::RawOrigin;
58pub use weights::WeightInfo;
59
60/// Re-export all pallet items.
61pub use pallet::*;
62
63/// The log target of this pallet.
64pub const LOG_TARGET: &'static str = "runtime::multisig";
65
66// syntactic sugar for logging.
67#[macro_export]
68macro_rules! log {
69	($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
70		log::$level!(
71			target: crate::LOG_TARGET,
72			concat!("[{:?}] ✍️ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
73		)
74	};
75}
76
77pub type BalanceOf<T> =
78	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
79
80pub type BlockNumberFor<T> =
81	<<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
82
83/// A global extrinsic index, formed as the extrinsic index within a block, together with that
84/// block's height. This allows a transaction in which a multisig operation of a particular
85/// composite was created to be uniquely identified.
86#[derive(
87	Copy,
88	Clone,
89	Eq,
90	PartialEq,
91	Encode,
92	Decode,
93	DecodeWithMemTracking,
94	Default,
95	RuntimeDebug,
96	TypeInfo,
97	MaxEncodedLen,
98)]
99pub struct Timepoint<BlockNumber> {
100	/// The height of the chain at the point in time.
101	pub height: BlockNumber,
102	/// The index of the extrinsic at the point in time.
103	pub index: u32,
104}
105
106/// An open multisig operation.
107#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)]
108#[scale_info(skip_type_params(MaxApprovals))]
109pub struct Multisig<BlockNumber, Balance, AccountId, MaxApprovals>
110where
111	MaxApprovals: Get<u32>,
112{
113	/// The extrinsic when the multisig operation was opened.
114	pub when: Timepoint<BlockNumber>,
115	/// The amount held in reserve of the `depositor`, to be returned once the operation ends.
116	pub deposit: Balance,
117	/// The account who opened it (i.e. the first to approve it).
118	pub depositor: AccountId,
119	/// The approvals achieved so far, including the depositor. Always sorted.
120	pub approvals: BoundedVec<AccountId, MaxApprovals>,
121}
122
123type CallHash = [u8; 32];
124
125enum CallOrHash<T: Config> {
126	Call(<T as Config>::RuntimeCall),
127	Hash([u8; 32]),
128}
129
130#[frame::pallet]
131pub mod pallet {
132	use super::*;
133
134	#[pallet::config]
135	pub trait Config: frame_system::Config {
136		/// The overarching event type.
137		#[allow(deprecated)]
138		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
139
140		/// The overarching call type.
141		type RuntimeCall: Parameter
142			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
143			+ GetDispatchInfo
144			+ From<frame_system::Call<Self>>;
145
146		/// The currency mechanism.
147		type Currency: ReservableCurrency<Self::AccountId>;
148
149		/// The base amount of currency needed to reserve for creating a multisig execution or to
150		/// store a dispatch call for later.
151		///
152		/// This is held for an additional storage item whose value size is
153		/// `4 + sizeof((BlockNumber, Balance, AccountId))` bytes and whose key size is
154		/// `32 + sizeof(AccountId)` bytes.
155		#[pallet::constant]
156		type DepositBase: Get<BalanceOf<Self>>;
157
158		/// The amount of currency needed per unit threshold when creating a multisig execution.
159		///
160		/// This is held for adding 32 bytes more into a pre-existing storage value.
161		#[pallet::constant]
162		type DepositFactor: Get<BalanceOf<Self>>;
163
164		/// The maximum amount of signatories allowed in the multisig.
165		#[pallet::constant]
166		type MaxSignatories: Get<u32>;
167
168		/// Weight information for extrinsics in this pallet.
169		type WeightInfo: weights::WeightInfo;
170
171		/// Query the current block number.
172		///
173		/// Must return monotonically increasing values when called from consecutive blocks.
174		/// Can be configured to return either:
175		/// - the local block number of the runtime via `frame_system::Pallet`
176		/// - a remote block number, eg from the relay chain through `RelaychainDataProvider`
177		/// - an arbitrary value through a custom implementation of the trait
178		///
179		/// There is currently no migration provided to "hot-swap" block number providers and it may
180		/// result in undefined behavior when doing so. Parachains are therefore best off setting
181		/// this to their local block number provider if they have the pallet already deployed.
182		///
183		/// Suggested values:
184		/// - Solo- and Relay-chains: `frame_system::Pallet`
185		/// - Parachains that may produce blocks sparingly or only when needed (on-demand):
186		///   - already have the pallet deployed: `frame_system::Pallet`
187		///   - are freshly deploying this pallet: `RelaychainDataProvider`
188		/// - Parachains with a reliably block production rate (PLO or bulk-coretime):
189		///   - already have the pallet deployed: `frame_system::Pallet`
190		///   - are freshly deploying this pallet: no strong recommendation. Both local and remote
191		///     providers can be used. Relay provider can be a bit better in cases where the
192		///     parachain is lagging its block production to avoid clock skew.
193		type BlockNumberProvider: BlockNumberProvider;
194	}
195
196	/// The in-code storage version.
197	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
198
199	#[pallet::pallet]
200	#[pallet::storage_version(STORAGE_VERSION)]
201	pub struct Pallet<T>(_);
202
203	/// The set of open multisig operations.
204	#[pallet::storage]
205	pub type Multisigs<T: Config> = StorageDoubleMap<
206		_,
207		Twox64Concat,
208		T::AccountId,
209		Blake2_128Concat,
210		[u8; 32],
211		Multisig<BlockNumberFor<T>, BalanceOf<T>, T::AccountId, T::MaxSignatories>,
212	>;
213
214	#[pallet::error]
215	pub enum Error<T> {
216		/// Threshold must be 2 or greater.
217		MinimumThreshold,
218		/// Call is already approved by this signatory.
219		AlreadyApproved,
220		/// Call doesn't need any (more) approvals.
221		NoApprovalsNeeded,
222		/// There are too few signatories in the list.
223		TooFewSignatories,
224		/// There are too many signatories in the list.
225		TooManySignatories,
226		/// The signatories were provided out of order; they should be ordered.
227		SignatoriesOutOfOrder,
228		/// The sender was contained in the other signatories; it shouldn't be.
229		SenderInSignatories,
230		/// Multisig operation not found in storage.
231		NotFound,
232		/// Only the account that originally created the multisig is able to cancel it or update
233		/// its deposits.
234		NotOwner,
235		/// No timepoint was given, yet the multisig operation is already underway.
236		NoTimepoint,
237		/// A different timepoint was given to the multisig operation that is underway.
238		WrongTimepoint,
239		/// A timepoint was given, yet no multisig operation is underway.
240		UnexpectedTimepoint,
241		/// The maximum weight information provided was too low.
242		MaxWeightTooLow,
243		/// The data to be stored is already stored.
244		AlreadyStored,
245	}
246
247	#[pallet::event]
248	#[pallet::generate_deposit(pub(super) fn deposit_event)]
249	pub enum Event<T: Config> {
250		/// A new multisig operation has begun.
251		NewMultisig { approving: T::AccountId, multisig: T::AccountId, call_hash: CallHash },
252		/// A multisig operation has been approved by someone.
253		MultisigApproval {
254			approving: T::AccountId,
255			timepoint: Timepoint<BlockNumberFor<T>>,
256			multisig: T::AccountId,
257			call_hash: CallHash,
258		},
259		/// A multisig operation has been executed.
260		MultisigExecuted {
261			approving: T::AccountId,
262			timepoint: Timepoint<BlockNumberFor<T>>,
263			multisig: T::AccountId,
264			call_hash: CallHash,
265			result: DispatchResult,
266		},
267		/// A multisig operation has been cancelled.
268		MultisigCancelled {
269			cancelling: T::AccountId,
270			timepoint: Timepoint<BlockNumberFor<T>>,
271			multisig: T::AccountId,
272			call_hash: CallHash,
273		},
274		/// The deposit for a multisig operation has been updated/poked.
275		DepositPoked {
276			who: T::AccountId,
277			call_hash: CallHash,
278			old_deposit: BalanceOf<T>,
279			new_deposit: BalanceOf<T>,
280		},
281	}
282
283	#[pallet::hooks]
284	impl<T: Config> Hooks<frame_system::pallet_prelude::BlockNumberFor<T>> for Pallet<T> {}
285
286	#[pallet::call]
287	impl<T: Config> Pallet<T> {
288		/// Immediately dispatch a multi-signature call using a single approval from the caller.
289		///
290		/// The dispatch origin for this call must be _Signed_.
291		///
292		/// - `other_signatories`: The accounts (other than the sender) who are part of the
293		/// multi-signature, but do not participate in the approval process.
294		/// - `call`: The call to be executed.
295		///
296		/// Result is equivalent to the dispatched result.
297		///
298		/// ## Complexity
299		/// O(Z + C) where Z is the length of the call and C its execution weight.
300		#[pallet::call_index(0)]
301		#[pallet::weight({
302			let dispatch_info = call.get_dispatch_info();
303			(
304				T::WeightInfo::as_multi_threshold_1(call.using_encoded(|c| c.len() as u32))
305					// AccountData for inner call origin accountdata.
306					.saturating_add(T::DbWeight::get().reads_writes(1, 1))
307					.saturating_add(dispatch_info.call_weight),
308				dispatch_info.class,
309			)
310		})]
311		pub fn as_multi_threshold_1(
312			origin: OriginFor<T>,
313			other_signatories: Vec<T::AccountId>,
314			call: Box<<T as Config>::RuntimeCall>,
315		) -> DispatchResultWithPostInfo {
316			let who = ensure_signed(origin)?;
317			let max_sigs = T::MaxSignatories::get() as usize;
318			ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
319			let other_signatories_len = other_signatories.len();
320			ensure!(other_signatories_len < max_sigs, Error::<T>::TooManySignatories);
321			let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
322
323			let id = Self::multi_account_id(&signatories, 1);
324
325			let (call_len, call_hash) = call.using_encoded(|c| (c.len(), blake2_256(&c)));
326			let result = call.dispatch(RawOrigin::Signed(id.clone()).into());
327
328			Self::deposit_event(Event::MultisigExecuted {
329				approving: who,
330				timepoint: Self::timepoint(),
331				multisig: id,
332				call_hash,
333				result: result.map(|_| ()).map_err(|e| e.error),
334			});
335
336			result
337				.map(|post_dispatch_info| {
338					post_dispatch_info
339						.actual_weight
340						.map(|actual_weight| {
341							T::WeightInfo::as_multi_threshold_1(call_len as u32)
342								.saturating_add(actual_weight)
343						})
344						.into()
345				})
346				.map_err(|err| match err.post_info.actual_weight {
347					Some(actual_weight) => {
348						let weight_used = T::WeightInfo::as_multi_threshold_1(call_len as u32)
349							.saturating_add(actual_weight);
350						let post_info = Some(weight_used).into();
351						DispatchErrorWithPostInfo { post_info, error: err.error }
352					},
353					None => err,
354				})
355		}
356
357		/// Register approval for a dispatch to be made from a deterministic composite account if
358		/// approved by a total of `threshold - 1` of `other_signatories`.
359		///
360		/// If there are enough, then dispatch the call.
361		///
362		/// Payment: `DepositBase` will be reserved if this is the first approval, plus
363		/// `threshold` times `DepositFactor`. It is returned once this dispatch happens or
364		/// is cancelled.
365		///
366		/// The dispatch origin for this call must be _Signed_.
367		///
368		/// - `threshold`: The total number of approvals for this dispatch before it is executed.
369		/// - `other_signatories`: The accounts (other than the sender) who can approve this
370		/// dispatch. May not be empty.
371		/// - `maybe_timepoint`: If this is the first approval, then this must be `None`. If it is
372		/// not the first approval, then it must be `Some`, with the timepoint (block number and
373		/// transaction index) of the first approval transaction.
374		/// - `call`: The call to be executed.
375		///
376		/// NOTE: Unless this is the final approval, you will generally want to use
377		/// `approve_as_multi` instead, since it only requires a hash of the call.
378		///
379		/// Result is equivalent to the dispatched result if `threshold` is exactly `1`. Otherwise
380		/// on success, result is `Ok` and the result from the interior call, if it was executed,
381		/// may be found in the deposited `MultisigExecuted` event.
382		///
383		/// ## Complexity
384		/// - `O(S + Z + Call)`.
385		/// - Up to one balance-reserve or unreserve operation.
386		/// - One passthrough operation, one insert, both `O(S)` where `S` is the number of
387		///   signatories. `S` is capped by `MaxSignatories`, with weight being proportional.
388		/// - One call encode & hash, both of complexity `O(Z)` where `Z` is tx-len.
389		/// - One encode & hash, both of complexity `O(S)`.
390		/// - Up to one binary search and insert (`O(logS + S)`).
391		/// - I/O: 1 read `O(S)`, up to 1 mutate `O(S)`. Up to one remove.
392		/// - One event.
393		/// - The weight of the `call`.
394		/// - Storage: inserts one item, value size bounded by `MaxSignatories`, with a deposit
395		///   taken for its lifetime of `DepositBase + threshold * DepositFactor`.
396		#[pallet::call_index(1)]
397		#[pallet::weight({
398			let s = other_signatories.len() as u32;
399			let z = call.using_encoded(|d| d.len()) as u32;
400
401			T::WeightInfo::as_multi_create(s, z)
402			.max(T::WeightInfo::as_multi_approve(s, z))
403			.max(T::WeightInfo::as_multi_complete(s, z))
404			.saturating_add(*max_weight)
405		})]
406		pub fn as_multi(
407			origin: OriginFor<T>,
408			threshold: u16,
409			other_signatories: Vec<T::AccountId>,
410			maybe_timepoint: Option<Timepoint<BlockNumberFor<T>>>,
411			call: Box<<T as Config>::RuntimeCall>,
412			max_weight: Weight,
413		) -> DispatchResultWithPostInfo {
414			let who = ensure_signed(origin)?;
415			Self::operate(
416				who,
417				threshold,
418				other_signatories,
419				maybe_timepoint,
420				CallOrHash::Call(*call),
421				max_weight,
422			)
423		}
424
425		/// Register approval for a dispatch to be made from a deterministic composite account if
426		/// approved by a total of `threshold - 1` of `other_signatories`.
427		///
428		/// Payment: `DepositBase` will be reserved if this is the first approval, plus
429		/// `threshold` times `DepositFactor`. It is returned once this dispatch happens or
430		/// is cancelled.
431		///
432		/// The dispatch origin for this call must be _Signed_.
433		///
434		/// - `threshold`: The total number of approvals for this dispatch before it is executed.
435		/// - `other_signatories`: The accounts (other than the sender) who can approve this
436		/// dispatch. May not be empty.
437		/// - `maybe_timepoint`: If this is the first approval, then this must be `None`. If it is
438		/// not the first approval, then it must be `Some`, with the timepoint (block number and
439		/// transaction index) of the first approval transaction.
440		/// - `call_hash`: The hash of the call to be executed.
441		///
442		/// NOTE: If this is the final approval, you will want to use `as_multi` instead.
443		///
444		/// ## Complexity
445		/// - `O(S)`.
446		/// - Up to one balance-reserve or unreserve operation.
447		/// - One passthrough operation, one insert, both `O(S)` where `S` is the number of
448		///   signatories. `S` is capped by `MaxSignatories`, with weight being proportional.
449		/// - One encode & hash, both of complexity `O(S)`.
450		/// - Up to one binary search and insert (`O(logS + S)`).
451		/// - I/O: 1 read `O(S)`, up to 1 mutate `O(S)`. Up to one remove.
452		/// - One event.
453		/// - Storage: inserts one item, value size bounded by `MaxSignatories`, with a deposit
454		///   taken for its lifetime of `DepositBase + threshold * DepositFactor`.
455		#[pallet::call_index(2)]
456		#[pallet::weight({
457			let s = other_signatories.len() as u32;
458
459			T::WeightInfo::approve_as_multi_create(s)
460				.max(T::WeightInfo::approve_as_multi_approve(s))
461				.saturating_add(*max_weight)
462		})]
463		pub fn approve_as_multi(
464			origin: OriginFor<T>,
465			threshold: u16,
466			other_signatories: Vec<T::AccountId>,
467			maybe_timepoint: Option<Timepoint<BlockNumberFor<T>>>,
468			call_hash: [u8; 32],
469			max_weight: Weight,
470		) -> DispatchResultWithPostInfo {
471			let who = ensure_signed(origin)?;
472			Self::operate(
473				who,
474				threshold,
475				other_signatories,
476				maybe_timepoint,
477				CallOrHash::Hash(call_hash),
478				max_weight,
479			)
480		}
481
482		/// Cancel a pre-existing, on-going multisig transaction. Any deposit reserved previously
483		/// for this operation will be unreserved on success.
484		///
485		/// The dispatch origin for this call must be _Signed_.
486		///
487		/// - `threshold`: The total number of approvals for this dispatch before it is executed.
488		/// - `other_signatories`: The accounts (other than the sender) who can approve this
489		/// dispatch. May not be empty.
490		/// - `timepoint`: The timepoint (block number and transaction index) of the first approval
491		/// transaction for this dispatch.
492		/// - `call_hash`: The hash of the call to be executed.
493		///
494		/// ## Complexity
495		/// - `O(S)`.
496		/// - Up to one balance-reserve or unreserve operation.
497		/// - One passthrough operation, one insert, both `O(S)` where `S` is the number of
498		///   signatories. `S` is capped by `MaxSignatories`, with weight being proportional.
499		/// - One encode & hash, both of complexity `O(S)`.
500		/// - One event.
501		/// - I/O: 1 read `O(S)`, one remove.
502		/// - Storage: removes one item.
503		#[pallet::call_index(3)]
504		#[pallet::weight(T::WeightInfo::cancel_as_multi(other_signatories.len() as u32))]
505		pub fn cancel_as_multi(
506			origin: OriginFor<T>,
507			threshold: u16,
508			other_signatories: Vec<T::AccountId>,
509			timepoint: Timepoint<BlockNumberFor<T>>,
510			call_hash: [u8; 32],
511		) -> DispatchResult {
512			let who = ensure_signed(origin)?;
513			ensure!(threshold >= 2, Error::<T>::MinimumThreshold);
514			let max_sigs = T::MaxSignatories::get() as usize;
515			ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
516			ensure!(other_signatories.len() < max_sigs, Error::<T>::TooManySignatories);
517			let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
518
519			let id = Self::multi_account_id(&signatories, threshold);
520
521			let m = <Multisigs<T>>::get(&id, call_hash).ok_or(Error::<T>::NotFound)?;
522			ensure!(m.when == timepoint, Error::<T>::WrongTimepoint);
523			ensure!(m.depositor == who, Error::<T>::NotOwner);
524
525			let err_amount = T::Currency::unreserve(&m.depositor, m.deposit);
526			debug_assert!(err_amount.is_zero());
527			<Multisigs<T>>::remove(&id, &call_hash);
528
529			Self::deposit_event(Event::MultisigCancelled {
530				cancelling: who,
531				timepoint,
532				multisig: id,
533				call_hash,
534			});
535			Ok(())
536		}
537
538		/// Poke the deposit reserved for an existing multisig operation.
539		///
540		/// The dispatch origin for this call must be _Signed_ and must be the original depositor of
541		/// the multisig operation.
542		///
543		/// The transaction fee is waived if the deposit amount has changed.
544		///
545		/// - `threshold`: The total number of approvals needed for this multisig.
546		/// - `other_signatories`: The accounts (other than the sender) who are part of the
547		///   multisig.
548		/// - `call_hash`: The hash of the call this deposit is reserved for.
549		///
550		/// Emits `DepositPoked` if successful.
551		#[pallet::call_index(4)]
552		#[pallet::weight(T::WeightInfo::poke_deposit(other_signatories.len() as u32))]
553		pub fn poke_deposit(
554			origin: OriginFor<T>,
555			threshold: u16,
556			other_signatories: Vec<T::AccountId>,
557			call_hash: [u8; 32],
558		) -> DispatchResultWithPostInfo {
559			let who = ensure_signed(origin)?;
560			ensure!(threshold >= 2, Error::<T>::MinimumThreshold);
561			let max_sigs = T::MaxSignatories::get() as usize;
562			ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
563			ensure!(other_signatories.len() < max_sigs, Error::<T>::TooManySignatories);
564			// Get the multisig account ID
565			let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
566			let id = Self::multi_account_id(&signatories, threshold);
567
568			Multisigs::<T>::try_mutate(
569				&id,
570				call_hash,
571				|maybe_multisig| -> DispatchResultWithPostInfo {
572					let mut multisig = maybe_multisig.take().ok_or(Error::<T>::NotFound)?;
573					ensure!(multisig.depositor == who, Error::<T>::NotOwner);
574
575					// Calculate the new deposit
576					let new_deposit = Self::deposit(threshold);
577					let old_deposit = multisig.deposit;
578
579					if new_deposit == old_deposit {
580						*maybe_multisig = Some(multisig);
581						return Ok(Pays::Yes.into());
582					}
583
584					// Update the reserved amount
585					if new_deposit > old_deposit {
586						let extra = new_deposit.saturating_sub(old_deposit);
587						T::Currency::reserve(&who, extra)?;
588					} else {
589						let excess = old_deposit.saturating_sub(new_deposit);
590						let remaining_unreserved = T::Currency::unreserve(&who, excess);
591						if !remaining_unreserved.is_zero() {
592							defensive!(
593								"Failed to unreserve for full amount for multisig. (Call Hash, Requested, Actual): ",
594								(call_hash, excess, excess.saturating_sub(remaining_unreserved))
595							);
596						}
597					}
598
599					// Update storage
600					multisig.deposit = new_deposit;
601					*maybe_multisig = Some(multisig);
602
603					// Emit event
604					Self::deposit_event(Event::DepositPoked {
605						who: who.clone(),
606						call_hash,
607						old_deposit,
608						new_deposit,
609					});
610
611					Ok(Pays::No.into())
612				},
613			)
614		}
615	}
616}
617
618impl<T: Config> Pallet<T> {
619	/// Derive a multi-account ID from the sorted list of accounts and the threshold that are
620	/// required.
621	///
622	/// NOTE: `who` must be sorted. If it is not, then you'll get the wrong answer.
623	pub fn multi_account_id(who: &[T::AccountId], threshold: u16) -> T::AccountId {
624		let entropy = (b"modlpy/utilisuba", who, threshold).using_encoded(blake2_256);
625		Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
626			.expect("infinite length input; no invalid inputs for type; qed")
627	}
628
629	fn operate(
630		who: T::AccountId,
631		threshold: u16,
632		other_signatories: Vec<T::AccountId>,
633		maybe_timepoint: Option<Timepoint<BlockNumberFor<T>>>,
634		call_or_hash: CallOrHash<T>,
635		max_weight: Weight,
636	) -> DispatchResultWithPostInfo {
637		ensure!(threshold >= 2, Error::<T>::MinimumThreshold);
638		let max_sigs = T::MaxSignatories::get() as usize;
639		ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
640		let other_signatories_len = other_signatories.len();
641		ensure!(other_signatories_len < max_sigs, Error::<T>::TooManySignatories);
642		let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
643
644		let id = Self::multi_account_id(&signatories, threshold);
645
646		// Threshold > 1; this means it's a multi-step operation. We extract the `call_hash`.
647		let (call_hash, call_len, maybe_call) = match call_or_hash {
648			CallOrHash::Call(call) => {
649				let (call_hash, call_len) = call.using_encoded(|d| (blake2_256(d), d.len()));
650				(call_hash, call_len, Some(call))
651			},
652			CallOrHash::Hash(h) => (h, 0, None),
653		};
654
655		// Branch on whether the operation has already started or not.
656		if let Some(mut m) = <Multisigs<T>>::get(&id, call_hash) {
657			// Yes; ensure that the timepoint exists and agrees.
658			let timepoint = maybe_timepoint.ok_or(Error::<T>::NoTimepoint)?;
659			ensure!(m.when == timepoint, Error::<T>::WrongTimepoint);
660
661			// Ensure that either we have not yet signed or that it is at threshold.
662			let mut approvals = m.approvals.len() as u16;
663			// We only bother with the approval if we're below threshold.
664			let maybe_pos = m.approvals.binary_search(&who).err().filter(|_| approvals < threshold);
665			// Bump approvals if not yet voted and the vote is needed.
666			if maybe_pos.is_some() {
667				approvals += 1;
668			}
669
670			// We only bother fetching/decoding call if we know that we're ready to execute.
671			if let Some(call) = maybe_call.filter(|_| approvals >= threshold) {
672				// verify weight
673				ensure!(
674					call.get_dispatch_info().call_weight.all_lte(max_weight),
675					Error::<T>::MaxWeightTooLow
676				);
677
678				// Clean up storage before executing call to avoid an possibility of reentrancy
679				// attack.
680				<Multisigs<T>>::remove(&id, call_hash);
681				T::Currency::unreserve(&m.depositor, m.deposit);
682
683				let result = call.dispatch(RawOrigin::Signed(id.clone()).into());
684				Self::deposit_event(Event::MultisigExecuted {
685					approving: who,
686					timepoint,
687					multisig: id,
688					call_hash,
689					result: result.map(|_| ()).map_err(|e| e.error),
690				});
691				Ok(get_result_weight(result)
692					.map(|actual_weight| {
693						T::WeightInfo::as_multi_complete(
694							other_signatories_len as u32,
695							call_len as u32,
696						)
697						.saturating_add(actual_weight)
698					})
699					.into())
700			} else {
701				// We cannot dispatch the call now; either it isn't available, or it is, but we
702				// don't have threshold approvals even with our signature.
703
704				if let Some(pos) = maybe_pos {
705					// Record approval.
706					m.approvals
707						.try_insert(pos, who.clone())
708						.map_err(|_| Error::<T>::TooManySignatories)?;
709					<Multisigs<T>>::insert(&id, call_hash, m);
710					Self::deposit_event(Event::MultisigApproval {
711						approving: who,
712						timepoint,
713						multisig: id,
714						call_hash,
715					});
716				} else {
717					// If we already approved and didn't store the Call, then this was useless and
718					// we report an error.
719					Err(Error::<T>::AlreadyApproved)?
720				}
721
722				let final_weight =
723					T::WeightInfo::as_multi_approve(other_signatories_len as u32, call_len as u32);
724				// Call is not made, so the actual weight does not include call
725				Ok(Some(final_weight).into())
726			}
727		} else {
728			// Not yet started; there should be no timepoint given.
729			ensure!(maybe_timepoint.is_none(), Error::<T>::UnexpectedTimepoint);
730
731			// Just start the operation by recording it in storage.
732			let deposit = Self::deposit(threshold);
733
734			T::Currency::reserve(&who, deposit)?;
735
736			let initial_approvals =
737				vec![who.clone()].try_into().map_err(|_| Error::<T>::TooManySignatories)?;
738
739			<Multisigs<T>>::insert(
740				&id,
741				call_hash,
742				Multisig {
743					when: Self::timepoint(),
744					deposit,
745					depositor: who.clone(),
746					approvals: initial_approvals,
747				},
748			);
749			Self::deposit_event(Event::NewMultisig { approving: who, multisig: id, call_hash });
750
751			let final_weight =
752				T::WeightInfo::as_multi_create(other_signatories_len as u32, call_len as u32);
753			// Call is not made, so the actual weight does not include call
754			Ok(Some(final_weight).into())
755		}
756	}
757
758	/// The current `Timepoint`.
759	pub fn timepoint() -> Timepoint<BlockNumberFor<T>> {
760		Timepoint {
761			height: T::BlockNumberProvider::current_block_number(),
762			index: <frame_system::Pallet<T>>::extrinsic_index().unwrap_or_default(),
763		}
764	}
765
766	/// Check that signatories is sorted and doesn't contain sender, then insert sender.
767	fn ensure_sorted_and_insert(
768		other_signatories: Vec<T::AccountId>,
769		who: T::AccountId,
770	) -> Result<Vec<T::AccountId>, DispatchError> {
771		let mut signatories = other_signatories;
772		let mut maybe_last = None;
773		let mut index = 0;
774		for item in signatories.iter() {
775			if let Some(last) = maybe_last {
776				ensure!(last < item, Error::<T>::SignatoriesOutOfOrder);
777			}
778			if item <= &who {
779				ensure!(item != &who, Error::<T>::SenderInSignatories);
780				index += 1;
781			}
782			maybe_last = Some(item);
783		}
784		signatories.insert(index, who);
785		Ok(signatories)
786	}
787
788	/// Calculate the deposit for a multisig operation.
789	///
790	/// The deposit is calculated as `DepositBase + DepositFactor * threshold`.
791	pub fn deposit(threshold: u16) -> BalanceOf<T> {
792		T::DepositBase::get() + T::DepositFactor::get() * threshold.into()
793	}
794}
795
796/// Return the weight of a dispatch call result as an `Option`.
797///
798/// Will return the weight regardless of what the state of the result is.
799fn get_result_weight(result: DispatchResultWithPostInfo) -> Option<Weight> {
800	match result {
801		Ok(post_info) => post_info.actual_weight,
802		Err(err) => err.post_info.actual_weight,
803	}
804}