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