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	Debug,
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	Debug,
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 the approval threshold is met (including the sender's approval), this will
372		/// immediately execute the call.** This is the only way to execute a multisig call -
373		/// `approve_as_multi` will never trigger execution.
374		///
375		/// Payment: `DepositBase` will be reserved if this is the first approval, plus
376		/// `threshold` times `DepositFactor`. It is returned once this dispatch happens or
377		/// is cancelled.
378		///
379		/// The dispatch origin for this call must be _Signed_.
380		///
381		/// - `threshold`: The total number of approvals for this dispatch before it is executed.
382		/// - `other_signatories`: The accounts (other than the sender) who can approve this
383		/// dispatch. May not be empty.
384		/// - `maybe_timepoint`: If this is the first approval, then this must be `None`. If it is
385		/// not the first approval, then it must be `Some`, with the timepoint (block number and
386		/// transaction index) of the first approval transaction.
387		/// - `call`: The call to be executed.
388		///
389		/// NOTE: For intermediate approvals (not the final approval), you should generally use
390		/// `approve_as_multi` instead, since it only requires a hash of the call and is more
391		/// efficient.
392		///
393		/// Result is equivalent to the dispatched result if `threshold` is exactly `1`. Otherwise
394		/// on success, result is `Ok` and the result from the interior call, if it was executed,
395		/// may be found in the deposited `MultisigExecuted` event.
396		///
397		/// ## Complexity
398		/// - `O(S + Z + Call)`.
399		/// - Up to one balance-reserve or unreserve operation.
400		/// - One passthrough operation, one insert, both `O(S)` where `S` is the number of
401		///   signatories. `S` is capped by `MaxSignatories`, with weight being proportional.
402		/// - One call encode & hash, both of complexity `O(Z)` where `Z` is tx-len.
403		/// - One encode & hash, both of complexity `O(S)`.
404		/// - Up to one binary search and insert (`O(logS + S)`).
405		/// - I/O: 1 read `O(S)`, up to 1 mutate `O(S)`. Up to one remove.
406		/// - One event.
407		/// - The weight of the `call`.
408		/// - Storage: inserts one item, value size bounded by `MaxSignatories`, with a deposit
409		///   taken for its lifetime of `DepositBase + threshold * DepositFactor`.
410		#[pallet::call_index(1)]
411		#[pallet::weight({
412			let s = other_signatories.len() as u32;
413			let z = call.using_encoded(|d| d.len()) as u32;
414
415			T::WeightInfo::as_multi_create(s, z)
416			.max(T::WeightInfo::as_multi_approve(s, z))
417			.max(T::WeightInfo::as_multi_complete(s, z))
418			.saturating_add(*max_weight)
419		})]
420		pub fn as_multi(
421			origin: OriginFor<T>,
422			threshold: u16,
423			other_signatories: Vec<T::AccountId>,
424			maybe_timepoint: Option<Timepoint<BlockNumberFor<T>>>,
425			call: Box<<T as Config>::RuntimeCall>,
426			max_weight: Weight,
427		) -> DispatchResultWithPostInfo {
428			let who = ensure_signed(origin)?;
429			Self::operate(
430				who,
431				threshold,
432				other_signatories,
433				maybe_timepoint,
434				CallOrHash::Call(*call),
435				max_weight,
436			)
437		}
438
439		/// Register approval for a dispatch to be made from a deterministic composite account if
440		/// approved by a total of `threshold - 1` of `other_signatories`.
441		///
442		/// **This function will NEVER execute the call, even if the approval threshold is
443		/// reached.** It only registers approval. To actually execute the call, `as_multi` must
444		/// be called with the full call data by any of the signatories.
445		///
446		/// This function is more efficient than `as_multi` for intermediate approvals since it
447		/// only requires the call hash, not the full call data.
448		///
449		/// Payment: `DepositBase` will be reserved if this is the first approval, plus
450		/// `threshold` times `DepositFactor`. It is returned once this dispatch happens or
451		/// is cancelled.
452		///
453		/// The dispatch origin for this call must be _Signed_.
454		///
455		/// - `threshold`: The total number of approvals for this dispatch before it is executed.
456		/// - `other_signatories`: The accounts (other than the sender) who can approve this
457		/// dispatch. May not be empty.
458		/// - `maybe_timepoint`: If this is the first approval, then this must be `None`. If it is
459		/// not the first approval, then it must be `Some`, with the timepoint (block number and
460		/// transaction index) of the first approval transaction.
461		/// - `call_hash`: The hash of the call to be executed.
462		///
463		/// NOTE: To execute the call after approvals are gathered, any signatory must call
464		/// `as_multi` with the full call data. This function cannot execute the call.
465		///
466		/// ## Complexity
467		/// - `O(S)`.
468		/// - Up to one balance-reserve or unreserve operation.
469		/// - One passthrough operation, one insert, both `O(S)` where `S` is the number of
470		///   signatories. `S` is capped by `MaxSignatories`, with weight being proportional.
471		/// - One encode & hash, both of complexity `O(S)`.
472		/// - Up to one binary search and insert (`O(logS + S)`).
473		/// - I/O: 1 read `O(S)`, up to 1 mutate `O(S)`. Up to one remove.
474		/// - One event.
475		/// - Storage: inserts one item, value size bounded by `MaxSignatories`, with a deposit
476		///   taken for its lifetime of `DepositBase + threshold * DepositFactor`.
477		#[pallet::call_index(2)]
478		#[pallet::weight({
479			let s = other_signatories.len() as u32;
480
481			T::WeightInfo::approve_as_multi_create(s)
482				.max(T::WeightInfo::approve_as_multi_approve(s))
483				.saturating_add(*max_weight)
484		})]
485		pub fn approve_as_multi(
486			origin: OriginFor<T>,
487			threshold: u16,
488			other_signatories: Vec<T::AccountId>,
489			maybe_timepoint: Option<Timepoint<BlockNumberFor<T>>>,
490			call_hash: [u8; 32],
491			max_weight: Weight,
492		) -> DispatchResultWithPostInfo {
493			let who = ensure_signed(origin)?;
494			Self::operate(
495				who,
496				threshold,
497				other_signatories,
498				maybe_timepoint,
499				CallOrHash::Hash(call_hash),
500				max_weight,
501			)
502		}
503
504		/// Cancel a pre-existing, on-going multisig transaction. Any deposit reserved previously
505		/// for this operation will be unreserved on success.
506		///
507		/// The dispatch origin for this call must be _Signed_.
508		///
509		/// - `threshold`: The total number of approvals for this dispatch before it is executed.
510		/// - `other_signatories`: The accounts (other than the sender) who can approve this
511		/// dispatch. May not be empty.
512		/// - `timepoint`: The timepoint (block number and transaction index) of the first approval
513		/// transaction for this dispatch.
514		/// - `call_hash`: The hash of the call to be executed.
515		///
516		/// ## Complexity
517		/// - `O(S)`.
518		/// - Up to one balance-reserve or unreserve operation.
519		/// - One passthrough operation, one insert, both `O(S)` where `S` is the number of
520		///   signatories. `S` is capped by `MaxSignatories`, with weight being proportional.
521		/// - One encode & hash, both of complexity `O(S)`.
522		/// - One event.
523		/// - I/O: 1 read `O(S)`, one remove.
524		/// - Storage: removes one item.
525		#[pallet::call_index(3)]
526		#[pallet::weight(T::WeightInfo::cancel_as_multi(other_signatories.len() as u32))]
527		pub fn cancel_as_multi(
528			origin: OriginFor<T>,
529			threshold: u16,
530			other_signatories: Vec<T::AccountId>,
531			timepoint: Timepoint<BlockNumberFor<T>>,
532			call_hash: [u8; 32],
533		) -> DispatchResult {
534			let who = ensure_signed(origin)?;
535			ensure!(threshold >= 2, Error::<T>::MinimumThreshold);
536			let max_sigs = T::MaxSignatories::get() as usize;
537			ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
538			ensure!(other_signatories.len() < max_sigs, Error::<T>::TooManySignatories);
539			let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
540
541			let id = Self::multi_account_id(&signatories, threshold);
542
543			let m = <Multisigs<T>>::get(&id, call_hash).ok_or(Error::<T>::NotFound)?;
544			ensure!(m.when == timepoint, Error::<T>::WrongTimepoint);
545			ensure!(m.depositor == who, Error::<T>::NotOwner);
546
547			let err_amount = T::Currency::unreserve(&m.depositor, m.deposit);
548			debug_assert!(err_amount.is_zero());
549			<Multisigs<T>>::remove(&id, &call_hash);
550
551			Self::deposit_event(Event::MultisigCancelled {
552				cancelling: who,
553				timepoint,
554				multisig: id,
555				call_hash,
556			});
557			Ok(())
558		}
559
560		/// Poke the deposit reserved for an existing multisig operation.
561		///
562		/// The dispatch origin for this call must be _Signed_ and must be the original depositor of
563		/// the multisig operation.
564		///
565		/// The transaction fee is waived if the deposit amount has changed.
566		///
567		/// - `threshold`: The total number of approvals needed for this multisig.
568		/// - `other_signatories`: The accounts (other than the sender) who are part of the
569		///   multisig.
570		/// - `call_hash`: The hash of the call this deposit is reserved for.
571		///
572		/// Emits `DepositPoked` if successful.
573		#[pallet::call_index(4)]
574		#[pallet::weight(T::WeightInfo::poke_deposit(other_signatories.len() as u32))]
575		pub fn poke_deposit(
576			origin: OriginFor<T>,
577			threshold: u16,
578			other_signatories: Vec<T::AccountId>,
579			call_hash: [u8; 32],
580		) -> DispatchResultWithPostInfo {
581			let who = ensure_signed(origin)?;
582			ensure!(threshold >= 2, Error::<T>::MinimumThreshold);
583			let max_sigs = T::MaxSignatories::get() as usize;
584			ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
585			ensure!(other_signatories.len() < max_sigs, Error::<T>::TooManySignatories);
586			// Get the multisig account ID
587			let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
588			let id = Self::multi_account_id(&signatories, threshold);
589
590			Multisigs::<T>::try_mutate(
591				&id,
592				call_hash,
593				|maybe_multisig| -> DispatchResultWithPostInfo {
594					let mut multisig = maybe_multisig.take().ok_or(Error::<T>::NotFound)?;
595					ensure!(multisig.depositor == who, Error::<T>::NotOwner);
596
597					// Calculate the new deposit
598					let new_deposit = Self::deposit(threshold);
599					let old_deposit = multisig.deposit;
600
601					if new_deposit == old_deposit {
602						*maybe_multisig = Some(multisig);
603						return Ok(Pays::Yes.into());
604					}
605
606					// Update the reserved amount
607					if new_deposit > old_deposit {
608						let extra = new_deposit.saturating_sub(old_deposit);
609						T::Currency::reserve(&who, extra)?;
610					} else {
611						let excess = old_deposit.saturating_sub(new_deposit);
612						let remaining_unreserved = T::Currency::unreserve(&who, excess);
613						if !remaining_unreserved.is_zero() {
614							defensive!(
615								"Failed to unreserve for full amount for multisig. (Call Hash, Requested, Actual): ",
616								(call_hash, excess, excess.saturating_sub(remaining_unreserved))
617							);
618						}
619					}
620
621					// Update storage
622					multisig.deposit = new_deposit;
623					*maybe_multisig = Some(multisig);
624
625					// Emit event
626					Self::deposit_event(Event::DepositPoked {
627						who: who.clone(),
628						call_hash,
629						old_deposit,
630						new_deposit,
631					});
632
633					Ok(Pays::No.into())
634				},
635			)
636		}
637	}
638}
639
640impl<T: Config> Pallet<T> {
641	/// Derive a multi-account ID from the sorted list of accounts and the threshold that are
642	/// required.
643	///
644	/// NOTE: `who` must be sorted. If it is not, then you'll get the wrong answer.
645	pub fn multi_account_id(who: &[T::AccountId], threshold: u16) -> T::AccountId {
646		let entropy = (b"modlpy/utilisuba", who, threshold).using_encoded(blake2_256);
647		Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
648			.expect("infinite length input; no invalid inputs for type; qed")
649	}
650
651	fn operate(
652		who: T::AccountId,
653		threshold: u16,
654		other_signatories: Vec<T::AccountId>,
655		maybe_timepoint: Option<Timepoint<BlockNumberFor<T>>>,
656		call_or_hash: CallOrHash<T>,
657		max_weight: Weight,
658	) -> DispatchResultWithPostInfo {
659		ensure!(threshold >= 2, Error::<T>::MinimumThreshold);
660		let max_sigs = T::MaxSignatories::get() as usize;
661		ensure!(!other_signatories.is_empty(), Error::<T>::TooFewSignatories);
662		let other_signatories_len = other_signatories.len();
663		ensure!(other_signatories_len < max_sigs, Error::<T>::TooManySignatories);
664		let signatories = Self::ensure_sorted_and_insert(other_signatories, who.clone())?;
665
666		let id = Self::multi_account_id(&signatories, threshold);
667
668		// Threshold > 1; this means it's a multi-step operation. We extract the `call_hash`.
669		let (call_hash, call_len, maybe_call) = match call_or_hash {
670			CallOrHash::Call(call) => {
671				let (call_hash, call_len) = call.using_encoded(|d| (blake2_256(d), d.len()));
672				(call_hash, call_len, Some(call))
673			},
674			CallOrHash::Hash(h) => (h, 0, None),
675		};
676
677		// Branch on whether the operation has already started or not.
678		if let Some(mut m) = <Multisigs<T>>::get(&id, call_hash) {
679			// Yes; ensure that the timepoint exists and agrees.
680			let timepoint = maybe_timepoint.ok_or(Error::<T>::NoTimepoint)?;
681			ensure!(m.when == timepoint, Error::<T>::WrongTimepoint);
682
683			// Ensure that either we have not yet signed or that it is at threshold.
684			let mut approvals = m.approvals.len() as u16;
685			// We only bother with the approval if we're below threshold.
686			let maybe_pos = m.approvals.binary_search(&who).err().filter(|_| approvals < threshold);
687			// Bump approvals if not yet voted and the vote is needed.
688			if maybe_pos.is_some() {
689				approvals += 1;
690			}
691
692			// We only bother fetching/decoding call if we know that we're ready to execute.
693			if let Some(call) = maybe_call.filter(|_| approvals >= threshold) {
694				// verify weight
695				ensure!(
696					call.get_dispatch_info().call_weight.all_lte(max_weight),
697					Error::<T>::MaxWeightTooLow
698				);
699
700				// Clean up storage before executing call to avoid an possibility of reentrancy
701				// attack.
702				<Multisigs<T>>::remove(&id, call_hash);
703				T::Currency::unreserve(&m.depositor, m.deposit);
704
705				let result = call.dispatch(RawOrigin::Signed(id.clone()).into());
706				Self::deposit_event(Event::MultisigExecuted {
707					approving: who,
708					timepoint,
709					multisig: id,
710					call_hash,
711					result: result.map(|_| ()).map_err(|e| e.error),
712				});
713				Ok(get_result_weight(result)
714					.map(|actual_weight| {
715						T::WeightInfo::as_multi_complete(
716							other_signatories_len as u32,
717							call_len as u32,
718						)
719						.saturating_add(actual_weight)
720					})
721					.into())
722			} else {
723				// We cannot dispatch the call now; either it isn't available, or it is, but we
724				// don't have threshold approvals even with our signature.
725
726				if let Some(pos) = maybe_pos {
727					// Record approval.
728					m.approvals
729						.try_insert(pos, who.clone())
730						.map_err(|_| Error::<T>::TooManySignatories)?;
731					<Multisigs<T>>::insert(&id, call_hash, m);
732					Self::deposit_event(Event::MultisigApproval {
733						approving: who,
734						timepoint,
735						multisig: id,
736						call_hash,
737					});
738				} else {
739					// If we already approved and didn't store the Call, then this was useless and
740					// we report an error.
741					Err(Error::<T>::AlreadyApproved)?
742				}
743
744				let final_weight =
745					T::WeightInfo::as_multi_approve(other_signatories_len as u32, call_len as u32);
746				// Call is not made, so the actual weight does not include call
747				Ok(Some(final_weight).into())
748			}
749		} else {
750			// Not yet started; there should be no timepoint given.
751			ensure!(maybe_timepoint.is_none(), Error::<T>::UnexpectedTimepoint);
752
753			// Just start the operation by recording it in storage.
754			let deposit = Self::deposit(threshold);
755
756			T::Currency::reserve(&who, deposit)?;
757
758			let initial_approvals =
759				vec![who.clone()].try_into().map_err(|_| Error::<T>::TooManySignatories)?;
760
761			<Multisigs<T>>::insert(
762				&id,
763				call_hash,
764				Multisig {
765					when: Self::timepoint(),
766					deposit,
767					depositor: who.clone(),
768					approvals: initial_approvals,
769				},
770			);
771			Self::deposit_event(Event::NewMultisig { approving: who, multisig: id, call_hash });
772
773			let final_weight =
774				T::WeightInfo::as_multi_create(other_signatories_len as u32, call_len as u32);
775			// Call is not made, so the actual weight does not include call
776			Ok(Some(final_weight).into())
777		}
778	}
779
780	/// The current `Timepoint`.
781	pub fn timepoint() -> Timepoint<BlockNumberFor<T>> {
782		Timepoint {
783			height: T::BlockNumberProvider::current_block_number(),
784			index: <frame_system::Pallet<T>>::extrinsic_index().unwrap_or_default(),
785		}
786	}
787
788	/// Check that signatories is sorted and doesn't contain sender, then insert sender.
789	fn ensure_sorted_and_insert(
790		other_signatories: Vec<T::AccountId>,
791		who: T::AccountId,
792	) -> Result<Vec<T::AccountId>, DispatchError> {
793		let mut signatories = other_signatories;
794		let mut maybe_last = None;
795		let mut index = 0;
796		for item in signatories.iter() {
797			if let Some(last) = maybe_last {
798				ensure!(last < item, Error::<T>::SignatoriesOutOfOrder);
799			}
800			if item <= &who {
801				ensure!(item != &who, Error::<T>::SenderInSignatories);
802				index += 1;
803			}
804			maybe_last = Some(item);
805		}
806		signatories.insert(index, who);
807		Ok(signatories)
808	}
809
810	/// Calculate the deposit for a multisig operation.
811	///
812	/// The deposit is calculated as `DepositBase + DepositFactor * threshold`.
813	pub fn deposit(threshold: u16) -> BalanceOf<T> {
814		T::DepositBase::get() + T::DepositFactor::get() * threshold.into()
815	}
816}
817
818/// Return the weight of a dispatch call result as an `Option`.
819///
820/// Will return the weight regardless of what the state of the result is.
821fn get_result_weight(result: DispatchResultWithPostInfo) -> Option<Weight> {
822	match result {
823		Ok(post_info) => post_info.actual_weight,
824		Err(err) => err.post_info.actual_weight,
825	}
826}