lmax_multisig/
lib.rs

1//! An example of a multisig to execute arbitrary Solana transactions.
2//!
3//! This program can be used to allow a multisig to govern anything a regular
4//! Pubkey can govern. One can use the multisig as a BPF program upgrade
5//! authority, a mint authority, etc.
6//!
7//! To use, one must first create a `Multisig` account, specifying two important
8//! parameters:
9//!
10//! 1. Owners - the set of addresses that sign transactions for the multisig.
11//! 2. Threshold - the number of signers required to execute a transaction.
12//!
13//! Once the `Multisig` account is created, one can create a `Transaction`
14//! account, specifying the parameters for a normal solana transaction.
15//!
16//! To sign, owners should invoke the `approve` instruction, and finally,
17//! the `execute_transaction`, once enough (i.e. `threshold`) of the owners have
18//! signed.
19
20use anchor_lang::prelude::*;
21use anchor_lang::solana_program;
22use anchor_lang::solana_program::instruction::Instruction;
23use std::convert::Into;
24
25
26const ANCHOR_ACCT_DESCRIM_SIZE: usize = 8;
27const VEC_SIZE: usize = 4;
28const PUBKEY_SIZE: usize = 32;
29
30#[cfg(not(feature = "no-entrypoint"))]
31use solana_security_txt::security_txt;
32
33#[cfg(not(feature = "no-entrypoint"))]
34security_txt! {
35    name: "LMAX Multisig",
36    project_url: "https://www.lmax.com",
37    contacts: "email:infosec@lmax.com",
38    policy: "https://lmax.com/.well-known/security.txt",
39
40    preferred_languages: "en",
41    auditors: "https://www.certik.com"
42}
43
44#[macro_export]
45macro_rules! vec_len {
46    ( $elem_size:expr, $elem_count:expr ) => {
47        {
48            ($elem_size * $elem_count + VEC_SIZE)
49        }
50    };
51}
52
53#[macro_export]
54macro_rules! instructions_len {
55    ( $instructions: expr) => {
56        {
57            ($instructions.iter().map(|ix| {
58                PUBKEY_SIZE + vec_len!(PUBKEY_SIZE + 1 + 1, ix.accounts.len()) + vec_len!(1, ix.data.len())
59            })
60            .sum::<usize>() + VEC_SIZE)
61        }
62    };
63}
64
65#[macro_export]
66macro_rules! multisig_data_len {
67    ( $owner_count:expr ) => {
68        {
69            (ANCHOR_ACCT_DESCRIM_SIZE + vec_len!(PUBKEY_SIZE, $owner_count) + 8 + 1 + 4)
70        }
71    };
72}
73
74#[macro_export]
75macro_rules! transaction_data_len {
76    ( $instructions:expr, $owner_count:expr ) => {
77        {
78            (ANCHOR_ACCT_DESCRIM_SIZE + PUBKEY_SIZE + instructions_len!($instructions) + vec_len!(1, $owner_count) + 4)
79        }
80    };
81}
82
83
84
85declare_id!("LMAXm1DhfBg1YMvi79gXdPfsJpYuJb9urGkGNa12hvJ");
86
87#[program]
88pub mod lmax_multisig {
89    use super::*;
90
91    // Initializes a new multisig account with a set of owners and a threshold.
92    pub fn create_multisig(
93        ctx: Context<CreateMultisig>,
94        owners: Vec<Pubkey>,
95        threshold: u64,
96        nonce: u8,
97    ) -> Result<()> {
98        assert_unique_owners(&owners)?;
99        require!(
100            threshold > 0 && threshold <= owners.len() as u64,
101            ErrorCode::InvalidThreshold
102        );
103        require!(!owners.is_empty(), ErrorCode::NotEnoughOwners);
104
105        let multisig = &mut ctx.accounts.multisig;
106        multisig.owners = owners;
107        multisig.threshold = threshold;
108        multisig.nonce = nonce;
109        multisig.owner_set_seqno = 0;
110        Ok(())
111    }
112
113    // Creates a new transaction account, automatically signed by the creator,
114    // which must be one of the owners of the multisig.
115    pub fn create_transaction(
116        ctx: Context<CreateTransaction>,
117        instructions: Vec<TransactionInstruction>,
118        transaction_nonce: u64,
119    ) -> Result<()> {
120        require!(!instructions.is_empty(), ErrorCode::MissingInstructions);
121
122        let owner_index = ctx
123            .accounts
124            .multisig
125            .owners
126            .iter()
127            .position(|a| a == ctx.accounts.proposer.key)
128            .ok_or(ErrorCode::InvalidOwner)?;
129
130        let mut signers = Vec::new();
131        signers.resize(ctx.accounts.multisig.owners.len(), false);
132        signers[owner_index] = true;
133
134        let tx = &mut ctx.accounts.transaction;
135        tx.instructions = instructions;
136        tx.signers = signers;
137        tx.multisig = ctx.accounts.multisig.key();
138        tx.owner_set_seqno = ctx.accounts.multisig.owner_set_seqno;
139        tx.transaction_nonce = transaction_nonce;
140
141        Ok(())
142    }
143
144    // Approves a transaction on behalf of an owner of the multisig.
145    pub fn approve(ctx: Context<Approve>) -> Result<()> {
146        let owner_index = ctx
147            .accounts
148            .multisig
149            .owners
150            .iter()
151            .position(|a| a == ctx.accounts.owner.key)
152            .ok_or(ErrorCode::InvalidOwner)?;
153
154        ctx.accounts.transaction.signers[owner_index] = true;
155
156        Ok(())
157    }
158
159    // Set owners and threshold at once.
160    pub fn set_owners_and_change_threshold<'info>(
161        ctx: Context<'_, '_, '_, 'info, Auth<'info>>,
162        owners: Vec<Pubkey>,
163        threshold: u64,
164    ) -> Result<()> {
165        let multisig = &mut ctx.accounts.multisig;
166        execute_set_owners(multisig, owners)?;
167        execute_change_threshold(multisig, threshold)
168    }
169
170    // Sets the owners field on the multisig. The only way this can be invoked
171    // is via a recursive call from execute_transaction -> set_owners.
172    pub fn set_owners(ctx: Context<Auth>, owners: Vec<Pubkey>) -> Result<()> {
173        execute_set_owners(&mut ctx.accounts.multisig, owners)
174    }
175
176    // Changes the execution threshold of the multisig. The only way this can be
177    // invoked is via a recursive call from execute_transaction ->
178    // change_threshold.
179    pub fn change_threshold(ctx: Context<Auth>, threshold: u64) -> Result<()> {
180        let multisig = &mut ctx.accounts.multisig;
181        execute_change_threshold(multisig, threshold)
182    }
183
184    // Executes the given transaction if threshold owners have signed it.
185    pub fn execute_transaction(ctx: Context<ExecuteTransaction>) -> Result<()> {
186        require!(ctx.accounts.multisig.owners.contains(ctx.accounts.executor.key), ErrorCode::InvalidExecutor);
187
188        // Do we have enough signers?
189        let sig_count = ctx.accounts.transaction.signers.iter()
190            .filter(|&did_sign| *did_sign)
191            .count() as u64;
192        require!(sig_count >= ctx.accounts.multisig.threshold, ErrorCode::NotEnoughSigners);
193
194        let multisig_key = ctx.accounts.multisig.key();
195        let seeds = &[multisig_key.as_ref(), &[ctx.accounts.multisig.nonce]];
196        let signer = &[&seeds[..]];
197        let accounts = ctx.remaining_accounts;
198
199        // Execute the transaction signed by the multisig.
200        ctx.accounts.transaction.instructions.iter()
201            .map(|ix| {
202                let mut ix: Instruction = ix.into();
203                ix.accounts = ix.accounts.iter()
204                    .map(|acc| {
205                        let mut acc = acc.clone();
206                        if &acc.pubkey == ctx.accounts.multisig_signer.key {
207                            acc.is_signer = true;
208                        }
209                        acc
210                    })
211                    .collect();
212                solana_program::program::invoke_signed(&ix, accounts, signer)
213            })
214            // Collect will process Result objects from the invoke_signed until it finds an error, when it will return that error
215            .collect::<std::result::Result<Vec<_>, _>>()?;
216
217        Ok(())
218    }
219
220    // Cancel the given transaction regardless of signatures.
221    pub fn cancel_transaction(ctx: Context<CancelTransaction>) -> Result<()> {
222        require!(ctx.accounts.multisig.owners.contains(ctx.accounts.executor.key), ErrorCode::InvalidExecutor);
223        Ok(())
224    }
225}
226
227#[derive(Accounts)]
228#[instruction(owners: Vec<Pubkey>, threshold: u64, nonce: u8)]
229pub struct CreateMultisig<'info> {
230    // see https://book.anchor-lang.com/anchor_references/space.html
231    #[account(
232        init,
233        space = multisig_data_len!(owners.len()),
234        payer = payer,
235        signer
236    )]
237    multisig: Box<Account<'info, Multisig>>,
238    /// CHECK: multisig_signer is a PDA program signer. Data is never read or written to
239    #[account(
240        seeds = [multisig.key().as_ref()],
241        bump = nonce,
242    )]
243    multisig_signer: UncheckedAccount<'info>,
244    #[account(mut)]
245    payer: Signer<'info>,
246    system_program: Program<'info, System>,
247}
248
249#[derive(Accounts)]
250#[instruction(instructions: Vec<TransactionInstruction>, transaction_nonce: u64)]
251pub struct CreateTransaction<'info> {
252    multisig: Box<Account<'info, Multisig>>,
253    // see https://book.anchor-lang.com/anchor_references/space.html
254    #[account(
255        init,
256        space = transaction_data_len!(instructions, multisig.owners.len()) + 8,
257        payer = payer,
258        seeds = [b"transaction_nonce", transaction_nonce.to_le_bytes().as_ref()],
259        bump,
260    )]
261    transaction: Box<Account<'info, Transaction>>,
262    // One of the owners. Checked in the handler.
263    proposer: Signer<'info>,
264    #[account(mut)]
265    payer: Signer<'info>,
266    system_program: Program<'info, System>,
267}
268
269#[derive(Accounts)]
270pub struct Approve<'info> {
271    #[account(constraint = multisig.owner_set_seqno == transaction.owner_set_seqno)]
272    multisig: Box<Account<'info, Multisig>>,
273    #[account(mut, has_one = multisig)]
274    transaction: Box<Account<'info, Transaction>>,
275    // One of the multisig owners. Checked in the handler.
276    owner: Signer<'info>,
277}
278
279#[derive(Accounts)]
280pub struct Auth<'info> {
281    #[account(mut)]
282    multisig: Box<Account<'info, Multisig>>,
283    #[account(
284        seeds = [multisig.key().as_ref()],
285        bump = multisig.nonce,
286    )]
287    multisig_signer: Signer<'info>,
288}
289
290#[derive(Accounts)]
291pub struct ExecuteTransaction<'info> {
292    #[account(constraint = multisig.owner_set_seqno == transaction.owner_set_seqno)]
293    multisig: Box<Account<'info, Multisig>>,
294    /// CHECK: multisig_signer is a PDA program signer. Data is never read or written to
295    #[account(
296        seeds = [multisig.key().as_ref()],
297        bump = multisig.nonce,
298    )]
299    multisig_signer: UncheckedAccount<'info>,
300    #[account(mut, has_one = multisig, close = refundee)]
301    transaction: Box<Account<'info, Transaction>>,
302    /// CHECK: success can be any address where rent exempt funds are sent
303    #[account(mut)]
304    refundee:  AccountInfo<'info>,
305    executor: Signer<'info>,
306}
307
308#[derive(Accounts)]
309pub struct CancelTransaction<'info> {
310    #[account(constraint = multisig.owner_set_seqno >= transaction.owner_set_seqno)]
311    multisig: Box<Account<'info, Multisig>>,
312    #[account(mut, has_one = multisig, close = refundee)]
313    transaction: Box<Account<'info, Transaction>>,
314    /// CHECK: success can be any address where rent exempt funds are sent
315    #[account(mut)]
316    refundee:  AccountInfo<'info>,
317    executor: Signer<'info>,
318}
319
320#[account]
321pub struct Multisig {
322    pub owners: Vec<Pubkey>,
323    pub threshold: u64,
324    pub nonce: u8,
325    pub owner_set_seqno: u32,
326}
327
328#[account]
329pub struct Transaction {
330    // The multisig account this transaction belongs to.
331    pub multisig: Pubkey,
332    // The instructions to be executed by this transaction
333    pub instructions: Vec<TransactionInstruction>,
334    // signers[index] is true iff multisig.owners[index] signed the transaction.
335    pub signers: Vec<bool>,
336    // Owner set sequence number.
337    pub owner_set_seqno: u32,
338    // transaction nonce for tracking and uniqueness.
339    pub transaction_nonce: u64,
340}
341
342#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
343pub struct TransactionInstruction {
344    /// Pubkey of the program that executes this instruction.
345    pub program_id: Pubkey,
346    /// Metadata describing accounts that should be passed to the program.
347    pub accounts: Vec<TransactionAccount>,
348    /// Opaque data passed to the program for its own interpretation.
349    pub data: Vec<u8>,
350}
351
352impl From<&TransactionInstruction> for Instruction {
353    fn from(ix: &TransactionInstruction) -> Instruction {
354        Instruction {
355            program_id: ix.program_id,
356            accounts: ix.accounts.iter().map(Into::into).collect(),
357            data: ix.data.clone(),
358        }
359    }
360}
361
362#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
363pub struct TransactionAccount {
364    pub pubkey: Pubkey,
365    pub is_signer: bool,
366    pub is_writable: bool,
367}
368
369impl From<&TransactionAccount> for AccountMeta {
370    fn from(account: &TransactionAccount) -> AccountMeta {
371        match account.is_writable {
372            false => AccountMeta::new_readonly(account.pubkey, account.is_signer),
373            true => AccountMeta::new(account.pubkey, account.is_signer),
374        }
375    }
376}
377
378fn assert_unique_owners(owners: &[Pubkey]) -> Result<()> {
379    for (i, owner) in owners.iter().enumerate() {
380        require!(
381            !owners.iter().skip(i + 1).any(|item| item == owner),
382            ErrorCode::UniqueOwners
383        )
384    }
385    Ok(())
386}
387
388fn execute_set_owners(multisig: &mut Account<Multisig>, owners: Vec<Pubkey>) -> Result<()> {
389    assert_unique_owners(&owners)?;
390    require!(!owners.is_empty(), ErrorCode::NotEnoughOwners);
391    // Increasing the number of owners requires reallocation of space in the data account.
392    // This requires a signer to pay the fees for more space, but the instruction will be executed by the multisig.
393    require!(multisig_data_len!(owners.len()) <= multisig.to_account_info().data.borrow().len(), ErrorCode::TooManyOwners);
394
395    if (owners.len() as u64) < multisig.threshold {
396        multisig.threshold = owners.len() as u64;
397    }
398
399    multisig.owners = owners;
400    multisig.owner_set_seqno += 1;
401
402    Ok(())
403}
404
405fn execute_change_threshold(multisig: &mut Multisig, threshold: u64) -> Result<()> {
406    require!(threshold > 0 && threshold <= multisig.owners.len() as u64, ErrorCode::InvalidThreshold);
407    multisig.threshold = threshold;
408    Ok(())
409}
410
411#[error_code]
412pub enum ErrorCode {
413    #[msg("The given owner is not part of this multisig.")]
414    InvalidOwner,
415    #[msg("Owners length must be non zero.")]
416    NotEnoughOwners,
417    #[msg("The number of owners cannot be increased.")]
418    TooManyOwners,
419    #[msg("Not enough owners signed this transaction.")]
420    NotEnoughSigners,
421    #[msg("Cannot delete a transaction that has been signed by an owner.")]
422    TransactionAlreadySigned,
423    #[msg("Overflow when adding.")]
424    Overflow,
425    #[msg("Cannot delete a transaction the owner did not create.")]
426    UnableToDelete,
427    #[msg("The given transaction has already been executed.")]
428    AlreadyExecuted,
429    #[msg("Threshold must be less than or equal to the number of owners and greater than zero.")]
430    InvalidThreshold,
431    #[msg("Owners must be unique.")]
432    UniqueOwners,
433    #[msg("Executor is not a multisig owner.")]
434    InvalidExecutor,
435    #[msg("Failed to close transaction account and refund rent-exemption SOL.")]
436    AccountCloseFailed,
437    #[msg("The number of instructions must be greater than zero.")]
438    MissingInstructions,
439}