#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![allow(clippy::arithmetic_side_effects)]
#[cfg(any(feature = "std", target_arch = "wasm32"))]
extern crate std;
#[cfg(feature = "dev-context-only-utils")]
use arbitrary::Arbitrary;
#[cfg(feature = "bytemuck")]
use bytemuck_derive::{Pod, Zeroable};
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
#[cfg(any(feature = "std", target_arch = "wasm32"))]
use std::vec::Vec;
#[cfg(feature = "borsh")]
use {
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
std::string::ToString,
};
use {
clone_solana_decode_error::DecodeError,
core::{
array,
convert::{Infallible, TryFrom},
fmt,
hash::{Hash, Hasher},
mem,
str::{from_utf8, FromStr},
},
num_traits::{FromPrimitive, ToPrimitive},
};
#[cfg(target_arch = "wasm32")]
use {
js_sys::{Array, Uint8Array},
wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue},
};
#[cfg(target_os = "solana")]
pub mod syscalls;
pub const PUBKEY_BYTES: usize = 32;
pub const MAX_SEED_LEN: usize = 32;
pub const MAX_SEEDS: usize = 16;
const MAX_BASE58_LEN: usize = 44;
#[cfg(any(target_os = "solana", feature = "sha2", feature = "curve25519"))]
const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
#[cfg(target_os = "solana")]
const SUCCESS: u64 = 0;
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PubkeyError {
MaxSeedLengthExceeded,
InvalidSeeds,
IllegalOwner,
}
impl ToPrimitive for PubkeyError {
#[inline]
fn to_i64(&self) -> Option<i64> {
Some(match *self {
PubkeyError::MaxSeedLengthExceeded => PubkeyError::MaxSeedLengthExceeded as i64,
PubkeyError::InvalidSeeds => PubkeyError::InvalidSeeds as i64,
PubkeyError::IllegalOwner => PubkeyError::IllegalOwner as i64,
})
}
#[inline]
fn to_u64(&self) -> Option<u64> {
self.to_i64().map(|x| x as u64)
}
}
impl FromPrimitive for PubkeyError {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
if n == PubkeyError::MaxSeedLengthExceeded as i64 {
Some(PubkeyError::MaxSeedLengthExceeded)
} else if n == PubkeyError::InvalidSeeds as i64 {
Some(PubkeyError::InvalidSeeds)
} else if n == PubkeyError::IllegalOwner as i64 {
Some(PubkeyError::IllegalOwner)
} else {
None
}
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
Self::from_i64(n as i64)
}
}
#[cfg(feature = "std")]
impl std::error::Error for PubkeyError {}
impl fmt::Display for PubkeyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PubkeyError::MaxSeedLengthExceeded => {
f.write_str("Length of the seed is too long for address generation")
}
PubkeyError::InvalidSeeds => {
f.write_str("Provided seeds do not result in a valid address")
}
PubkeyError::IllegalOwner => f.write_str("Provided owner is not allowed"),
}
}
}
impl<T> DecodeError<T> for PubkeyError {
fn type_of() -> &'static str {
"PubkeyError"
}
}
impl From<u64> for PubkeyError {
fn from(error: u64) -> Self {
match error {
0 => PubkeyError::MaxSeedLengthExceeded,
1 => PubkeyError::InvalidSeeds,
2 => PubkeyError::IllegalOwner,
_ => panic!("Unsupported PubkeyError"),
}
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
#[repr(transparent)]
#[cfg_attr(
feature = "frozen-abi",
derive(clone_solana_frozen_abi_macro::AbiExample)
)]
#[cfg_attr(
feature = "borsh",
derive(BorshSerialize, BorshDeserialize),
borsh(crate = "borsh")
)]
#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
pub struct Pubkey(pub(crate) [u8; 32]);
impl Hash for Pubkey {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(self.as_array());
}
}
#[cfg(all(feature = "rand", not(target_os = "solana")))]
mod hasher {
use {
crate::PUBKEY_BYTES,
core::{
cell::Cell,
hash::{BuildHasher, Hasher},
},
rand::{thread_rng, Rng},
};
#[derive(Default)]
pub struct PubkeyHasher {
offset: usize,
state: u64,
}
impl Hasher for PubkeyHasher {
#[inline]
fn finish(&self) -> u64 {
self.state
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
debug_assert_eq!(
bytes.len(),
PUBKEY_BYTES,
"This hasher is intended to be used with pubkeys and nothing else"
);
let chunk: &[u8; size_of::<u64>()] = bytes[self.offset..self.offset + size_of::<u64>()]
.try_into()
.unwrap();
self.state = u64::from_ne_bytes(*chunk);
}
}
#[derive(Clone)]
pub struct PubkeyHasherBuilder {
offset: usize,
}
impl Default for PubkeyHasherBuilder {
fn default() -> Self {
std::thread_local!(static OFFSET: Cell<usize> = {
let mut rng = thread_rng();
Cell::new(rng.gen_range(0..PUBKEY_BYTES - size_of::<u64>()))
});
let offset = OFFSET.with(|offset| {
let mut next_offset = offset.get() + 1;
if next_offset > PUBKEY_BYTES - size_of::<u64>() {
next_offset = 0;
}
offset.set(next_offset);
next_offset
});
PubkeyHasherBuilder { offset }
}
}
impl BuildHasher for PubkeyHasherBuilder {
type Hasher = PubkeyHasher;
#[inline]
fn build_hasher(&self) -> Self::Hasher {
PubkeyHasher {
offset: self.offset,
state: 0,
}
}
}
#[cfg(test)]
mod tests {
use {
super::PubkeyHasherBuilder,
crate::Pubkey,
core::hash::{BuildHasher, Hasher},
};
#[test]
fn test_pubkey_hasher_builder() {
let key = Pubkey::new_unique();
let builder = PubkeyHasherBuilder::default();
let mut hasher1 = builder.build_hasher();
let mut hasher2 = builder.build_hasher();
hasher1.write(key.as_array());
hasher2.write(key.as_array());
assert_eq!(
hasher1.finish(),
hasher2.finish(),
"Hashers made with same builder should be identical"
);
let builder2 = PubkeyHasherBuilder::default();
for _ in 0..64 {
let mut hasher3 = builder2.build_hasher();
hasher3.write(key.as_array());
std::dbg!(hasher1.finish());
std::dbg!(hasher3.finish());
if hasher1.finish() != hasher3.finish() {
return;
}
}
panic!("Hashers built with different builder should be different due to random offset");
}
#[test]
fn test_pubkey_hasher() {
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
let builder = PubkeyHasherBuilder::default();
let mut hasher1 = builder.build_hasher();
let mut hasher2 = builder.build_hasher();
hasher1.write(key1.as_array());
hasher2.write(key2.as_array());
assert_ne!(hasher1.finish(), hasher2.finish());
}
}
}
#[cfg(all(feature = "rand", not(target_os = "solana")))]
pub use hasher::{PubkeyHasher, PubkeyHasherBuilder};
impl clone_solana_sanitize::Sanitize for Pubkey {}
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsePubkeyError {
WrongSize,
Invalid,
}
impl ToPrimitive for ParsePubkeyError {
#[inline]
fn to_i64(&self) -> Option<i64> {
Some(match *self {
ParsePubkeyError::WrongSize => ParsePubkeyError::WrongSize as i64,
ParsePubkeyError::Invalid => ParsePubkeyError::Invalid as i64,
})
}
#[inline]
fn to_u64(&self) -> Option<u64> {
self.to_i64().map(|x| x as u64)
}
}
impl FromPrimitive for ParsePubkeyError {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
if n == ParsePubkeyError::WrongSize as i64 {
Some(ParsePubkeyError::WrongSize)
} else if n == ParsePubkeyError::Invalid as i64 {
Some(ParsePubkeyError::Invalid)
} else {
None
}
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
Self::from_i64(n as i64)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParsePubkeyError {}
impl fmt::Display for ParsePubkeyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParsePubkeyError::WrongSize => f.write_str("String is the wrong size"),
ParsePubkeyError::Invalid => f.write_str("Invalid Base58 string"),
}
}
}
impl From<Infallible> for ParsePubkeyError {
fn from(_: Infallible) -> Self {
unreachable!("Infallible uninhabited");
}
}
impl<T> DecodeError<T> for ParsePubkeyError {
fn type_of() -> &'static str {
"ParsePubkeyError"
}
}
impl FromStr for Pubkey {
type Err = ParsePubkeyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > MAX_BASE58_LEN {
return Err(ParsePubkeyError::WrongSize);
}
let mut bytes = [0; PUBKEY_BYTES];
let decoded_size = bs58::decode(s)
.onto(&mut bytes)
.map_err(|_| ParsePubkeyError::Invalid)?;
if decoded_size != mem::size_of::<Pubkey>() {
Err(ParsePubkeyError::WrongSize)
} else {
Ok(Pubkey(bytes))
}
}
}
impl From<&Pubkey> for Pubkey {
#[inline]
fn from(value: &Pubkey) -> Self {
*value
}
}
impl From<[u8; 32]> for Pubkey {
#[inline]
fn from(from: [u8; 32]) -> Self {
Self(from)
}
}
impl TryFrom<&[u8]> for Pubkey {
type Error = array::TryFromSliceError;
#[inline]
fn try_from(pubkey: &[u8]) -> Result<Self, Self::Error> {
<[u8; 32]>::try_from(pubkey).map(Self::from)
}
}
#[cfg(any(feature = "std", target_arch = "wasm32"))]
impl TryFrom<Vec<u8>> for Pubkey {
type Error = Vec<u8>;
#[inline]
fn try_from(pubkey: Vec<u8>) -> Result<Self, Self::Error> {
<[u8; 32]>::try_from(pubkey).map(Self::from)
}
}
impl TryFrom<&str> for Pubkey {
type Error = ParsePubkeyError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Pubkey::from_str(s)
}
}
#[cfg(any(target_os = "solana", feature = "curve25519"))]
#[allow(clippy::used_underscore_binding)]
pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
#[cfg(not(target_os = "solana"))]
{
let Ok(compressed_edwards_y) =
curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
else {
return false;
};
compressed_edwards_y.decompress().is_some()
}
#[cfg(target_os = "solana")]
unimplemented!();
}
impl Pubkey {
pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
Self(pubkey_array)
}
pub const fn from_str_const(s: &str) -> Self {
let id_array = five8_const::decode_32_const(s);
Pubkey::new_from_array(id_array)
}
pub fn new_unique() -> Self {
use clone_solana_atomic_u64::AtomicU64;
static I: AtomicU64 = AtomicU64::new(1);
type T = u32;
const COUNTER_BYTES: usize = size_of::<T>();
let mut b = [0u8; PUBKEY_BYTES];
let mut i = I.fetch_add(1) as T;
b[0..COUNTER_BYTES].copy_from_slice(&i.to_be_bytes());
#[cfg(any(feature = "std", target_arch = "wasm32"))]
{
extern crate std;
let mut hash = std::hash::DefaultHasher::new();
for slice in b[COUNTER_BYTES..].chunks_mut(COUNTER_BYTES) {
hash.write_u32(i);
i += 1;
slice.copy_from_slice(&hash.finish().to_ne_bytes()[0..COUNTER_BYTES]);
}
}
#[cfg(not(any(feature = "std", target_arch = "wasm32")))]
{
for b in b[COUNTER_BYTES..].iter_mut() {
*b = (i & 0xFF) as u8;
}
}
Self::from(b)
}
#[cfg(any(target_os = "solana", feature = "sha2"))]
pub fn create_with_seed(
base: &Pubkey,
seed: &str,
owner: &Pubkey,
) -> Result<Pubkey, PubkeyError> {
if seed.len() > MAX_SEED_LEN {
return Err(PubkeyError::MaxSeedLengthExceeded);
}
let owner = owner.as_ref();
if owner.len() >= PDA_MARKER.len() {
let slice = &owner[owner.len() - PDA_MARKER.len()..];
if slice == PDA_MARKER {
return Err(PubkeyError::IllegalOwner);
}
}
let hash = clone_solana_sha256_hasher::hashv(&[base.as_ref(), seed.as_ref(), owner]);
Ok(Pubkey::from(hash.to_bytes()))
}
#[cfg(any(target_os = "solana", feature = "curve25519"))]
pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
Self::try_find_program_address(seeds, program_id)
.unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
}
#[cfg(any(target_os = "solana", feature = "curve25519"))]
#[allow(clippy::same_item_push)]
pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
#[cfg(not(target_os = "solana"))]
{
let mut bump_seed = [u8::MAX];
for _ in 0..u8::MAX {
{
let mut seeds_with_bump = seeds.to_vec();
seeds_with_bump.push(&bump_seed);
match Self::create_program_address(&seeds_with_bump, program_id) {
Ok(address) => return Some((address, bump_seed[0])),
Err(PubkeyError::InvalidSeeds) => (),
_ => break,
}
}
bump_seed[0] -= 1;
}
None
}
#[cfg(target_os = "solana")]
{
let mut bytes = [0; 32];
let mut bump_seed = u8::MAX;
let result = unsafe {
crate::syscalls::sol_try_find_program_address(
seeds as *const _ as *const u8,
seeds.len() as u64,
program_id as *const _ as *const u8,
&mut bytes as *mut _ as *mut u8,
&mut bump_seed as *mut _ as *mut u8,
)
};
match result {
SUCCESS => Some((Pubkey::from(bytes), bump_seed)),
_ => None,
}
}
}
#[cfg(any(target_os = "solana", feature = "curve25519"))]
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError> {
if seeds.len() > MAX_SEEDS {
return Err(PubkeyError::MaxSeedLengthExceeded);
}
for seed in seeds.iter() {
if seed.len() > MAX_SEED_LEN {
return Err(PubkeyError::MaxSeedLengthExceeded);
}
}
#[cfg(not(target_os = "solana"))]
{
let mut hasher = clone_solana_sha256_hasher::Hasher::default();
for seed in seeds.iter() {
hasher.hash(seed);
}
hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
let hash = hasher.result();
if bytes_are_curve_point(hash) {
return Err(PubkeyError::InvalidSeeds);
}
Ok(Pubkey::from(hash.to_bytes()))
}
#[cfg(target_os = "solana")]
{
let mut bytes = [0; 32];
let result = unsafe {
crate::syscalls::sol_create_program_address(
seeds as *const _ as *const u8,
seeds.len() as u64,
program_id as *const _ as *const u8,
&mut bytes as *mut _ as *mut u8,
)
};
match result {
SUCCESS => Ok(Pubkey::from(bytes)),
_ => Err(result.into()),
}
}
}
pub const fn to_bytes(self) -> [u8; 32] {
self.0
}
#[inline(always)]
pub const fn as_array(&self) -> &[u8; 32] {
&self.0
}
#[cfg(any(target_os = "solana", feature = "curve25519"))]
pub fn is_on_curve(&self) -> bool {
bytes_are_curve_point(self)
}
pub fn log(&self) {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::sol_log_pubkey(self.as_ref() as *const _ as *const u8)
};
#[cfg(all(not(target_os = "solana"), feature = "std"))]
std::println!("{}", std::string::ToString::to_string(&self));
}
}
impl AsRef<[u8]> for Pubkey {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl AsMut<[u8]> for Pubkey {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0[..]
}
}
fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result {
let mut out = [0u8; MAX_BASE58_LEN];
let out_slice: &mut [u8] = &mut out;
let len = bs58::encode(p.0).onto(out_slice).unwrap();
let as_str = from_utf8(&out[..len]).unwrap();
f.write_str(as_str)
}
impl fmt::Debug for Pubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write_as_base58(f, self)
}
}
impl fmt::Display for Pubkey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write_as_base58(f, self)
}
}
#[cfg(feature = "borsh")]
impl borsh0_10::de::BorshDeserialize for Pubkey {
fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
reader: &mut R,
) -> Result<Self, borsh0_10::maybestd::io::Error> {
Ok(Self(borsh0_10::BorshDeserialize::deserialize_reader(
reader,
)?))
}
}
#[cfg(feature = "borsh")]
macro_rules! impl_borsh_schema {
($borsh:ident) => {
impl $borsh::BorshSchema for Pubkey
where
[u8; 32]: $borsh::BorshSchema,
{
fn declaration() -> $borsh::schema::Declaration {
std::string::String::from("Pubkey")
}
fn add_definitions_recursively(
definitions: &mut $borsh::maybestd::collections::HashMap<
$borsh::schema::Declaration,
$borsh::schema::Definition,
>,
) {
let fields = $borsh::schema::Fields::UnnamedFields(<[_]>::into_vec(
$borsh::maybestd::boxed::Box::new([
<[u8; 32] as $borsh::BorshSchema>::declaration(),
]),
));
let definition = $borsh::schema::Definition::Struct { fields };
<Self as $borsh::BorshSchema>::add_definition(
<Self as $borsh::BorshSchema>::declaration(),
definition,
definitions,
);
<[u8; 32] as $borsh::BorshSchema>::add_definitions_recursively(definitions);
}
}
};
}
#[cfg(feature = "borsh")]
impl_borsh_schema!(borsh0_10);
#[cfg(feature = "borsh")]
macro_rules! impl_borsh_serialize {
($borsh:ident) => {
impl $borsh::ser::BorshSerialize for Pubkey {
fn serialize<W: $borsh::maybestd::io::Write>(
&self,
writer: &mut W,
) -> ::core::result::Result<(), $borsh::maybestd::io::Error> {
$borsh::BorshSerialize::serialize(&self.0, writer)?;
Ok(())
}
}
};
}
#[cfg(feature = "borsh")]
impl_borsh_serialize!(borsh0_10);
#[cfg(all(target_arch = "wasm32", feature = "curve25519"))]
fn js_value_to_seeds_vec(array_of_uint8_arrays: &[JsValue]) -> Result<Vec<Vec<u8>>, JsValue> {
let vec_vec_u8 = array_of_uint8_arrays
.iter()
.filter_map(|u8_array| {
u8_array
.dyn_ref::<Uint8Array>()
.map(|u8_array| u8_array.to_vec())
})
.collect::<Vec<_>>();
if vec_vec_u8.len() != array_of_uint8_arrays.len() {
Err("Invalid Array of Uint8Arrays".into())
} else {
Ok(vec_vec_u8)
}
}
#[cfg(target_arch = "wasm32")]
fn display_to_jsvalue<T: fmt::Display>(display: T) -> JsValue {
std::string::ToString::to_string(&display).into()
}
#[allow(non_snake_case)]
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
impl Pubkey {
#[wasm_bindgen(constructor)]
pub fn constructor(value: JsValue) -> Result<Pubkey, JsValue> {
if let Some(base58_str) = value.as_string() {
base58_str.parse::<Pubkey>().map_err(display_to_jsvalue)
} else if let Some(uint8_array) = value.dyn_ref::<Uint8Array>() {
Pubkey::try_from(uint8_array.to_vec())
.map_err(|err| JsValue::from(std::format!("Invalid Uint8Array pubkey: {err:?}")))
} else if let Some(array) = value.dyn_ref::<Array>() {
let mut bytes = std::vec![];
let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable");
for x in iterator {
let x = x?;
if let Some(n) = x.as_f64() {
if n >= 0. && n <= 255. {
bytes.push(n as u8);
continue;
}
}
return Err(std::format!("Invalid array argument: {:?}", x).into());
}
Pubkey::try_from(bytes)
.map_err(|err| JsValue::from(std::format!("Invalid Array pubkey: {err:?}")))
} else if value.is_undefined() {
Ok(Pubkey::default())
} else {
Err("Unsupported argument".into())
}
}
pub fn toString(&self) -> std::string::String {
std::string::ToString::to_string(self)
}
#[cfg(feature = "curve25519")]
pub fn isOnCurve(&self) -> bool {
self.is_on_curve()
}
pub fn equals(&self, other: &Pubkey) -> bool {
self == other
}
pub fn toBytes(&self) -> std::boxed::Box<[u8]> {
self.0.clone().into()
}
#[cfg(feature = "sha2")]
pub fn createWithSeed(base: &Pubkey, seed: &str, owner: &Pubkey) -> Result<Pubkey, JsValue> {
Pubkey::create_with_seed(base, seed, owner).map_err(display_to_jsvalue)
}
#[cfg(feature = "curve25519")]
pub fn createProgramAddress(
seeds: std::boxed::Box<[JsValue]>,
program_id: &Pubkey,
) -> Result<Pubkey, JsValue> {
let seeds_vec = js_value_to_seeds_vec(&seeds)?;
let seeds_slice = seeds_vec
.iter()
.map(|seed| seed.as_slice())
.collect::<Vec<_>>();
Pubkey::create_program_address(seeds_slice.as_slice(), program_id)
.map_err(display_to_jsvalue)
}
#[cfg(feature = "curve25519")]
pub fn findProgramAddress(
seeds: std::boxed::Box<[JsValue]>,
program_id: &Pubkey,
) -> Result<JsValue, JsValue> {
let seeds_vec = js_value_to_seeds_vec(&seeds)?;
let seeds_slice = seeds_vec
.iter()
.map(|seed| seed.as_slice())
.collect::<Vec<_>>();
let (address, bump_seed) = Pubkey::find_program_address(seeds_slice.as_slice(), program_id);
let result = Array::new_with_length(2);
result.set(0, address.into());
result.set(1, bump_seed.into());
Ok(result.into())
}
}
#[macro_export]
macro_rules! declare_id {
($address:expr) => {
pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
pub fn check_id(id: &$crate::Pubkey) -> bool {
id == &ID
}
pub const fn id() -> $crate::Pubkey {
ID
}
#[cfg(test)]
#[test]
fn test_id() {
assert!(check_id(&id()));
}
};
}
#[macro_export]
macro_rules! declare_deprecated_id {
($address:expr) => {
pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
#[deprecated()]
pub fn check_id(id: &$crate::Pubkey) -> bool {
id == &ID
}
#[deprecated()]
pub const fn id() -> $crate::Pubkey {
ID
}
#[cfg(test)]
#[test]
#[allow(deprecated)]
fn test_id() {
assert!(check_id(&id()));
}
};
}
#[macro_export]
macro_rules! pubkey {
($input:literal) => {
$crate::Pubkey::from_str_const($input)
};
}
#[cfg(all(feature = "rand", not(target_os = "solana")))]
pub fn new_rand() -> Pubkey {
Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>())
}
#[cfg(test)]
mod tests {
use {super::*, strum::IntoEnumIterator};
#[test]
fn test_new_unique() {
assert!(Pubkey::new_unique() != Pubkey::new_unique());
}
#[test]
fn pubkey_fromstr() {
let pubkey = Pubkey::new_unique();
let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
assert_eq!(
pubkey_base58_str.parse::<Pubkey>(),
Err(ParsePubkeyError::WrongSize)
);
pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
assert_eq!(
pubkey_base58_str.parse::<Pubkey>(),
Err(ParsePubkeyError::WrongSize)
);
let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
pubkey_base58_str.replace_range(..1, "I");
assert_eq!(
pubkey_base58_str.parse::<Pubkey>(),
Err(ParsePubkeyError::Invalid)
);
let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
too_long.push('1');
assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
}
#[test]
fn test_create_with_seed() {
assert!(
Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
);
assert_eq!(
Pubkey::create_with_seed(
&Pubkey::new_unique(),
from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
&Pubkey::new_unique()
),
Err(PubkeyError::MaxSeedLengthExceeded)
);
assert!(Pubkey::create_with_seed(
&Pubkey::new_unique(),
"\
\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
",
&Pubkey::new_unique()
)
.is_ok());
assert_eq!(
Pubkey::create_with_seed(
&Pubkey::new_unique(),
"\
x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
",
&Pubkey::new_unique()
),
Err(PubkeyError::MaxSeedLengthExceeded)
);
assert!(Pubkey::create_with_seed(
&Pubkey::new_unique(),
from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
&Pubkey::new_unique(),
)
.is_ok());
assert!(
Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
);
assert_eq!(
Pubkey::create_with_seed(
&Pubkey::default(),
"limber chicken: 4/45",
&Pubkey::default(),
),
Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
.parse()
.unwrap())
);
}
#[test]
fn test_create_program_address() {
let exceeded_seed = &[127; MAX_SEED_LEN + 1];
let max_seed = &[0; MAX_SEED_LEN];
let exceeded_seeds: &[&[u8]] = &[
&[1],
&[2],
&[3],
&[4],
&[5],
&[6],
&[7],
&[8],
&[9],
&[10],
&[11],
&[12],
&[13],
&[14],
&[15],
&[16],
&[17],
];
let max_seeds: &[&[u8]] = &[
&[1],
&[2],
&[3],
&[4],
&[5],
&[6],
&[7],
&[8],
&[9],
&[10],
&[11],
&[12],
&[13],
&[14],
&[15],
&[16],
];
let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
assert_eq!(
Pubkey::create_program_address(&[exceeded_seed], &program_id),
Err(PubkeyError::MaxSeedLengthExceeded)
);
assert_eq!(
Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
Err(PubkeyError::MaxSeedLengthExceeded)
);
assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
assert_eq!(
Pubkey::create_program_address(exceeded_seeds, &program_id),
Err(PubkeyError::MaxSeedLengthExceeded)
);
assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
assert_eq!(
Pubkey::create_program_address(&[b"", &[1]], &program_id),
Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
.parse()
.unwrap())
);
assert_eq!(
Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
.parse()
.unwrap())
);
assert_eq!(
Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
.parse()
.unwrap())
);
assert_eq!(
Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
.parse()
.unwrap())
);
assert_ne!(
Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
);
}
#[test]
fn test_pubkey_off_curve() {
let mut addresses = std::vec![];
for _ in 0..1_000 {
let program_id = Pubkey::new_unique();
let bytes1 = rand::random::<[u8; 10]>();
let bytes2 = rand::random::<[u8; 32]>();
if let Ok(program_address) =
Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
{
assert!(!program_address.is_on_curve());
assert!(!addresses.contains(&program_address));
addresses.push(program_address);
}
}
}
#[test]
fn test_find_program_address() {
for _ in 0..1_000 {
let program_id = Pubkey::new_unique();
let (address, bump_seed) =
Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
assert_eq!(
address,
Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
.unwrap()
);
}
}
fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
let key = Pubkey::new_unique();
let owner = Pubkey::default();
let mut to_fake = owner.to_bytes().to_vec();
to_fake.extend_from_slice(marker);
let seed = from_utf8(&to_fake[..to_fake.len() - 32]).expect("not utf8");
let base = &Pubkey::try_from(&to_fake[to_fake.len() - 32..]).unwrap();
Pubkey::create_with_seed(&key, seed, base)
}
#[test]
fn test_create_with_seed_rejects_illegal_owner() {
assert_eq!(
pubkey_from_seed_by_marker(PDA_MARKER),
Err(PubkeyError::IllegalOwner)
);
assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
}
#[test]
fn test_pubkey_error_from_primitive_exhaustive() {
for variant in PubkeyError::iter() {
let variant_i64 = variant.clone() as i64;
assert_eq!(
PubkeyError::from_repr(variant_i64 as usize),
PubkeyError::from_i64(variant_i64)
);
assert_eq!(PubkeyError::from(variant_i64 as u64), variant);
}
}
#[test]
fn test_parse_pubkey_error_from_primitive_exhaustive() {
for variant in ParsePubkeyError::iter() {
let variant_i64 = variant as i64;
assert_eq!(
ParsePubkeyError::from_repr(variant_i64 as usize),
ParsePubkeyError::from_i64(variant_i64)
);
}
}
#[test]
fn test_pubkey_macro() {
const PK: Pubkey = Pubkey::from_str_const("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq");
assert_eq!(pubkey!("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"), PK);
assert_eq!(
Pubkey::from_str("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq").unwrap(),
PK
);
}
#[test]
fn test_as_array() {
let bytes = [1u8; 32];
let key = Pubkey::from(bytes);
assert_eq!(key.as_array(), &bytes);
assert_eq!(key.as_array(), &key.to_bytes());
assert_eq!(key.as_array().as_ptr(), key.0.as_ptr());
}
}