use crate::address::Address;
use crate::error::ProgramError;
use crate::AccountView;
pub const MAX_SEED_LEN: usize = 32;
const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
#[inline]
pub fn create_with_seed(
base: &Address,
seed: &[u8],
owner: &Address,
) -> Result<Address, ProgramError> {
if seed.len() > MAX_SEED_LEN {
return Err(ProgramError::MaxSeedLengthExceeded);
}
let owner_bytes = owner.as_array();
if owner_bytes[32 - PDA_MARKER.len()..] == PDA_MARKER[..] {
return Err(ProgramError::IllegalOwner);
}
let digest = hopper_native::hash::sha256(&[base.as_array(), seed, owner_bytes])
.map_err(|_| ProgramError::InvalidArgument)?;
Ok(Address::new_from_array(digest))
}
#[inline]
pub fn verify_address_with_seed(
expected: &Address,
base: &Address,
seed: &[u8],
owner: &Address,
) -> Result<(), ProgramError> {
if create_with_seed(base, seed, owner)? == *expected {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
#[inline]
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Address,
) -> Result<Address, ProgramError> {
crate::native_boundary::create_program_address(seeds, program_id)
}
#[inline]
pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
crate::native_boundary::find_program_address(seeds, program_id)
}
#[inline]
pub fn try_find_program_address(
seeds: &[&[u8]],
program_id: &Address,
) -> Result<(Address, u8), ProgramError> {
let backend = hopper_native::Address::new_from_array(*program_id.as_array());
hopper_native::pda::based_try_find_program_address(seeds, &backend)
.map(|(address, bump)| (Address::new_from_array(address.to_bytes()), bump))
.map_err(ProgramError::from)
}
pub const fn find_program_address_const(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
let backend = hopper_native::address::Address::new_from_array(*program_id.as_array());
let (address, bump) = hopper_native::pda::find_program_address_const(seeds, &backend);
(Address::new_from_array(address.to_bytes()), bump)
}
#[inline(always)]
pub fn derive(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
find_program_address(seeds, program_id)
}
pub const fn const_program_address(program_id: &Address, seeds: &[&[u8]], bump: u8) -> Address {
let backend = hopper_native::address::Address::new_from_array(*program_id.as_array());
Address::new_from_array(
hopper_native::pda::program_address_const(seeds, bump, &backend).to_bytes(),
)
}
#[inline]
pub fn verify_pda_address(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<(), ProgramError> {
hopper_native::pda::verify_program_address(
seeds,
crate::native_boundary::as_backend_address(program_id),
crate::native_boundary::as_backend_address(expected),
)
.map_err(ProgramError::from)
}
#[cold]
#[inline(never)]
pub fn verify_pda_address_cold(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<(), ProgramError> {
verify_pda_address(seeds, program_id, expected)
}
#[inline]
pub fn verify_pda_address_checked(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<(), ProgramError> {
let derived = create_program_address(seeds, program_id)?;
if crate::address::address_eq(&derived, expected) {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
#[inline]
pub fn find_bump_for_address(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<u8, ProgramError> {
hopper_native::pda::find_bump_for_address(
seeds,
crate::native_boundary::as_backend_address(program_id),
crate::native_boundary::as_backend_address(expected),
)
.map_err(ProgramError::from)
}
#[inline]
pub fn find_canonical_bump_checked(
seeds: &[&[u8]],
program_id: &Address,
expected: &Address,
) -> Result<u8, ProgramError> {
#[cfg(target_os = "solana")]
let (derived, bump) = hopper_native::pda::based_try_find_program_address(
seeds,
crate::native_boundary::as_backend_address(program_id),
)
.map(|(address, bump)| (Address::new_from_array(address.to_bytes()), bump))
.map_err(ProgramError::from)?;
#[cfg(not(target_os = "solana"))]
let (derived, bump) = find_program_address(seeds, program_id);
if crate::address::address_eq(&derived, expected) {
Ok(bump)
} else {
Err(ProgramError::InvalidSeeds)
}
}
#[inline]
pub fn verify_pda(
account: &AccountView<'_>,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<(), ProgramError> {
#[cfg(target_os = "solana")]
{
hopper_native::pda::verify_pda(
account.as_backend(),
seeds,
crate::native_boundary::as_backend_address(program_id),
)
.map_err(ProgramError::from)
}
#[cfg(not(target_os = "solana"))]
{
let expected = create_program_address(seeds, program_id)?;
if crate::address::address_eq(account.address(), &expected) {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
}
#[inline]
pub fn verify_pda_with_bump(
account: &AccountView<'_>,
seeds: &[&[u8]],
bump: u8,
program_id: &Address,
) -> Result<(), ProgramError> {
#[cfg(target_os = "solana")]
{
hopper_native::pda::verify_pda_with_bump(
account.as_backend(),
seeds,
bump,
crate::native_boundary::as_backend_address(program_id),
)
.map_err(ProgramError::from)
}
#[cfg(not(target_os = "solana"))]
{
if seeds.len() >= 16 {
return Err(ProgramError::InvalidSeeds);
}
let mut full_seeds: [&[u8]; 16] = [&[]; 16];
let num = seeds.len();
let mut i = 0;
while i < num {
full_seeds[i] = seeds[i];
i += 1;
}
let bump_bytes = [bump];
full_seeds[num] = &bump_bytes;
let expected = create_program_address(&full_seeds[..num + 1], program_id)?;
if crate::address::address_eq(account.address(), &expected) {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
}
#[inline]
pub fn find_and_verify_pda(
account: &AccountView<'_>,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<u8, ProgramError> {
#[cfg(target_os = "solana")]
{
let expected_addr = account.as_backend().address();
let backend_expected =
unsafe { &*(expected_addr as *const hopper_native::address::Address) };
verify_pda_sha256_loop(backend_expected, seeds, program_id)
}
#[cfg(not(target_os = "solana"))]
{
let (expected, bump) = find_program_address(seeds, program_id);
if crate::address::address_eq(account.address(), &expected) {
Ok(bump)
} else {
Err(ProgramError::InvalidSeeds)
}
}
}
#[inline]
pub fn verify_pda_strict(
expected: &Address,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<(), ProgramError> {
#[cfg(target_os = "solana")]
{
let backend_expected =
unsafe { &*(expected as *const Address as *const hopper_native::address::Address) };
verify_pda_sha256_loop(backend_expected, seeds, program_id).map(|_| ())
}
#[cfg(not(target_os = "solana"))]
{
let (derived, _) = find_program_address(seeds, program_id);
if crate::address::address_eq(&derived, expected) {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
}
#[cfg(target_os = "solana")]
#[inline(always)]
fn verify_pda_sha256_loop(
expected: &hopper_native::address::Address,
seeds: &[&[u8]],
program_id: &Address,
) -> Result<u8, ProgramError> {
hopper_native::pda::find_bump_for_address(
seeds,
crate::native_boundary::as_backend_address(program_id),
expected,
)
.map_err(ProgramError::from)
}
#[inline]
pub fn verify_pda_from_stored_bump(
account: &AccountView<'_>,
seeds: &[&[u8]],
bump_offset: usize,
program_id: &Address,
) -> Result<(), ProgramError> {
#[cfg(target_os = "solana")]
{
hopper_native::verify_pda_from_stored_bump(
account.as_backend(),
seeds,
bump_offset,
crate::native_boundary::as_backend_address(program_id),
)
.map_err(ProgramError::from)
}
#[cfg(not(target_os = "solana"))]
{
let data = account.try_borrow()?;
if bump_offset >= data.len() {
return Err(ProgramError::AccountDataTooSmall);
}
let bump = data[bump_offset];
if seeds.len() >= 16 {
return Err(ProgramError::InvalidSeeds);
}
let mut full_seeds: [&[u8]; 16] = [&[]; 16];
let num = seeds.len();
let mut i = 0;
while i < num {
full_seeds[i] = seeds[i];
i += 1;
}
let bump_bytes = [bump];
full_seeds[num] = &bump_bytes;
let expected = create_program_address(&full_seeds[..num + 1], program_id)?;
if crate::address::address_eq(account.address(), &expected) {
Ok(())
} else {
Err(ProgramError::InvalidSeeds)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn const_program_address_matches_devnet_created_pdas() {
const PROGRAM: Address = crate::address!("F4Um7PWsnZfN7y8WFzu1aPYJwqGduJTa4zuCGY9EUqMy");
const PAYER: Address = crate::address!("4sbBUbY71JFeA4kJckBmNnTADiFu4jtu84Gzev52ZEhn");
const AUTHORITY_C: Address =
crate::address!("7Qj28pSptq3YEdppwTmxDEP4jLsS1o67D1ZfKQJB9SE2");
const PINA_COUNTER: Address =
crate::address!("GJQcuWrT2f3f4KNuJcXhhwUa1ZQTYbxzzJ1hotzKu8hS");
const PDA_A: Address = crate::const_pda!(PROGRAM, [b"counter", PAYER.as_array()], 252);
const PDA_C: Address =
crate::const_pda!(PROGRAM, [b"counter", AUTHORITY_C.as_array()], 254);
const PDA_PINA: Address =
const_program_address(&PINA_COUNTER, &[b"counter", PAYER.as_array()], 253);
assert_eq!(
PDA_A,
crate::address!("Cn3JBYNBEctRDGuotxM7c3Fz3QgCZgRXkKV1G7h1qZKn")
);
assert_eq!(
PDA_C,
crate::address!("6vh34eBGs3gvwdaJ3fgXDLQMtNfqUwSJYrHqZ3FgCwYP")
);
assert_eq!(
PDA_PINA,
crate::address!("CW1z5aL4hTAFFubWKVKw1ANkdYurNEAiWbqxKsDCaERH")
);
assert_ne!(
const_program_address(&PROGRAM, &[b"counter", PAYER.as_array()], 251),
PDA_A
);
}
}
#[cfg(test)]
mod seeded_address_tests {
use super::*;
use solana_pubkey::Pubkey;
#[test]
fn fallible_search_matches_sdk_and_rejects_invalid_seed_shapes() {
let program = Address::new_from_array([9; 32]);
for seed in [b"".as_slice(), b"vault", &[7; 32]] {
let (address, bump) = try_find_program_address(&[seed], &program).unwrap();
let (expected, expected_bump) =
Pubkey::find_program_address(&[seed], &Pubkey::new_from_array([9; 32]));
assert_eq!(address.to_bytes(), expected.to_bytes());
assert_eq!(bump, expected_bump);
}
assert_eq!(
try_find_program_address(&[&[0; 33]], &program),
Err(ProgramError::InvalidSeeds)
);
assert_eq!(
try_find_program_address(&[b"x".as_slice(); 16], &program),
Err(ProgramError::InvalidSeeds)
);
}
#[test]
fn create_with_seed_matches_the_canonical_derivation() {
let base = Address::new_from_array([3; 32]);
let owner = Address::new_from_array([9; 32]);
for seed in ["", "vault", "0123456789abcdef0123456789abcdef", "caf\u{e9}"] {
let canonical = Pubkey::create_with_seed(
&Pubkey::new_from_array([3; 32]),
seed,
&Pubkey::new_from_array([9; 32]),
)
.unwrap();
let derived = create_with_seed(&base, seed.as_bytes(), &owner).unwrap();
assert_eq!(derived.as_array(), &canonical.to_bytes(), "seed {seed:?}");
assert_eq!(
verify_address_with_seed(&derived, &base, seed.as_bytes(), &owner),
Ok(())
);
assert_eq!(
verify_address_with_seed(&base, &base, seed.as_bytes(), &owner),
Err(ProgramError::InvalidSeeds)
);
}
}
#[test]
fn create_with_seed_refuses_what_the_runtime_refuses() {
let base = Address::new_from_array([3; 32]);
let owner = Address::new_from_array([9; 32]);
assert_eq!(
create_with_seed(&base, &[b'x'; 33], &owner),
Err(ProgramError::MaxSeedLengthExceeded)
);
let mut marked = [9u8; 32];
marked[11..].copy_from_slice(b"ProgramDerivedAddress");
assert_eq!(
create_with_seed(&base, b"vault", &Address::new_from_array(marked)),
Err(ProgramError::IllegalOwner)
);
assert!(Pubkey::create_with_seed(
&Pubkey::new_from_array([3; 32]),
"vault",
&Pubkey::new_from_array(marked),
)
.is_err());
}
}