use crate::{
error::ProgramWhitelistError,
ids::{
account_solana::ID as WHITELIST_ACCOUNT_ID,
program_solana::ID as WHITELIST_PROGRAM_ID,
},
state::{load, whitelist_entry::ProgramWhitelistEntry},
};
use {
solana_instruction::error::InstructionError as SolanaInstructionError,
solana_pubkey::Pubkey as SolanaPubkey,
};
pub struct WhitelistEntryInformation<'a> {
pub key: &'a SolanaPubkey,
pub owner: &'a SolanaPubkey,
pub data: &'a [u8],
}
pub fn validate_deploy_authority_solana_program(
entry: WhitelistEntryInformation,
deploy_authority: &SolanaPubkey,
) -> Result<(), SolanaInstructionError> {
let (pda, _) = SolanaPubkey::find_program_address(
&[WHITELIST_ACCOUNT_ID.as_ref(), deploy_authority.as_ref()],
&WHITELIST_PROGRAM_ID,
);
if &pda != entry.key {
return Err(to_solana_instruction_error(
ProgramWhitelistError::InvalidProgramWhitelistEntryAccount,
));
}
if entry.owner != &WHITELIST_PROGRAM_ID {
return Err(to_solana_instruction_error(
ProgramWhitelistError::InvalidProgramWhitelistEntryAccountOwner,
));
}
let entry_data = load::<ProgramWhitelistEntry>(entry.data)
.map_err(|_| SolanaInstructionError::InvalidAccountData)?;
if entry_data.entry_address != deploy_authority.to_bytes() {
return Err(SolanaInstructionError::InvalidAccountData);
}
if !entry_data.is_approved() {
return Err(to_solana_instruction_error(
ProgramWhitelistError::DeployerNotApproved,
));
}
Ok(())
}
pub fn to_solana_instruction_error(
error: ProgramWhitelistError,
) -> SolanaInstructionError {
SolanaInstructionError::Custom(error as u32)
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::state::{
whitelist_entry::ProgramWhitelistEntry, EntryState, SizeOf,
},
};
fn entry_pda(deploy_authority: &SolanaPubkey) -> SolanaPubkey {
SolanaPubkey::find_program_address(
&[WHITELIST_ACCOUNT_ID.as_ref(), deploy_authority.as_ref()],
&WHITELIST_PROGRAM_ID,
)
.0
}
fn entry_bytes(
deploy_authority: &SolanaPubkey,
state: EntryState,
) -> [u8; ProgramWhitelistEntry::SIZE_OF] {
let mut data = [0u8; ProgramWhitelistEntry::SIZE_OF];
data[..32].copy_from_slice(deploy_authority.as_ref());
data[32] = state as u8;
data
}
#[test]
fn deploy_gate_blocks_pending_allows_approved() {
let deploy_authority = SolanaPubkey::new_from_array([7u8; 32]);
let key = entry_pda(&deploy_authority);
let pending = entry_bytes(&deploy_authority, EntryState::Pending);
let err = validate_deploy_authority_solana_program(
WhitelistEntryInformation {
key: &key,
owner: &WHITELIST_PROGRAM_ID,
data: &pending,
},
&deploy_authority,
)
.unwrap_err();
assert_eq!(
err,
SolanaInstructionError::Custom(
ProgramWhitelistError::DeployerNotApproved as u32
),
);
let approved = entry_bytes(&deploy_authority, EntryState::Approved);
validate_deploy_authority_solana_program(
WhitelistEntryInformation {
key: &key,
owner: &WHITELIST_PROGRAM_ID,
data: &approved,
},
&deploy_authority,
)
.expect("approved entry must pass the deploy gate");
}
#[test]
fn deploy_gate_rejects_wrong_entry_pda() {
let deploy_authority = SolanaPubkey::new_from_array([7u8; 32]);
let wrong_key = SolanaPubkey::new_from_array([9u8; 32]);
let approved = entry_bytes(&deploy_authority, EntryState::Approved);
let err = validate_deploy_authority_solana_program(
WhitelistEntryInformation {
key: &wrong_key,
owner: &WHITELIST_PROGRAM_ID,
data: &approved,
},
&deploy_authority,
)
.unwrap_err();
assert_eq!(
err,
SolanaInstructionError::Custom(
ProgramWhitelistError::InvalidProgramWhitelistEntryAccount as u32
),
);
}
#[test]
fn deploy_gate_rejects_foreign_owner() {
let deploy_authority = SolanaPubkey::new_from_array([7u8; 32]);
let key = entry_pda(&deploy_authority);
let approved = entry_bytes(&deploy_authority, EntryState::Approved);
let foreign_owner = SolanaPubkey::new_from_array([2u8; 32]);
let err = validate_deploy_authority_solana_program(
WhitelistEntryInformation {
key: &key,
owner: &foreign_owner,
data: &approved,
},
&deploy_authority,
)
.unwrap_err();
assert_eq!(
err,
SolanaInstructionError::Custom(
ProgramWhitelistError::InvalidProgramWhitelistEntryAccountOwner as u32
),
);
}
#[test]
fn deploy_gate_rejects_malformed_data() {
let deploy_authority = SolanaPubkey::new_from_array([7u8; 32]);
let key = entry_pda(&deploy_authority);
let too_short = [0u8; 8];
let err = validate_deploy_authority_solana_program(
WhitelistEntryInformation {
key: &key,
owner: &WHITELIST_PROGRAM_ID,
data: &too_short,
},
&deploy_authority,
)
.unwrap_err();
assert_eq!(err, SolanaInstructionError::InvalidAccountData);
}
#[test]
fn deploy_gate_rejects_entry_address_mismatch() {
let deploy_authority = SolanaPubkey::new_from_array([7u8; 32]);
let key = entry_pda(&deploy_authority);
let other_authority = SolanaPubkey::new_from_array([8u8; 32]);
let mismatched = entry_bytes(&other_authority, EntryState::Approved);
let err = validate_deploy_authority_solana_program(
WhitelistEntryInformation {
key: &key,
owner: &WHITELIST_PROGRAM_ID,
data: &mismatched,
},
&deploy_authority,
)
.unwrap_err();
assert_eq!(err, SolanaInstructionError::InvalidAccountData);
}
}