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}