#[macro_export]
macro_rules! accounts_struct_meta {
($pubkey:expr, is_signer: $is_signer:expr, is_writable: true, ) => {
AccountMeta::new($pubkey, $is_signer)
};
($pubkey:expr, is_signer: $is_signer:expr, is_writable: false, ) => {
AccountMeta::new_readonly($pubkey, $is_signer)
};
}
#[macro_export]
macro_rules! accounts_struct {
{
$NameAccountMeta:ident, $NameAccountInfo:ident {
$(
pub $var_account:ident {
is_signer: $is_signer:expr,
is_writable: $is_writable:tt,
}
),*
$(
,
$(
const $const_account:ident = $const_value:expr
),*
)?
$(
,
pub ... $multi_account:ident {
// For now, only allow non-signer writable accounts in
// the variadic parts, so we don't have to implement
// verification checks. We can add that when we need it.
is_signer: false,
is_writable: true,
}
)?
,
}
} => {
#[derive(Debug)]
pub struct $NameAccountMeta {
$(
pub $var_account: Pubkey
),*
$(
,
pub $multi_account: Vec<Pubkey>,
)?
}
#[derive(Debug)]
pub struct $NameAccountInfo<'a, 'b> {
$(
pub $var_account: &'a AccountInfo<'b>
),*
$(
,
$(
pub $const_account: &'a AccountInfo<'b>
),*
)?
$(
,
pub $multi_account: &'a [AccountInfo<'b>],
)?
}
impl $NameAccountMeta {
#[must_use]
pub fn to_vec(&self) -> Vec<AccountMeta> {
#[allow(unused_mut)]
let mut result = vec![
$(
accounts_struct_meta!(
self.$var_account,
is_signer: $is_signer,
is_writable: $is_writable,
)
),*
$(
,
$(
AccountMeta::new_readonly(
$const_value,
false
)
),*
)?
];
$(
for pubkey in &self.$multi_account {
result.push(accounts_struct_meta!(
*pubkey,
is_signer: false,
is_writable: true,
));
}
)?
result
}
#[allow(dead_code)]
pub fn try_from_slice(accounts: &[AccountMeta]) -> Result<$NameAccountMeta, ProgramError> {
let mut accounts_iter = accounts.iter();
$(
let account = accounts_iter.next().ok_or(ProgramError::NotEnoughAccountKeys)?;
if (($is_signer && !account.is_signer)
|| ($is_writable && !account.is_writable)) {
return Err(LidoError::InvalidAccountInfo.into());
}
let $var_account = account.pubkey;
)*
$(
$(
let $const_account = accounts_iter.next().ok_or(ProgramError::NotEnoughAccountKeys)?;
let _ = $const_account;
)*
)?
$(
let mut $multi_account = Vec::new();
while let Some(meta) = accounts_iter.next() {
if !meta.is_writable {
return Err(LidoError::InvalidAccountInfo.into());
}
$multi_account.push(meta.pubkey);
}
)?
if accounts_iter.next().is_some() {
return Err(LidoError::TooManyAccountKeys.into());
}
let result = $NameAccountMeta {
$( $var_account ),*
$( , $multi_account )?
};
Ok(result)
}
}
impl<'a, 'b> $NameAccountInfo<'a, 'b> {
pub fn try_from_slice(accounts: &'a [AccountInfo<'b>]) -> Result<$NameAccountInfo<'a, 'b>, ProgramError> {
use solana_program::msg;
let mut accounts_iter = accounts.iter();
$(
let $var_account = match accounts_iter.next() {
Some(account) => account,
None => {
msg!(
"Not enough accounts provided. Expected {}.",
stringify!($var_account),
);
return Err(ProgramError::NotEnoughAccountKeys);
}
};
if $is_signer && !$var_account.is_signer {
msg!(
"Expected {} ({}) to be a signer, but it is not.",
stringify!($var_account),
$var_account.key,
);
return Err(LidoError::InvalidAccountInfo.into());
}
if $is_writable && !$var_account.is_writable {
msg!(
"Expected {} ({}) to be writable, but it is not.",
stringify!($var_account),
$var_account.key,
);
return Err(LidoError::InvalidAccountInfo.into());
}
)*
$(
$(
let $const_account = match accounts_iter.next() {
Some(account) => account,
None => {
msg!(
"Not enough accounts provided. Expected {}.",
stringify!($const_account),
);
return Err(ProgramError::NotEnoughAccountKeys);
}
};
if $const_account.is_signer || $const_account.is_writable {
msg!(
"Account {} ({}) is unexpectedly writable or signer.",
stringify!($const_account),
$const_account.key,
);
return Err(LidoError::InvalidAccountInfo.into());
}
if *$const_account.key != $const_value {
msg!(
"Account {} was expected to be set to {}, but found {} instead.",
stringify!($const_account),
$const_value,
$const_account.key,
);
return Err(LidoError::InvalidAccountInfo.into());
}
)*
)?
$(
let $multi_account = accounts_iter.as_slice();
for account in $multi_account {
if !account.is_writable {
msg!(
"Account {} ({}) should have been writable.",
stringify!($multi_account),
account.key,
);
return Err(LidoError::InvalidAccountInfo.into());
}
}
for _ in 0..$multi_account.len() {
accounts_iter.next();
}
)?
if let Some(account) = accounts_iter.next() {
msg!(
"Instruction was passed more accounts than needed, did not expect {}.",
account.key,
);
return Err(LidoError::TooManyAccountKeys.into());
}
let result = $NameAccountInfo {
$( $var_account ),*
$(
,
$( $const_account ),*
)?
$( , $multi_account )?
};
Ok(result)
}
}
}
}
#[cfg(test)]
mod test {
use crate::error::LidoError;
use solana_program::{
account_info::AccountInfo, instruction::AccountMeta, program_error::ProgramError,
pubkey::Pubkey,
};
#[test]
fn accounts_struct_only_pub() {
accounts_struct! {
TestAccountsMeta, TestAccountsInfo {
pub s0_w0 { is_signer: false, is_writable: false, },
pub s1_w0 { is_signer: true, is_writable: false, },
pub s0_w1 { is_signer: false, is_writable: true, },
pub s1_w1 { is_signer: true, is_writable: true, },
}
}
let input = TestAccountsMeta {
s0_w0: Pubkey::new_unique(),
s1_w0: Pubkey::new_unique(),
s0_w1: Pubkey::new_unique(),
s1_w1: Pubkey::new_unique(),
};
let account_metas: Vec<AccountMeta> = input.to_vec();
assert_eq!(account_metas[0].pubkey, input.s0_w0);
assert_eq!(account_metas[1].pubkey, input.s1_w0);
assert_eq!(account_metas[2].pubkey, input.s0_w1);
assert_eq!(account_metas[3].pubkey, input.s1_w1);
assert_eq!(account_metas[0].is_signer, false);
assert_eq!(account_metas[0].is_writable, false);
assert_eq!(account_metas[1].is_signer, true);
assert_eq!(account_metas[1].is_writable, false);
assert_eq!(account_metas[2].is_signer, false);
assert_eq!(account_metas[2].is_writable, true);
assert_eq!(account_metas[3].is_signer, true);
assert_eq!(account_metas[3].is_writable, true);
let roundtripped = TestAccountsMeta::try_from_slice(&account_metas).unwrap();
assert_eq!(roundtripped.s0_w0, input.s0_w0);
assert_eq!(roundtripped.s1_w0, input.s1_w0);
assert_eq!(roundtripped.s0_w1, input.s0_w1);
assert_eq!(roundtripped.s1_w1, input.s1_w1);
let mut lamports = vec![0; account_metas.len()];
let mut datas = vec![vec![]; account_metas.len()];
let owner = Pubkey::new_unique();
let executable = false;
let rent_epoch = 0;
let mut account_infos: Vec<AccountInfo> = account_metas
.iter()
.zip(lamports.iter_mut())
.zip(datas.iter_mut())
.map(|((meta, lamports), data)| {
AccountInfo::new(
&meta.pubkey,
meta.is_signer,
meta.is_writable,
lamports,
data,
&owner,
executable,
rent_epoch,
)
})
.collect();
let output = TestAccountsInfo::try_from_slice(&account_infos[..]).unwrap();
assert_eq!(output.s0_w0.key, &input.s0_w0);
assert_eq!(output.s1_w0.key, &input.s1_w0);
assert_eq!(output.s0_w1.key, &input.s0_w1);
account_infos[1].is_signer = false;
assert_eq!(
TestAccountsInfo::try_from_slice(&account_infos[..])
.err()
.unwrap(),
LidoError::InvalidAccountInfo.into(),
);
account_infos[1].is_signer = true;
account_infos[2].is_writable = false;
assert_eq!(
TestAccountsInfo::try_from_slice(&account_infos[..])
.err()
.unwrap(),
LidoError::InvalidAccountInfo.into(),
);
account_infos[2].is_writable = true;
account_infos[0].is_signer = true;
account_infos[0].is_writable = true;
assert!(TestAccountsInfo::try_from_slice(&account_infos[..]).is_ok());
}
#[test]
fn accounts_struct_with_const() {
use solana_program::sysvar::clock;
accounts_struct! {
TestAccountsMeta, TestAccountsInfo {
pub not_sysvar { is_signer: false, is_writable: false, },
const sysvar_clock = clock::id(),
}
}
let input = TestAccountsMeta {
not_sysvar: Pubkey::new_unique(),
};
let account_metas: Vec<AccountMeta> = input.to_vec();
assert_eq!(account_metas[0].pubkey, input.not_sysvar);
assert_eq!(account_metas[1].pubkey, clock::id());
assert_eq!(account_metas[1].is_signer, false);
assert_eq!(account_metas[1].is_writable, false);
let key0 = Pubkey::new_unique();
let key_clock = clock::id();
let is_signer = false;
let is_writable = false;
let mut lamports0 = 0;
let mut lamports1 = 0;
let mut data0 = vec![];
let mut data1 = vec![];
let owner = Pubkey::new_unique();
let executable = false;
let rent_epoch = 0;
let mut account_infos = vec![
AccountInfo::new(
&key0,
is_signer,
is_writable,
&mut lamports0,
&mut data0,
&owner,
executable,
rent_epoch,
),
AccountInfo::new(
&key_clock,
is_signer,
is_writable,
&mut lamports1,
&mut data1,
&owner,
executable,
rent_epoch,
),
];
let output = TestAccountsInfo::try_from_slice(&account_infos).unwrap();
assert_eq!(output.not_sysvar.key, account_infos[0].key);
assert_eq!(output.sysvar_clock.key, &clock::id());
let key1 = Pubkey::new_unique();
account_infos[1].key = &key1;
assert_eq!(
TestAccountsInfo::try_from_slice(&account_infos)
.err()
.unwrap(),
LidoError::InvalidAccountInfo.into(),
);
}
#[test]
fn accounts_struct_variadic() {
accounts_struct! {
TestAccountsMeta, TestAccountsInfo {
pub single { is_signer: false, is_writable: false, },
pub ...remainder { is_signer: false, is_writable: true, },
}
}
let input_0 = TestAccountsMeta {
single: Pubkey::new_unique(),
remainder: vec![],
};
let account_metas: Vec<AccountMeta> = input_0.to_vec();
assert_eq!(account_metas.len(), 1);
let input_1 = TestAccountsMeta {
single: Pubkey::new_unique(),
remainder: vec![Pubkey::new_unique()],
};
let account_metas: Vec<AccountMeta> = input_1.to_vec();
assert_eq!(account_metas.len(), 2);
assert_eq!(account_metas[0].pubkey, input_1.single);
assert_eq!(account_metas[1].pubkey, input_1.remainder[0]);
let input_2 = TestAccountsMeta {
single: Pubkey::new_unique(),
remainder: vec![Pubkey::new_unique(), Pubkey::new_unique()],
};
let account_metas: Vec<AccountMeta> = input_2.to_vec();
assert_eq!(account_metas.len(), 3);
assert_eq!(account_metas[0].pubkey, input_2.single);
assert_eq!(account_metas[1].pubkey, input_2.remainder[0]);
assert_eq!(account_metas[2].pubkey, input_2.remainder[1]);
let pubkeys = vec![
Pubkey::new_unique(),
Pubkey::new_unique(),
Pubkey::new_unique(),
];
let is_signer = false;
let is_writable = true;
let mut lamports = vec![0; 3];
let mut datas = vec![vec![]; 3];
let owner = Pubkey::new_unique();
let executable = false;
let rent_epoch = 0;
let mut account_infos: Vec<AccountInfo> = pubkeys
.iter()
.zip(lamports.iter_mut())
.zip(datas.iter_mut())
.map(|((pubkey, lamports), data)| {
AccountInfo::new(
pubkey,
is_signer,
is_writable,
lamports,
data,
&owner,
executable,
rent_epoch,
)
})
.collect();
let output_info = TestAccountsInfo::try_from_slice(&account_infos).unwrap();
assert_eq!(output_info.single.key, &pubkeys[0]);
assert_eq!(output_info.remainder.len(), 2);
assert_eq!(output_info.remainder[0].key, &pubkeys[1]);
assert_eq!(output_info.remainder[1].key, &pubkeys[2]);
let mut account_metas: Vec<_> = account_infos
.iter()
.map(|ai| match ai.is_writable {
true => AccountMeta::new(*ai.key, ai.is_signer),
false => AccountMeta::new_readonly(*ai.key, ai.is_signer),
})
.collect();
let output_meta = TestAccountsMeta::try_from_slice(&account_metas).unwrap();
assert_eq!(output_meta.single, pubkeys[0]);
assert_eq!(output_meta.remainder.len(), 2);
assert_eq!(output_meta.remainder[0], pubkeys[1]);
assert_eq!(output_meta.remainder[1], pubkeys[2]);
account_infos[1].is_writable = false;
account_metas[1].is_writable = false;
assert_eq!(
TestAccountsInfo::try_from_slice(&account_infos).err(),
Some(LidoError::InvalidAccountInfo.into()),
);
assert_eq!(
TestAccountsMeta::try_from_slice(&account_metas).err(),
Some(LidoError::InvalidAccountInfo.into()),
);
}
}