1use 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 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 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 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 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 pub fn set_owners(ctx: Context<Auth>, owners: Vec<Pubkey>) -> Result<()> {
173 execute_set_owners(&mut ctx.accounts.multisig, owners)
174 }
175
176 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 pub fn execute_transaction(ctx: Context<ExecuteTransaction>) -> Result<()> {
186 require!(ctx.accounts.multisig.owners.contains(ctx.accounts.executor.key), ErrorCode::InvalidExecutor);
187
188 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 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::<std::result::Result<Vec<_>, _>>()?;
216
217 Ok(())
218 }
219
220 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 #[account(
232 init,
233 space = multisig_data_len!(owners.len()),
234 payer = payer,
235 signer
236 )]
237 multisig: Box<Account<'info, Multisig>>,
238 #[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 #[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 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 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 #[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 #[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 #[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 pub multisig: Pubkey,
332 pub instructions: Vec<TransactionInstruction>,
334 pub signers: Vec<bool>,
336 pub owner_set_seqno: u32,
338 pub transaction_nonce: u64,
340}
341
342#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
343pub struct TransactionInstruction {
344 pub program_id: Pubkey,
346 pub accounts: Vec<TransactionAccount>,
348 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 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}