use {
crate::{error::AccountResolutionError, pubkey_data::PubkeyData, seeds::Seed},
bytemuck::{Pod, Zeroable},
solana_program::{
account_info::AccountInfo,
instruction::AccountMeta,
program_error::ProgramError,
pubkey::{Pubkey, PUBKEY_BYTES},
},
spl_pod::primitives::PodBool,
};
fn resolve_pda<'a, F>(
seeds: &[Seed],
instruction_data: &[u8],
program_id: &Pubkey,
get_account_key_data_fn: F,
) -> Result<Pubkey, ProgramError>
where
F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
{
let mut pda_seeds: Vec<&[u8]> = vec![];
for config in seeds {
match config {
Seed::Uninitialized => (),
Seed::Literal { bytes } => pda_seeds.push(bytes),
Seed::InstructionData { index, length } => {
let arg_start = *index as usize;
let arg_end = arg_start + *length as usize;
if arg_end > instruction_data.len() {
return Err(AccountResolutionError::InstructionDataTooSmall.into());
}
pda_seeds.push(&instruction_data[arg_start..arg_end]);
}
Seed::AccountKey { index } => {
let account_index = *index as usize;
let address = get_account_key_data_fn(account_index)
.ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
.0;
pda_seeds.push(address.as_ref());
}
Seed::AccountData {
account_index,
data_index,
length,
} => {
let account_index = *account_index as usize;
let account_data = get_account_key_data_fn(account_index)
.ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
.1
.ok_or::<ProgramError>(AccountResolutionError::AccountDataNotFound.into())?;
let arg_start = *data_index as usize;
let arg_end = arg_start + *length as usize;
if account_data.len() < arg_end {
return Err(AccountResolutionError::AccountDataTooSmall.into());
}
pda_seeds.push(&account_data[arg_start..arg_end]);
}
}
}
Ok(Pubkey::find_program_address(&pda_seeds, program_id).0)
}
fn resolve_key_data<'a, F>(
key_data: &PubkeyData,
instruction_data: &[u8],
get_account_key_data_fn: F,
) -> Result<Pubkey, ProgramError>
where
F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
{
match key_data {
PubkeyData::Uninitialized => Err(ProgramError::InvalidAccountData),
PubkeyData::InstructionData { index } => {
let key_start = *index as usize;
let key_end = key_start + PUBKEY_BYTES;
if key_end > instruction_data.len() {
return Err(AccountResolutionError::InstructionDataTooSmall.into());
}
Ok(Pubkey::new_from_array(
instruction_data[key_start..key_end].try_into().unwrap(),
))
}
PubkeyData::AccountData {
account_index,
data_index,
} => {
let account_index = *account_index as usize;
let account_data = get_account_key_data_fn(account_index)
.ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
.1
.ok_or::<ProgramError>(AccountResolutionError::AccountDataNotFound.into())?;
let arg_start = *data_index as usize;
let arg_end = arg_start + PUBKEY_BYTES;
if account_data.len() < arg_end {
return Err(AccountResolutionError::AccountDataTooSmall.into());
}
Ok(Pubkey::new_from_array(
account_data[arg_start..arg_end].try_into().unwrap(),
))
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub struct ExtraAccountMeta {
pub discriminator: u8,
pub address_config: [u8; 32],
pub is_signer: PodBool,
pub is_writable: PodBool,
}
const U8_TOP_BIT: u8 = 1 << 7;
impl ExtraAccountMeta {
pub fn new_with_pubkey(
pubkey: &Pubkey,
is_signer: bool,
is_writable: bool,
) -> Result<Self, ProgramError> {
Ok(Self {
discriminator: 0,
address_config: pubkey.to_bytes(),
is_signer: is_signer.into(),
is_writable: is_writable.into(),
})
}
pub fn new_with_seeds(
seeds: &[Seed],
is_signer: bool,
is_writable: bool,
) -> Result<Self, ProgramError> {
Ok(Self {
discriminator: 1,
address_config: Seed::pack_into_address_config(seeds)?,
is_signer: is_signer.into(),
is_writable: is_writable.into(),
})
}
pub fn new_with_pubkey_data(
key_data: &PubkeyData,
is_signer: bool,
is_writable: bool,
) -> Result<Self, ProgramError> {
Ok(Self {
discriminator: 2,
address_config: PubkeyData::pack_into_address_config(key_data)?,
is_signer: is_signer.into(),
is_writable: is_writable.into(),
})
}
pub fn new_external_pda_with_seeds(
program_index: u8,
seeds: &[Seed],
is_signer: bool,
is_writable: bool,
) -> Result<Self, ProgramError> {
Ok(Self {
discriminator: program_index
.checked_add(U8_TOP_BIT)
.ok_or(AccountResolutionError::InvalidSeedConfig)?,
address_config: Seed::pack_into_address_config(seeds)?,
is_signer: is_signer.into(),
is_writable: is_writable.into(),
})
}
pub fn resolve<'a, F>(
&self,
instruction_data: &[u8],
program_id: &Pubkey,
get_account_key_data_fn: F,
) -> Result<AccountMeta, ProgramError>
where
F: Fn(usize) -> Option<(&'a Pubkey, Option<&'a [u8]>)>,
{
match self.discriminator {
0 => AccountMeta::try_from(self),
x if x == 1 || x >= U8_TOP_BIT => {
let program_id = if x == 1 {
program_id
} else {
get_account_key_data_fn(x.saturating_sub(U8_TOP_BIT) as usize)
.ok_or::<ProgramError>(AccountResolutionError::AccountNotFound.into())?
.0
};
let seeds = Seed::unpack_address_config(&self.address_config)?;
Ok(AccountMeta {
pubkey: resolve_pda(
&seeds,
instruction_data,
program_id,
get_account_key_data_fn,
)?,
is_signer: self.is_signer.into(),
is_writable: self.is_writable.into(),
})
}
2 => {
let key_data = PubkeyData::unpack(&self.address_config)?;
Ok(AccountMeta {
pubkey: resolve_key_data(&key_data, instruction_data, get_account_key_data_fn)?,
is_signer: self.is_signer.into(),
is_writable: self.is_writable.into(),
})
}
_ => Err(ProgramError::InvalidAccountData),
}
}
}
impl From<&AccountMeta> for ExtraAccountMeta {
fn from(meta: &AccountMeta) -> Self {
Self {
discriminator: 0,
address_config: meta.pubkey.to_bytes(),
is_signer: meta.is_signer.into(),
is_writable: meta.is_writable.into(),
}
}
}
impl From<AccountMeta> for ExtraAccountMeta {
fn from(meta: AccountMeta) -> Self {
ExtraAccountMeta::from(&meta)
}
}
impl From<&AccountInfo<'_>> for ExtraAccountMeta {
fn from(account_info: &AccountInfo) -> Self {
Self {
discriminator: 0,
address_config: account_info.key.to_bytes(),
is_signer: account_info.is_signer.into(),
is_writable: account_info.is_writable.into(),
}
}
}
impl From<AccountInfo<'_>> for ExtraAccountMeta {
fn from(account_info: AccountInfo) -> Self {
ExtraAccountMeta::from(&account_info)
}
}
impl TryFrom<&ExtraAccountMeta> for AccountMeta {
type Error = ProgramError;
fn try_from(pod: &ExtraAccountMeta) -> Result<Self, Self::Error> {
if pod.discriminator == 0 {
Ok(AccountMeta {
pubkey: Pubkey::from(pod.address_config),
is_signer: pod.is_signer.into(),
is_writable: pod.is_writable.into(),
})
} else {
Err(AccountResolutionError::AccountTypeNotAccountMeta.into())
}
}
}