use solana_account_view::AccountView;
use solana_address::Address;
use solana_program_error::ProgramError;
use solana_pubkey::Pubkey;
use crate::pda::SETTLEMENT_SEED;
use crate::SettlementError;
pub const BUFFER_SEED: &[u8] = b"buffer";
pub fn buffer_pda_seeds(mint: &[u8; 32]) -> [&[u8]; 3] {
[SETTLEMENT_SEED, mint, BUFFER_SEED]
}
pub fn buffer_pda_signer_seeds<'a>(mint: &'a [u8; 32], bump: &'a [u8; 1]) -> [&'a [u8]; 4] {
let [s0, s1, s2] = buffer_pda_seeds(mint);
[s0, s1, s2, bump]
}
pub fn find_buffer_pda(program_id: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&buffer_pda_seeds(mint.as_array()), program_id)
}
#[must_use = "ignoring the output means ignoring the validation result"]
pub fn validate_buffer_pda(
program_id: &Address,
buffer: &AccountView,
mint: &Address,
bump: u8,
) -> Result<(), ProgramError> {
let derived = Address::create_program_address(
&buffer_pda_signer_seeds(&mint.to_bytes(), &[bump]),
program_id,
)
.map_err(|_| SettlementError::PushSourceNotBuffer)?;
if buffer.address() != &derived {
return Err(SettlementError::PushSourceNotBuffer.into());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pda::tests::assert_distinct_versions_yield_distinct_pdas;
#[test]
fn find_buffer_pda_uses_canonical_seeds() {
let token = Pubkey::new_unique();
crate::pda::tests::assert_canonical_bump(
|program_id| find_buffer_pda(program_id, &token),
buffer_pda_seeds(token.as_array()),
);
}
#[test]
fn distinct_versions_yield_distinct_buffer_pdas() {
let mint = Pubkey::new_unique();
let (pda, _) = find_buffer_pda(&crate::ID, &mint);
assert_distinct_versions_yield_distinct_pdas(&pda, &[mint.as_array(), BUFFER_SEED]);
}
#[test]
fn accepts_a_valid_pda() {
let program_id = Pubkey::new_unique();
let mint = Pubkey::new_unique();
let (pda, bump) = find_buffer_pda(&program_id, &mint);
let buffer = crate::instruction::fixtures::fake_account(pda);
validate_buffer_pda(&program_id, &buffer, &mint, bump)
.expect("the canonical buffer PDA must be accepted");
}
#[test]
fn rejects_an_invalid_address() {
let program_id = Pubkey::new_unique();
let mint = Pubkey::new_unique();
let (_, bump) = find_buffer_pda(&program_id, &mint);
let buffer = crate::instruction::fixtures::fake_account(Pubkey::new_unique());
let err = validate_buffer_pda(&program_id, &buffer, &mint, bump)
.expect_err("a non-canonical address must be rejected");
assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
}
#[test]
fn rejects_a_wrong_bump() {
let program_id = Pubkey::new_unique();
let mint = Pubkey::new_unique();
let (pda, bump) = find_buffer_pda(&program_id, &mint);
let buffer = crate::instruction::fixtures::fake_account(pda);
let err = validate_buffer_pda(&program_id, &buffer, &mint, bump ^ 1)
.expect_err("a wrong bump must be rejected");
assert_eq!(err, SettlementError::PushSourceNotBuffer.into());
}
mod proptest {
use ::proptest::prelude::*;
use super::*;
proptest! {
#[test]
fn distinct_tokens_yield_distinct_pdas(
program_id in any::<[u8; 32]>(),
token1 in any::<[u8; 32]>(),
token2 in any::<[u8; 32]>(),
) {
prop_assume!(token1 != token2);
let program_id = Pubkey::new_from_array(program_id);
let (pda1, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token1));
let (pda2, _) = find_buffer_pda(&program_id, &Pubkey::new_from_array(token2));
prop_assert_ne!(pda1, pda2);
}
}
}
}