use crate::address::Address;
use crate::error::ProgramError;
use crate::AccountView;
#[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) {
#[cfg(target_os = "solana")]
{
crate::native_boundary::find_program_address(seeds, program_id)
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id);
panic!(
"hopper: find_program_address requires the SVM sha256 syscall; \
run PDA paths under the SVM harness (target_os = \"solana\")"
);
}
}
#[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> {
#[cfg(target_os = "solana")]
{
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)
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id, expected);
Err(ProgramError::InvalidSeeds)
}
}
#[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> {
#[cfg(target_os = "solana")]
{
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)
}
#[cfg(not(target_os = "solana"))]
{
let _ = (seeds, program_id, expected);
Err(ProgramError::InvalidSeeds)
}
}
#[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
);
}
}