Skip to content

Commit 880fb05

Browse files
committed
feat: add multisig state and ixs where needed
1 parent 0f3cc2f commit 880fb05

File tree

8 files changed

+403
-1
lines changed

8 files changed

+403
-1
lines changed

Cargo.lock

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use core::{mem::MaybeUninit, slice};
2+
3+
use pinocchio::{
4+
account_info::AccountInfo,
5+
cpi::invoke_with_bounds,
6+
instruction::{AccountMeta, Instruction},
7+
program_error::ProgramError,
8+
pubkey::Pubkey,
9+
ProgramResult,
10+
};
11+
12+
/// Maximum number of multisignature signers.
13+
pub const MAX_MULTISIG_SIGNERS: usize = 11;
14+
15+
/// Initialize a new Multisig.
16+
///
17+
/// ### Accounts:
18+
/// 0. `[writable]` The multisig account to initialize.
19+
/// 1. `[]` Rent sysvar
20+
/// 2. ..`2+N`. `[]` The N signer accounts, where N is between 1 and 11.
21+
pub struct InitializeMultisig<'a, 'b>
22+
where
23+
'a: 'b,
24+
{
25+
/// Multisig Account.
26+
pub multisig: &'a AccountInfo,
27+
/// Rent sysvar Account.
28+
pub rent_sysvar: &'a AccountInfo,
29+
/// Signer Accounts
30+
pub signers: &'b [&'a AccountInfo],
31+
/// The number of signers (M) required to validate this multisignature
32+
/// account.
33+
pub m: u8,
34+
/// Token Program
35+
pub token_program: &'b Pubkey,
36+
}
37+
38+
impl InitializeMultisig<'_, '_> {
39+
#[inline(always)]
40+
pub fn invoke(&self) -> ProgramResult {
41+
let &Self {
42+
multisig,
43+
rent_sysvar,
44+
signers,
45+
m,
46+
token_program,
47+
} = self;
48+
49+
if signers.len() > MAX_MULTISIG_SIGNERS {
50+
return Err(ProgramError::InvalidArgument);
51+
}
52+
53+
let num_accounts = 2 + signers.len();
54+
55+
// Account metadata
56+
const UNINIT_META: MaybeUninit<AccountMeta> = MaybeUninit::<AccountMeta>::uninit();
57+
let mut acc_metas = [UNINIT_META; 2 + MAX_MULTISIG_SIGNERS];
58+
59+
unsafe {
60+
// SAFETY:
61+
// - `account_metas` is sized to 2 + MAX_MULTISIG_SIGNERS
62+
// - Index 0 and 1 are always present
63+
acc_metas
64+
.get_unchecked_mut(0)
65+
.write(AccountMeta::writable(multisig.key()));
66+
acc_metas
67+
.get_unchecked_mut(1)
68+
.write(AccountMeta::readonly(rent_sysvar.key()));
69+
}
70+
71+
for (account_meta, signer) in acc_metas[2..].iter_mut().zip(signers.iter()) {
72+
account_meta.write(AccountMeta::readonly(signer.key()));
73+
}
74+
75+
// Instruction data layout:
76+
// - [0]: instruction discriminator (1 byte, u8)
77+
// - [1]: m (1 byte, u8)
78+
let data = &[2, m];
79+
80+
let instruction = Instruction {
81+
program_id: token_program,
82+
accounts: unsafe { slice::from_raw_parts(acc_metas.as_ptr() as _, num_accounts) },
83+
data,
84+
};
85+
86+
// Account info array
87+
const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit();
88+
let mut acc_infos = [UNINIT_INFO; 2 + MAX_MULTISIG_SIGNERS];
89+
90+
unsafe {
91+
// SAFETY:
92+
// - `account_infos` is sized to 2 + MAX_MULTISIG_SIGNERS
93+
// - Index 0 and 1 are always present
94+
acc_infos.get_unchecked_mut(0).write(multisig);
95+
acc_infos.get_unchecked_mut(1).write(rent_sysvar);
96+
}
97+
98+
// Fill signer accounts
99+
for (account_info, signer) in acc_infos[2..].iter_mut().zip(signers.iter()) {
100+
account_info.write(signer);
101+
}
102+
103+
invoke_with_bounds::<{ 2 + MAX_MULTISIG_SIGNERS }>(&instruction, unsafe {
104+
slice::from_raw_parts(acc_infos.as_ptr() as _, num_accounts)
105+
})
106+
}
107+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use core::{mem::MaybeUninit, slice};
2+
3+
use pinocchio::{
4+
account_info::AccountInfo,
5+
cpi::invoke_with_bounds,
6+
instruction::{AccountMeta, Instruction},
7+
program_error::ProgramError,
8+
pubkey::Pubkey,
9+
ProgramResult,
10+
};
11+
12+
use crate::instructions::MAX_MULTISIG_SIGNERS;
13+
14+
/// Initialize a new Multisig.
15+
///
16+
/// ### Accounts:
17+
/// 0. `[writable]` The multisig account to initialize.
18+
/// 1. ..`1+N`. `[]` The N signer accounts, where N is between 1 and 11.
19+
pub struct InitializeMultisig2<'a, 'b>
20+
where
21+
'a: 'b,
22+
{
23+
/// Multisig Account.
24+
pub multisig: &'a AccountInfo,
25+
/// Signer Accounts
26+
pub signers: &'b [&'a AccountInfo],
27+
/// The number of signers (M) required to validate this multisignature
28+
/// account.
29+
pub m: u8,
30+
/// Token Program
31+
pub token_program: &'b Pubkey,
32+
}
33+
34+
impl InitializeMultisig2<'_, '_> {
35+
#[inline(always)]
36+
pub fn invoke(&self) -> ProgramResult {
37+
let &Self {
38+
multisig,
39+
signers,
40+
m,
41+
token_program,
42+
} = self;
43+
44+
if signers.len() > MAX_MULTISIG_SIGNERS {
45+
return Err(ProgramError::InvalidArgument);
46+
}
47+
48+
let num_accounts = 1 + signers.len();
49+
50+
// Account metadata
51+
const UNINIT_META: MaybeUninit<AccountMeta> = MaybeUninit::<AccountMeta>::uninit();
52+
let mut acc_metas = [UNINIT_META; 1 + MAX_MULTISIG_SIGNERS];
53+
54+
unsafe {
55+
// SAFETY:
56+
// - `account_metas` is sized to 1 + MAX_MULTISIG_SIGNERS
57+
// - Index 0 is always present
58+
acc_metas
59+
.get_unchecked_mut(0)
60+
.write(AccountMeta::writable(multisig.key()));
61+
}
62+
63+
for (account_meta, signer) in acc_metas[1..].iter_mut().zip(signers.iter()) {
64+
account_meta.write(AccountMeta::readonly(signer.key()));
65+
}
66+
67+
// Instruction data layout:
68+
// - [0]: instruction discriminator (1 byte, u8)
69+
// - [1]: m (1 byte, u8)
70+
let data = &[19, m];
71+
72+
let instruction = Instruction {
73+
program_id: token_program,
74+
accounts: unsafe { slice::from_raw_parts(acc_metas.as_ptr() as _, num_accounts) },
75+
data,
76+
};
77+
78+
// Account info array
79+
const UNINIT_INFO: MaybeUninit<&AccountInfo> = MaybeUninit::uninit();
80+
let mut acc_infos = [UNINIT_INFO; 1 + MAX_MULTISIG_SIGNERS];
81+
82+
unsafe {
83+
// SAFETY:
84+
// - `account_infos` is sized to 1 + MAX_MULTISIG_SIGNERS
85+
// - Index 0 is always present
86+
acc_infos.get_unchecked_mut(0).write(multisig);
87+
}
88+
89+
// Fill signer accounts
90+
for (account_info, signer) in acc_infos[1..].iter_mut().zip(signers.iter()) {
91+
account_info.write(signer);
92+
}
93+
94+
invoke_with_bounds::<{ 1 + MAX_MULTISIG_SIGNERS }>(&instruction, unsafe {
95+
slice::from_raw_parts(acc_infos.as_ptr() as _, num_accounts)
96+
})
97+
}
98+
}

programs/token-2022/src/instructions/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ mod initialize_account_2;
99
mod initialize_account_3;
1010
mod initialize_mint;
1111
mod initialize_mint_2;
12+
mod initialize_multisig;
13+
mod initialize_multisig_2;
1214
mod mint_to;
1315
mod mint_to_checked;
1416
mod revoke;
@@ -29,6 +31,8 @@ pub use initialize_account_2::*;
2931
pub use initialize_account_3::*;
3032
pub use initialize_mint::*;
3133
pub use initialize_mint_2::*;
34+
pub use initialize_multisig::*;
35+
pub use initialize_multisig_2::*;
3236
pub use mint_to::*;
3337
pub use mint_to_checked::*;
3438
pub use revoke::*;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod account_state;
22
mod mint;
3+
mod multisig;
34
mod token;
45

56
pub use account_state::*;
67
pub use mint::*;
8+
pub use multisig::*;
79
pub use token::*;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use pinocchio::{
2+
account_info::{AccountInfo, Ref},
3+
program_error::ProgramError,
4+
pubkey::Pubkey,
5+
};
6+
7+
use crate::{instructions::MAX_MULTISIG_SIGNERS, ID};
8+
9+
/// Multisignature data.
10+
#[repr(C)]
11+
pub struct Multisig {
12+
/// Number of signers required
13+
pub m: u8,
14+
/// Number of valid signers
15+
pub n: u8,
16+
/// Is `true` if this structure has been initialized
17+
pub is_initialized: u8,
18+
/// Signer public keys
19+
pub signers: [Pubkey; MAX_MULTISIG_SIGNERS],
20+
}
21+
22+
impl Multisig {
23+
/// The length of the `Multisig` account data.
24+
pub const LEN: usize = core::mem::size_of::<Multisig>();
25+
26+
/// Return a `Multisig` from the given account info.
27+
///
28+
/// This method performs owner and length validation on `AccountInfo`, safe borrowing
29+
/// the account data.
30+
#[inline]
31+
pub fn from_account_info(account_info: &AccountInfo) -> Result<Ref<Multisig>, ProgramError> {
32+
if account_info.data_len() != Self::LEN {
33+
return Err(ProgramError::InvalidAccountData);
34+
}
35+
if !account_info.is_owned_by(&ID) {
36+
return Err(ProgramError::InvalidAccountOwner);
37+
}
38+
Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
39+
Self::from_bytes_unchecked(data)
40+
}))
41+
}
42+
43+
/// Return a `Multisig` from the given account info.
44+
///
45+
/// This method performs owner and length validation on `AccountInfo`, but does not
46+
/// perform the borrow check.
47+
///
48+
/// # Safety
49+
///
50+
/// The caller must ensure that it is safe to borrow the account data (e.g., there are
51+
/// no mutable borrows of the account data).
52+
#[inline]
53+
pub unsafe fn from_account_info_unchecked(
54+
account_info: &AccountInfo,
55+
) -> Result<&Self, ProgramError> {
56+
if account_info.data_len() != Self::LEN {
57+
return Err(ProgramError::InvalidAccountData);
58+
}
59+
if account_info.owner() != &ID {
60+
return Err(ProgramError::InvalidAccountOwner);
61+
}
62+
Ok(Self::from_bytes_unchecked(
63+
account_info.borrow_data_unchecked(),
64+
))
65+
}
66+
67+
/// Return a `Multisig` from the given bytes.
68+
///
69+
/// # Safety
70+
///
71+
/// The caller must ensure that `bytes` contains a valid representation of `Multisig`, and
72+
/// it is properly aligned to be interpreted as an instance of `Multisig`.
73+
/// At the moment `Multisig` has an alignment of 1 byte.
74+
/// This method does not perform a length validation.
75+
#[inline(always)]
76+
pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
77+
&*(bytes.as_ptr() as *const Multisig)
78+
}
79+
80+
#[inline(always)]
81+
pub fn required_signers(&self) -> u8 {
82+
self.m
83+
}
84+
85+
pub fn valid_signers(&self) -> u8 {
86+
self.n
87+
}
88+
89+
#[inline(always)]
90+
pub fn signers(&self) -> &[Pubkey; MAX_MULTISIG_SIGNERS] {
91+
&self.signers
92+
}
93+
}

programs/token/src/state/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod account_state;
22
mod mint;
3+
mod multisig;
34
mod token;
45

56
pub use account_state::*;
67
pub use mint::*;
8+
pub use multisig::*;
79
pub use token::*;

0 commit comments

Comments
 (0)