multisig_lite/
lib.rs

1//! A native [SOL] multisig on-chain program.
2//!
3//! [sol]: https://solana.com/
4//!
5//! # Examples
6//!
7//! Here is how to create a new multisig account on-chain.
8//!
9//! Please refer to the [`multisig_lite`] module level
10//! documentation for the other instructions' example.
11//!
12//! ```no_run
13//! use std::rc::Rc;
14//!
15//! use solana_sdk::commitment_config::CommitmentConfig;
16//! use solana_sdk::pubkey::Pubkey;
17//! use solana_sdk::signature::read_keypair_file;
18//! use solana_sdk::signer::Signer;
19//! use solana_sdk::system_program;
20//!
21//! use anchor_client::{Client, Cluster};
22//!
23//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
24//! let url = Cluster::Devnet;
25//! let funder = Rc::new(read_keypair_file(
26//!     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
27//! )?);
28//! let opts = CommitmentConfig::processed();
29//! let pid = multisig_lite::id();
30//! let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
31//!
32//! // Gets the PDAs.
33//! let (state_pda, state_bump) =
34//!     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
35//! let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
36//!
37//! // Creates a multisig account.
38//! let sig = program
39//!     .request()
40//!     .accounts(multisig_lite::accounts::Create {
41//!         funder: funder.pubkey(),
42//!         state: state_pda,
43//!         fund: fund_pda,
44//!         system_program: system_program::id(),
45//!     })
46//!     .args(multisig_lite::instruction::Create {
47//!         m: 2, // m as in m/n.
48//!         signers: vec![funder.pubkey(), Pubkey::new_unique(), Pubkey::new_unique()],
49//!         q: 10, // transfer queue limit.
50//!         _state_bump: state_bump,
51//!         fund_bump,
52//!     })
53//!     .signer(funder.as_ref())
54//!     .send()?;
55//!
56//! println!("{sig}");
57//! # Ok(())
58//! # }
59//! ```
60
61use std::collections::{HashMap, HashSet};
62use std::ops::DerefMut;
63
64use anchor_lang::prelude::*;
65use anchor_lang::solana_program::program::{invoke, invoke_signed};
66use anchor_lang::solana_program::system_instruction;
67
68#[cfg(not(feature = "localnet"))]
69declare_id!("Ecycmji8eeggXrA3rD2cdEHpHDnP4btvVfcyTBS9cG9t");
70#[cfg(feature = "localnet")]
71declare_id!("AeAQKcvUbG6LmunEAiL2Vim5dN2uL5TNwJfgsGdyroQ3");
72
73/// A multisig program specific error code.
74#[error_code]
75pub enum Error {
76    /// Multisig [`State`] queue is empty.
77    #[msg("Multisig account is empty. Please create transactions")]
78    AccountEmpty,
79
80    /// Multisig [`State`] queue is full.
81    #[msg("Multisig transaction queue is full. Please approve those.")]
82    AccountFull,
83
84    /// Multisig [`State`] account is locked.
85    #[msg("Multisig account is locked. Please approve the transactions")]
86    AccountLocked,
87
88    /// Missing transfer recipient AccountInfo.
89    #[msg("Missing transfer recipient AccountInfo")]
90    MissingRecipientAccountInfo,
91
92    /// Multisig `Fund` account is not writable.
93    #[msg("Fund account is not writable")]
94    FundAccountNotWritable,
95
96    /// Multisig `Fund` account data is not empty")]
97    #[msg("Fund account data is not empty")]
98    FundAccountIsNotEmpty,
99
100    /// Invalid Multisig `Fund` PDA.
101    #[msg("Invalid fund account")]
102    InvalidFundAddress,
103
104    /// Invalid Multisig `Fund` account bump.
105    #[msg("Invalid fund bump seed")]
106    InvalidFundBumpSeed,
107
108    /// No signers.
109    #[msg("No signers provided")]
110    NoSigners,
111
112    /// Too many signers provided.
113    #[msg("Too many signers provided")]
114    TooManySigners,
115
116    /// The threshold, e.g. `m`, is too high.
117    #[msg("Threshold too high")]
118    ThresholdTooHigh,
119
120    /// Invalid signer given.
121    #[msg("Invalid signer")]
122    InvalidSigner,
123
124    /// Not enough `Fund` balance.
125    #[msg("There is not enough fund balance")]
126    NotEnoughFundBalance,
127}
128
129/// A multisig [`State`] PDA account data.
130///
131/// # Examples
132///
133/// Here is how to query the [`State`] PDA account on Devnet.
134///
135/// ```no_run
136/// use std::rc::Rc;
137///
138/// use solana_sdk::commitment_config::CommitmentConfig;
139/// use solana_sdk::pubkey::Pubkey;
140/// use solana_sdk::signature::read_keypair_file;
141/// use solana_sdk::signer::Signer;
142///
143/// use anchor_client::{Client, Cluster};
144///
145/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
146/// let url = Cluster::Devnet;
147/// let funder = Rc::new(read_keypair_file(
148///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
149/// )?);
150/// let opts = CommitmentConfig::processed();
151/// let pid = multisig_lite::id();
152/// let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
153///
154/// // Gets the PDAs.
155/// let (state_pda, _state_bump) =
156///     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
157///
158/// // Query the `multisig_lite::State` account.
159/// let state: multisig_lite::State = program.account(state_pda)?;
160///
161/// // Print out the state account.
162/// println!("{state:?}");
163/// # Ok(())
164/// # }
165/// ```
166#[account]
167#[derive(Debug)]
168pub struct State {
169    /// A threshold.
170    pub m: u8,
171
172    /// An array of signers Pubkey.
173    pub signers: Vec<Pubkey>,
174
175    /// A current signed state.
176    pub signed: Vec<bool>,
177
178    /// A fund PDA account, holding the native SOL.
179    pub fund: Pubkey,
180
181    /// A balance of the fund in lamports.
182    pub balance: u64,
183
184    /// A limit of the pending transactions.
185    pub q: u8,
186
187    /// An array of the pending transactions.
188    pub queue: Vec<Pubkey>,
189}
190
191impl State {
192    /// A minimum signers.
193    const MIN_SIGNERS: u8 = 1;
194
195    /// A maximum signers.
196    const MAX_SIGNERS: u8 = u8::MAX;
197
198    /// A maximum transaction queue.
199    const MIN_QUEUE: u8 = 1;
200
201    /// A maximum transaction queue.
202    const MAX_QUEUE: u8 = u8::MAX;
203
204    fn space(signers: &[Pubkey], q: u8) -> usize {
205        let n = Self::valid_n(signers.len() as u8) as usize;
206        let q = Self::valid_q(q) as usize;
207        8 + 1 + 4 + 32 * n + 4 + n + 32 + 8 + 1 + 4 + 32 * q
208    }
209
210    /// Returns the valid n, number of signers.
211    fn valid_n(n: u8) -> u8 {
212        n.clamp(Self::MIN_SIGNERS, Self::MAX_SIGNERS)
213    }
214
215    /// Returns the valid q, queue length.
216    fn valid_q(q: u8) -> u8 {
217        q.clamp(Self::MIN_QUEUE, Self::MAX_QUEUE)
218    }
219
220    /// Checks if the transfer queue is empty.
221    fn is_queue_empty(&self) -> bool {
222        self.queue.is_empty()
223    }
224
225    /// Check if the multisig queue is full.
226    fn is_queue_full(&self) -> bool {
227        self.queue.len() == self.q as usize
228    }
229
230    /// Checks if the account had been locked.
231    ///
232    /// The multisig account is locked once it's signed
233    /// by anyone.  It will be unlocked once the current
234    /// pending transactions were completed.
235    fn is_locked(&self) -> bool {
236        self.signed.iter().any(|signed| *signed)
237    }
238
239    /// Validates the multisig queue.
240    #[allow(clippy::result_large_err)]
241    fn validate_queue(&self) -> Result<()> {
242        require!(!self.is_queue_full(), Error::AccountFull);
243        Ok(())
244    }
245
246    /// Validates the multisig fund account.
247    #[allow(clippy::result_large_err)]
248    fn validate_fund<'info>(
249        state: &Account<'info, Self>,
250        fund: &UncheckedAccount<'info>,
251        bump: u8,
252    ) -> Result<()> {
253        if !fund.is_writable {
254            Err(Error::FundAccountNotWritable)?;
255        }
256        if !fund.data_is_empty() {
257            Err(Error::FundAccountIsNotEmpty)?;
258        }
259        let state_key = state.key();
260        let seed = [b"fund", state_key.as_ref(), &[bump]];
261        let pda = match Pubkey::create_program_address(&seed, &id()) {
262            Err(_e) => Err(Error::InvalidFundBumpSeed)?,
263            Ok(pda) => pda,
264        };
265        require_keys_eq!(pda, fund.key(), Error::InvalidFundAddress);
266
267        Ok(())
268    }
269
270    /// Creates a fund account.
271    #[allow(clippy::result_large_err)]
272    fn create_fund_account<'info>(
273        state: &Account<'info, Self>,
274        fund: &UncheckedAccount<'info>,
275        funder: &Signer<'info>,
276        bump: u8,
277    ) -> Result<()> {
278        let lamports = Rent::get()?.minimum_balance(0);
279        let ix = system_instruction::create_account(&funder.key(), &fund.key(), lamports, 0, &id());
280        let state_key = state.key();
281        let accounts = [funder.to_account_info(), fund.to_account_info()];
282        let seed = [b"fund", state_key.as_ref(), &[bump]];
283
284        // CPI.
285        invoke_signed(&ix, &accounts, &[&seed])?;
286
287        Ok(())
288    }
289
290    /// Withdraw fund.
291    #[allow(clippy::result_large_err)]
292    fn transfer_fund(
293        _state: &Account<'_, Self>,
294        from: &AccountInfo<'_>,
295        to: &AccountInfo<'_>,
296        lamports: u64,
297        _bump: u8,
298    ) -> Result<()> {
299        // The following code hit the runtime error, [`InstructionError::ExternalLamportSpend`].
300        // Instead, we'll transfer the lamports natively,
301        // as suggested by the [Solana cookbook].
302        //
303        // [`InstructionError::ExternalLamportSpend`]: https://docs.rs/solana-program/latest/solana_program/instruction/enum.InstructionError.html#variant.ExternalAccountLamportSpend
304        // [solana cookbook]: https://solanacookbook.com/references/programs.html#how-to-transfer-sol-in-a-program
305
306        /*
307        let ix = system_instruction::transfer(from.key, &to.key, lamports);
308        let accounts = [from, to];
309        let state_key = state.key();
310        let seed = [b"fund", state_key.as_ref(), &[bump]];
311        invoke_signed(
312            &ix,
313            &accounts,
314            &[&seed],
315        )?;
316        */
317        **from.try_borrow_mut_lamports()? -= lamports;
318        **to.try_borrow_mut_lamports()? += lamports;
319
320        Ok(())
321    }
322}
323
324/// A multisig [`Transfer`] account data.
325///
326/// # Examples
327///
328/// Here is how to query the [`Transfer`] PDA account on Devnet.
329///
330/// ```no_run
331/// use std::rc::Rc;
332///
333/// use solana_sdk::commitment_config::CommitmentConfig;
334/// use solana_sdk::pubkey::Pubkey;
335/// use solana_sdk::signature::read_keypair_file;
336/// use solana_sdk::signer::Signer;
337///
338/// use anchor_client::{Client, Cluster};
339///
340/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
341/// let url = Cluster::Devnet;
342/// let funder = Rc::new(read_keypair_file(
343///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
344/// )?);
345/// let opts = CommitmentConfig::processed();
346/// let pid = multisig_lite::id();
347/// let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
348///
349/// // Gets the PDAs.
350/// let (state_pda, _state_bump) =
351///     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
352///
353/// // Query the `multisig_lite::State` account to get the queued transfers.
354/// let state: multisig_lite::State = program.account(state_pda)?;
355///
356/// // Query the `multisig_lite::Transfer` accounts iteratively.
357/// for transfer in state.queue {
358///     let transfer: multisig_lite::Transfer = program.account(transfer)?;
359///     println!("{transfer:?}");
360/// }
361/// # Ok(())
362/// # }
363/// ```
364#[account]
365#[derive(Debug)]
366pub struct Transfer {
367    /// An creator of the transfer, one of the multisig
368    /// signers.
369    pub creator: Pubkey,
370
371    /// A recipient of the transfer.
372    pub recipient: Pubkey,
373
374    /// A lamports to transfer.
375    pub lamports: u64,
376}
377
378impl Transfer {
379    const SPACE: usize = 8 + 32 + 32 + 8;
380}
381
382/// Accounts for the [`multisig_lite::create`] instruction handler.
383///
384/// Please refer to the [`multisig_lite::create`] document for the example.
385#[derive(Accounts)]
386#[instruction(m: u8, signers: Vec<Pubkey>, q: u8, state_bump: u8, fund_bump: u8)]
387pub struct Create<'info> {
388    /// A funder of the multisig account.
389    #[account(mut)]
390    pub funder: Signer<'info>,
391
392    /// A multisig state PDA account.
393    #[account(
394        init,
395        payer = funder,
396        space = State::space(&signers, q),
397        seeds = [b"state", funder.key.as_ref()],
398        bump,
399    )]
400    pub state: Account<'info, State>,
401
402    /// A multisig fund account.
403    ///
404    /// CHECK: Checked by the [`multisig_lite::create`] instruction handler.
405    #[account(mut, seeds = [b"fund", state.key().as_ref()], bump = fund_bump)]
406    pub fund: UncheckedAccount<'info>,
407
408    /// The system program to create a multisig PDA accounts.
409    pub system_program: Program<'info, System>,
410}
411
412/// Accounts for the [`multisig_lite::fund`] instruction handler.
413///
414/// Please refer to the [`multisig_lite::fund`] document for the example.
415#[derive(Accounts)]
416#[instruction(lamports: u64, state_bump: u8, fund_bump: u8)]
417pub struct Fund<'info> {
418    /// A funder of the account.
419    ///
420    /// The funding is only allowed by the multisig account creator.
421    #[account(mut)]
422    pub funder: Signer<'info>,
423
424    /// A multisig state PDA account.
425    #[account(mut, seeds = [b"state", funder.key.as_ref()], bump = state_bump)]
426    pub state: Box<Account<'info, State>>,
427
428    /// A multisig fund account.
429    ///
430    /// CHECK: Checked by the [`multisig_lite::fund`] instruction handler.
431    #[account(mut, seeds = [b"fund", state.key().as_ref()], bump = fund_bump)]
432    pub fund: UncheckedAccount<'info>,
433
434    /// The system program to make the transfer of the fund.
435    pub system_program: Program<'info, System>,
436}
437
438/// Accounts for the [`multisig_lite::create_transfer`] instruction handler.
439///
440/// Please refer to the [`multisig_lite::create_transfer`] document for the example.
441#[derive(Accounts)]
442#[instruction(recipient: Pubkey, lamports: u64, fund_bump: u8)]
443pub struct CreateTransfer<'info> {
444    /// An initiator of the fund transfer.
445    ///
446    /// It should be one of the signers of the multisig account.
447    #[account(mut)]
448    pub creator: Signer<'info>,
449
450    /// A multisig state PDA account.
451    #[account(mut)]
452    pub state: Box<Account<'info, State>>,
453
454    /// A multisig fund PDA account.
455    ///
456    /// CHECK: Checked by the [`multisig_lite::create_transfer`] instruction handler.
457    #[account(mut, seeds = [b"fund", state.key().as_ref()], bump = fund_bump)]
458    pub fund: UncheckedAccount<'info>,
459
460    /// A transfer account to keep the queued transfer info.
461    #[account(init, payer = creator, space = Transfer::SPACE)]
462    pub transfer: Box<Account<'info, Transfer>>,
463
464    /// The system program to create a transfer account.
465    pub system_program: Program<'info, System>,
466}
467
468/// Accounts for the [`multisig_lite::approve`] instruction handler.
469///
470/// Once one of the signer approves, the account is locked
471/// for the new transfer unless:
472///
473/// 1) Meets the m number of signers approval.
474/// 2) Closes the account.
475///
476/// In case of the 1 above, the account will be unlocked
477/// and starts to take a new transfer again.
478///
479/// Please refer to the [`multisig_lite::approve`] document for the example.
480#[derive(Accounts)]
481#[instruction(fund_bump: u8)]
482pub struct Approve<'info> {
483    /// An approver of the current state of the multisg account.
484    #[account(mut)]
485    pub signer: Signer<'info>,
486
487    /// A multisig state PDA account.
488    #[account(mut)]
489    pub state: Box<Account<'info, State>>,
490
491    /// A multisig fund account.
492    ///
493    /// CHECK: Checked by the [`multisig_lite::approve`] instruction handler.
494    #[account(mut, seeds = [b"fund", state.key().as_ref()], bump = fund_bump)]
495    pub fund: UncheckedAccount<'info>,
496}
497
498/// Accounts for the [`multisig_lite::close`] instruction handler.
499///
500/// Please refer to the [`multisig_lite::close`] document for the example.
501#[derive(Accounts)]
502#[instruction(state_bump: u8, fund_bump: u8)]
503pub struct Close<'info> {
504    /// An original funder of the multisig account.
505    #[account(mut)]
506    pub funder: Signer<'info>,
507
508    /// A multisig state PDA account.
509    #[account(mut, close = funder, seeds = [b"state", funder.key.as_ref()], bump = state_bump)]
510    pub state: Box<Account<'info, State>>,
511
512    /// A multisig fund PDA account.
513    ///
514    /// CHECK: Checked by the [`multisig_lite::close`] instruction handler.
515    #[account(mut, seeds = [b"fund", state.key().as_ref()], bump = fund_bump)]
516    pub fund: UncheckedAccount<'info>,
517}
518
519/// Module representing the program instruction handlers.
520///
521/// # Examples
522///
523/// Here is how to approve pending transfers on Devnet.
524///
525/// Please take a look at the individual functions for other
526/// instruction opperations. e.g. [`multisig_lite::fund`] for
527/// how to fund the multisig account.
528///
529/// ```no_run
530/// use std::rc::Rc;
531///
532/// use solana_sdk::commitment_config::CommitmentConfig;
533/// use solana_sdk::instruction::AccountMeta;
534/// use solana_sdk::pubkey::Pubkey;
535/// use solana_sdk::signature::read_keypair_file;
536/// use solana_sdk::signer::Signer;
537///
538/// use anchor_client::{Client, Cluster};
539///
540/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
541/// let url = Cluster::Devnet;
542/// let signer = Rc::new(read_keypair_file(
543///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
544/// )?);
545/// let opts = CommitmentConfig::processed();
546/// let pid = multisig_lite::id();
547/// let program = Client::new_with_options(url, signer.clone(), opts).program(pid);
548///
549/// // Gets the PDAs.
550/// let (state_pda, state_bump) =
551///     Pubkey::find_program_address(&[b"state", signer.pubkey().as_ref()], &pid);
552/// let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
553///
554/// // Gets the pending transfers and the recipients account info.
555/// let mut remaining_accounts = vec![];
556/// let state: multisig_lite::State = program.account(state_pda)?;
557/// for transfer_pubkey in state.queue {
558///     let transfer: multisig_lite::Transfer = program.account(transfer_pubkey)?;
559///
560///     // Pushes the transfer account.
561///     remaining_accounts.push(AccountMeta {
562///         pubkey: transfer_pubkey,
563///         is_signer: false,
564///         is_writable: true,
565///     });
566///
567///     // Pushes the recipient account.
568///     remaining_accounts.push(AccountMeta {
569///         pubkey: transfer.recipient,
570///         is_signer: false,
571///         is_writable: true,
572///     });
573/// }
574///
575/// // Approve the multisig account.
576/// let sig = program
577///     .request()
578///     .accounts(multisig_lite::accounts::Approve {
579///         signer: signer.pubkey(),
580///         state: state_pda,
581///         fund: fund_pda,
582///     })
583///     .args(multisig_lite::instruction::Approve { fund_bump })
584///     .accounts(remaining_accounts)
585///     .signer(signer.as_ref())
586///     .send()?;
587///
588/// println!("{sig}");
589/// # Ok(())
590/// # }
591/// ```
592#[program]
593pub mod multisig_lite {
594    use super::*;
595
596    /// Creates a multisig account.
597    ///
598    /// It's restricted one multisig account to each funder Pubkey,
599    /// as it's used for the multisig PDA address generation.
600    ///
601    /// # Examples
602    ///
603    /// Here is how you create a multisig account on Devnet:
604    ///
605    /// ```no_run
606    /// use std::rc::Rc;
607    ///
608    /// use solana_sdk::commitment_config::CommitmentConfig;
609    /// use solana_sdk::pubkey::Pubkey;
610    /// use solana_sdk::signature::read_keypair_file;
611    /// use solana_sdk::signer::Signer;
612    /// use solana_sdk::system_program;
613    ///
614    /// use anchor_client::{Client, Cluster};
615    ///
616    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
617    /// let url = Cluster::Devnet;
618    /// let funder = Rc::new(read_keypair_file(
619    ///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
620    /// )?);
621    /// let opts = CommitmentConfig::processed();
622    /// let pid = multisig_lite::id();
623    /// let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
624    ///
625    /// // Gets the PDAs.
626    /// let (state_pda, state_bump) =
627    ///     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
628    /// let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
629    ///
630    /// // Creates a multisig account.
631    /// let sig = program
632    ///     .request()
633    ///     .accounts(multisig_lite::accounts::Create {
634    ///         funder: funder.pubkey(),
635    ///         state: state_pda,
636    ///         fund: fund_pda,
637    ///         system_program: system_program::id(),
638    ///     })
639    ///     .args(multisig_lite::instruction::Create {
640    ///         m: 2, // m as in m/n.
641    ///         signers: vec![funder.pubkey(), Pubkey::new_unique(), Pubkey::new_unique()],
642    ///         q: 10, // transfer queue limit.
643    ///         _state_bump: state_bump,
644    ///         fund_bump,
645    ///     })
646    ///     .signer(funder.as_ref())
647    ///     .send()?;
648    ///
649    /// println!("{sig}");
650    /// # Ok(())
651    /// # }
652    /// ```
653    #[allow(clippy::result_large_err)]
654    pub fn create(
655        ctx: Context<Create>,
656        m: u8,
657        signers: Vec<Pubkey>,
658        q: u8,
659        _state_bump: u8,
660        fund_bump: u8,
661    ) -> Result<()> {
662        let funder = &mut ctx.accounts.funder;
663        let state = &mut ctx.accounts.state;
664        let fund = &mut ctx.accounts.fund;
665
666        // At least one signer is required.
667        require_gte!(m, State::MIN_SIGNERS, Error::NoSigners);
668
669        // Validate the multisig fund account.
670        State::validate_fund(state, fund, fund_bump)?;
671
672        // Checks the uniqueness of signer's address.
673        let signers: HashSet<_> = signers.into_iter().collect();
674        require_gte!(signers.len(), State::MIN_SIGNERS as usize, Error::NoSigners);
675        require_gte!(
676            State::MAX_SIGNERS as usize,
677            signers.len(),
678            Error::TooManySigners
679        );
680
681        let threshold = m as usize;
682        require_gte!(signers.len(), threshold, Error::ThresholdTooHigh);
683
684        // Creates a fund account.
685        State::create_fund_account(state, fund, funder, fund_bump)?;
686
687        // Initializes the multisig state account.
688        state.m = m;
689        state.signers = signers.into_iter().collect();
690        state.signed = vec![false; state.signers.len()];
691        state.fund = fund.key();
692        state.balance = 0;
693        state.q = State::valid_q(q);
694
695        Ok(())
696    }
697
698    /// Funds lamports to the multisig fund account.
699    ///
700    /// The funding is only allowed by the multisig account funder.
701    ///
702    /// # Examples
703    ///
704    /// Here is how to fund to the multisig account on Devnet:
705    ///
706    /// ```no_run
707    /// use std::rc::Rc;
708    ///
709    /// use solana_sdk::commitment_config::CommitmentConfig;
710    /// use solana_sdk::pubkey::Pubkey;
711    /// use solana_sdk::native_token::LAMPORTS_PER_SOL;
712    /// use solana_sdk::signature::read_keypair_file;
713    /// use solana_sdk::signer::Signer;
714    /// use solana_sdk::system_program;
715    ///
716    /// use anchor_client::{Client, Cluster};
717    ///
718    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
719    /// let url = Cluster::Devnet;
720    /// let funder = Rc::new(read_keypair_file(
721    ///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
722    /// )?);
723    /// let opts = CommitmentConfig::processed();
724    /// let pid = multisig_lite::id();
725    /// let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
726    ///
727    /// // Gets the PDAs.
728    /// let (state_pda, state_bump) =
729    ///     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
730    /// let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
731    ///
732    /// // Funds the multisig account.
733    /// let sig = program
734    ///     .request()
735    ///     .accounts(multisig_lite::accounts::Fund {
736    ///         funder: funder.pubkey(),
737    ///         state: state_pda,
738    ///         fund: fund_pda,
739    ///         system_program: system_program::id(),
740    ///     })
741    ///     .args(multisig_lite::instruction::Fund {
742    ///         lamports: 1_000_000 * LAMPORTS_PER_SOL, // 1M SOL!? :)
743    ///         _state_bump: state_bump,
744    ///         fund_bump,
745    ///     })
746    ///     .signer(funder.as_ref())
747    ///     .send()?;
748    ///
749    /// println!("{sig}");
750    /// # Ok(())
751    /// # }
752    /// ```
753    #[allow(clippy::result_large_err)]
754    pub fn fund(ctx: Context<Fund>, lamports: u64, _state_bump: u8, fund_bump: u8) -> Result<()> {
755        let funder = &ctx.accounts.funder;
756        let state = &mut ctx.accounts.state;
757        let fund = &mut ctx.accounts.fund;
758
759        // Validate the multisig fund account.
760        State::validate_fund(state, fund, fund_bump)?;
761
762        // CPI to transfer fund to the multisig fund account.
763        let ix = system_instruction::transfer(&funder.key(), &fund.key(), lamports);
764        let accounts = [funder.to_account_info(), fund.to_account_info()];
765        invoke(&ix, &accounts)?;
766
767        // Update the balance.
768        state.balance += lamports;
769
770        Ok(())
771    }
772
773    /// Creates a queued transfer lamports to the recipient.
774    ///
775    /// Transfer account creation fee will be given back to the
776    /// creator of the transfer from the multisig fund.
777    ///
778    /// # Examples
779    ///
780    /// Here is how to create a pending transfer on Devnet:
781    ///
782    /// ```no_run
783    /// use std::rc::Rc;
784    ///
785    /// use solana_sdk::commitment_config::CommitmentConfig;
786    /// use solana_sdk::native_token::LAMPORTS_PER_SOL;
787    /// use solana_sdk::pubkey::Pubkey;
788    /// use solana_sdk::signature::read_keypair_file;
789    /// use solana_sdk::signer::{keypair::Keypair, Signer};
790    /// use solana_sdk::system_program;
791    ///
792    /// use anchor_client::{Client, Cluster};
793    ///
794    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
795    /// let url = Cluster::Devnet;
796    /// let funder = Rc::new(read_keypair_file(
797    ///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
798    /// )?);
799    /// let opts = CommitmentConfig::processed();
800    /// let pid = multisig_lite::id();
801    /// let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
802    ///
803    /// // Gets the PDAs.
804    /// let (state_pda, state_bump) =
805    ///     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
806    /// let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
807    ///
808    /// // Temporary transfer keypair.
809    /// //
810    /// // This is only required for the transaction signature and
811    /// // won't be required once the transaction is recorded on the
812    /// // ledger.
813    /// let transfer = Keypair::new();
814    ///
815    /// // Creates a pending transfer.
816    /// let sig = program
817    ///     .request()
818    ///     .accounts(multisig_lite::accounts::CreateTransfer {
819    ///         creator: funder.pubkey(),
820    ///         state: state_pda,
821    ///         fund: fund_pda,
822    ///         transfer: transfer.pubkey(),
823    ///         system_program: system_program::id(),
824    ///     })
825    ///     .args(multisig_lite::instruction::CreateTransfer {
826    ///         recipient: Pubkey::new_unique(),
827    ///         lamports: 1_000_000 * LAMPORTS_PER_SOL, // 1M SOL!? :)
828    ///         fund_bump,
829    ///     })
830    ///     .signer(funder.as_ref())
831    ///     .signer(&transfer)
832    ///     .send()?;
833    ///
834    /// println!("{sig}");
835    /// # Ok(())
836    /// # }
837    /// ```
838    #[allow(clippy::result_large_err)]
839    pub fn create_transfer(
840        ctx: Context<CreateTransfer>,
841        recipient: Pubkey,
842        lamports: u64,
843        fund_bump: u8,
844    ) -> Result<()> {
845        let creator = &ctx.accounts.creator;
846        let state = &mut ctx.accounts.state;
847        let fund = &mut ctx.accounts.fund;
848        let transfer = &mut ctx.accounts.transfer;
849
850        // Checks if the account is locked.
851        require!(!state.is_locked(), Error::AccountLocked);
852
853        // Validate the multisig fund account.
854        State::validate_fund(state, fund, fund_bump)?;
855
856        // Checks the creator.
857        let creator_key = creator.key();
858        let signers = &state.signers;
859        require!(signers.contains(&creator_key), Error::InvalidSigner);
860
861        // Check the current transfer queue.
862        state.validate_queue()?;
863
864        // Checks the multisig fund balance.
865        require_gte!(state.balance, lamports, Error::NotEnoughFundBalance);
866
867        // Giving back the rent fee to the creator.
868        let from = fund.to_account_info();
869        let to = creator.to_account_info();
870        let rent = transfer.to_account_info().lamports();
871        State::transfer_fund(state, &from, &to, rent, fund_bump)?;
872
873        // Initializes the transfer account, and
874        // queue it under multisig account for the
875        // future transfer execution.
876        transfer.creator = creator_key;
877        transfer.recipient = recipient;
878        transfer.lamports = lamports;
879        state.balance -= lamports;
880        state.queue.push(transfer.key());
881
882        Ok(())
883    }
884
885    /// Approves the transactions and executes the transfer
886    /// in case the `m` approvals are met.
887    ///
888    /// # Examples
889    ///
890    /// Here is how to approve pending transfers on Devnet:
891    ///
892    /// ```no_run
893    /// use std::rc::Rc;
894    ///
895    /// use solana_sdk::commitment_config::CommitmentConfig;
896    /// use solana_sdk::instruction::AccountMeta;
897    /// use solana_sdk::pubkey::Pubkey;
898    /// use solana_sdk::signature::read_keypair_file;
899    /// use solana_sdk::signer::Signer;
900    ///
901    /// use anchor_client::{Client, Cluster};
902    ///
903    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
904    /// let url = Cluster::Devnet;
905    /// let signer = Rc::new(read_keypair_file(
906    ///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
907    /// )?);
908    /// let opts = CommitmentConfig::processed();
909    /// let pid = multisig_lite::id();
910    /// let program = Client::new_with_options(url, signer.clone(), opts).program(pid);
911    ///
912    /// // Gets the PDAs.
913    /// let (state_pda, state_bump) =
914    ///     Pubkey::find_program_address(&[b"state", signer.pubkey().as_ref()], &pid);
915    /// let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
916    ///
917    /// // Gets the pending transfers and the recipients account info.
918    /// let mut remaining_accounts = vec![];
919    /// let state: multisig_lite::State = program.account(state_pda)?;
920    /// for transfer_pubkey in state.queue {
921    ///     let transfer: multisig_lite::Transfer = program.account(transfer_pubkey)?;
922    ///
923    ///     // Pushes the transfer account.
924    ///     remaining_accounts.push(AccountMeta {
925    ///         pubkey: transfer_pubkey,
926    ///         is_signer: false,
927    ///         is_writable: true,
928    ///     });
929    ///
930    ///     // Pushes the recipient account.
931    ///     remaining_accounts.push(AccountMeta {
932    ///         pubkey: transfer.recipient,
933    ///         is_signer: false,
934    ///         is_writable: true,
935    ///     });
936    /// }
937    ///
938    /// // Approve the multisig account.
939    /// let sig = program
940    ///     .request()
941    ///     .accounts(multisig_lite::accounts::Approve {
942    ///         signer: signer.pubkey(),
943    ///         state: state_pda,
944    ///         fund: fund_pda,
945    ///     })
946    ///     .args(multisig_lite::instruction::Approve { fund_bump })
947    ///     .accounts(remaining_accounts)
948    ///     .signer(signer.as_ref())
949    ///     .send()?;
950    ///
951    /// println!("{sig}");
952    /// # Ok(())
953    /// # }
954    /// ```
955    #[allow(clippy::result_large_err)]
956    pub fn approve(ctx: Context<Approve>, fund_bump: u8) -> Result<()> {
957        let signer = &ctx.accounts.signer;
958        let state = &mut ctx.accounts.state;
959        let fund = &mut ctx.accounts.fund;
960        let remaining_accounts: HashMap<_, _> = ctx
961            .remaining_accounts
962            .iter()
963            .map(|account| (account.key, account))
964            .collect();
965
966        // Validate the multisig fund account.
967        State::validate_fund(state, fund, fund_bump)?;
968
969        // Nothing to approve.
970        require!(!state.is_queue_empty(), Error::AccountEmpty);
971
972        // Checks the signer.
973        let signer_key = signer.key();
974        let signers = &state.signers;
975        let signer_index = match signers.iter().position(|pubkey| *pubkey == signer_key) {
976            None => return Err(Error::InvalidSigner.into()),
977            Some(signer_index) => signer_index,
978        };
979
980        // Due to the single transaction limitation, we allow the multiple approval
981        // so that we take care of the transfer in batch.
982        if !state.signed[signer_index] {
983            state.signed[signer_index] = true;
984        }
985
986        // Checks the threshold.
987        let signed = state.signed.iter().filter(|&signed| *signed).count() as u8;
988        if signed < state.m {
989            return Ok(());
990        }
991
992        // Finds out the executable transactions.
993        let mut executable = Vec::new();
994        let mut remaining = Vec::new();
995        for transfer_addr in &state.queue {
996            let transfer_info = match remaining_accounts.get(transfer_addr) {
997                Some(transfer) => transfer,
998                None => {
999                    remaining.push(*transfer_addr);
1000                    continue;
1001                }
1002            };
1003            let mut ref_data = transfer_info.try_borrow_mut_data()?;
1004            let mut transfer_data: &[u8] = ref_data.deref_mut();
1005            let tx = Transfer::try_deserialize(&mut transfer_data)?;
1006            let to = match remaining_accounts.get(&tx.recipient) {
1007                None => return Err(Error::MissingRecipientAccountInfo.into()),
1008                Some(recipient) => recipient,
1009            };
1010            executable.push((transfer_info, to, tx.lamports));
1011        }
1012
1013        // There is no executable account info.  Just returns the success.
1014        //
1015        // This is a case that the approver approved the multisig but didn't
1016        // provide the account info.
1017        if executable.is_empty() {
1018            return Ok(());
1019        }
1020
1021        // Executes the queued transfers.
1022        let fund = fund.to_account_info();
1023        for (transfer, to, lamports) in executable {
1024            // Fund to the recipient and closes the transfer account.
1025            State::transfer_fund(state, &fund, to, lamports, fund_bump)?;
1026            let lamports = transfer.lamports();
1027            State::transfer_fund(state, transfer, &fund, lamports, fund_bump)?;
1028        }
1029
1030        // Update the queue.
1031        state.queue = remaining;
1032
1033        // Reset the signed status once the queue is empty.
1034        if state.is_queue_empty() {
1035            state.signed.iter_mut().for_each(|signed| *signed = false);
1036        }
1037
1038        Ok(())
1039    }
1040
1041    /// Closes the multisig account.
1042    ///
1043    /// It cleans up all the remaining accounts and return those rents
1044    /// back to the funder, original creator of the multisig account.
1045    ///
1046    /// # Examples
1047    ///
1048    /// Here is how you close the multisig account on devnet:
1049    ///
1050    /// ```no_run
1051    /// use std::rc::Rc;
1052    ///
1053    /// use solana_sdk::commitment_config::CommitmentConfig;
1054    /// use solana_sdk::instruction::AccountMeta;
1055    /// use solana_sdk::pubkey::Pubkey;
1056    /// use solana_sdk::signature::read_keypair_file;
1057    /// use solana_sdk::signer::Signer;
1058    ///
1059    /// use anchor_client::{Client, Cluster};
1060    ///
1061    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1062    /// let url = Cluster::Devnet;
1063    /// let funder = Rc::new(read_keypair_file(
1064    ///     shellexpand::tilde("~/.config/solana/id.json").as_ref(),
1065    /// )?);
1066    /// let opts = CommitmentConfig::processed();
1067    /// let pid = multisig_lite::id();
1068    /// let program = Client::new_with_options(url, funder.clone(), opts).program(pid);
1069    ///
1070    /// // Gets the PDAs.
1071    /// let (state_pda, state_bump) =
1072    ///     Pubkey::find_program_address(&[b"state", funder.pubkey().as_ref()], &pid);
1073    /// let (fund_pda, fund_bump) = Pubkey::find_program_address(&[b"fund", state_pda.as_ref()], &pid);
1074    ///
1075    /// // Gets the remaining transfers to collects the rents.
1076    /// let state: multisig_lite::State = program.account(state_pda)?;
1077    /// let remaining_accounts: Vec<_> = state
1078    ///     .queue
1079    ///     .into_iter()
1080    ///     .map(|pubkey| AccountMeta {
1081    ///         pubkey,
1082    ///         is_signer: false,
1083    ///         is_writable: true,
1084    ///     })
1085    ///     .collect();
1086    ///
1087    /// // close the multisig account.
1088    /// let sig = program
1089    ///     .request()
1090    ///     .accounts(multisig_lite::accounts::Close {
1091    ///         funder: funder.pubkey(),
1092    ///         state: state_pda,
1093    ///         fund: fund_pda,
1094    ///     })
1095    ///     .args(multisig_lite::instruction::Close {
1096    ///         _state_bump: state_bump,
1097    ///         fund_bump,
1098    ///     })
1099    ///     .signer(funder.as_ref())
1100    ///     .send()?;
1101    ///
1102    /// println!("{sig}");
1103    /// # Ok(())
1104    /// # }
1105    /// ```
1106    #[allow(clippy::result_large_err)]
1107    pub fn close(ctx: Context<Close>, _state_bump: u8, fund_bump: u8) -> Result<()> {
1108        let funder = &mut ctx.accounts.funder;
1109        let state = &mut ctx.accounts.state;
1110        let fund = &mut ctx.accounts.fund;
1111        let remaining_accounts: HashMap<_, _> = ctx
1112            .remaining_accounts
1113            .iter()
1114            .map(|account| (account.key, account))
1115            .collect();
1116
1117        // Validate the multisig fund account.
1118        State::validate_fund(state, fund, fund_bump)?;
1119
1120        // Closes the transfer accounts by transfering the
1121        // rent fee back to the fund account.
1122        let to = fund.to_account_info();
1123        for transfer_addr in &state.queue {
1124            let from = match remaining_accounts.get(transfer_addr) {
1125                Some(transfer) => transfer,
1126                None => continue,
1127            };
1128            let lamports = from.lamports();
1129            State::transfer_fund(state, from, &to, lamports, fund_bump)?;
1130        }
1131
1132        // Closes the multisig fund account by transfering all the lamports
1133        // back to the funder.
1134        let from = fund.to_account_info();
1135        let to = funder.to_account_info();
1136        let lamports = fund.lamports();
1137        State::transfer_fund(state, &from, &to, lamports, fund_bump)?;
1138
1139        Ok(())
1140    }
1141}