use core::marker::PhantomData;
#[repr(transparent)]
pub struct TypedAddress<T> {
bytes: [u8; 32],
_phantom: PhantomData<T>,
}
impl<T> Clone for TypedAddress<T> {
#[inline(always)]
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for TypedAddress<T> {}
impl<T> core::fmt::Debug for TypedAddress<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "TypedAddress({:02x?})", &self.bytes[..4])
}
}
const _: () = assert!(core::mem::size_of::<TypedAddress<()>>() == 32);
const _: () = assert!(core::mem::align_of::<TypedAddress<()>>() == 1);
#[cfg(feature = "hopper-native-backend")]
unsafe impl<T: 'static> ::hopper_runtime::__hopper_native::bytemuck::Zeroable for TypedAddress<T> {}
#[cfg(feature = "hopper-native-backend")]
unsafe impl<T: Copy + 'static> ::hopper_runtime::__hopper_native::bytemuck::Pod
for TypedAddress<T>
{
}
unsafe impl<T: Copy + 'static> crate::account::Pod for TypedAddress<T> {}
unsafe impl<T: Copy + 'static> ::hopper_runtime::__sealed::HopperZeroCopySealed
for TypedAddress<T>
{
}
impl<T> crate::account::FixedLayout for TypedAddress<T> {
const SIZE: usize = 32;
}
impl<T> TypedAddress<T> {
#[inline(always)]
pub const fn new(bytes: [u8; 32]) -> Self {
Self {
bytes,
_phantom: PhantomData,
}
}
#[inline(always)]
pub fn from_slice(slice: &[u8; 32]) -> Self {
Self::new(*slice)
}
#[inline(always)]
pub fn from_account(account: &hopper_runtime::AccountView) -> Self {
let bytes =
unsafe { *(account.address() as *const hopper_runtime::Address as *const [u8; 32]) };
Self::new(bytes)
}
#[inline(always)]
pub const fn zeroed() -> Self {
Self::new([0u8; 32])
}
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.bytes
}
#[inline(always)]
pub fn eq_bytes(&self, other: &[u8; 32]) -> bool {
crate::check::keys_eq_fast(&self.bytes, other)
}
#[inline(always)]
pub fn eq_account(&self, account: &hopper_runtime::AccountView) -> bool {
let addr =
unsafe { &*(account.address() as *const hopper_runtime::Address as *const [u8; 32]) };
crate::check::keys_eq_fast(&self.bytes, addr)
}
#[inline(always)]
pub fn require_eq_account(
&self,
account: &hopper_runtime::AccountView,
) -> Result<(), hopper_runtime::error::ProgramError> {
if self.eq_account(account) {
Ok(())
} else {
Err(hopper_runtime::error::ProgramError::InvalidAccountData)
}
}
#[inline(always)]
pub fn is_zero(&self) -> bool {
crate::check::is_zero_address(&self.bytes)
}
#[inline(always)]
pub const fn cast<U>(self) -> TypedAddress<U> {
TypedAddress {
bytes: self.bytes,
_phantom: PhantomData,
}
}
#[inline(always)]
pub const fn untyped(self) -> UntypedAddress {
UntypedAddress(self.bytes)
}
}
impl<T> PartialEq for TypedAddress<T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
crate::check::keys_eq_fast(&self.bytes, &other.bytes)
}
}
impl<T> Eq for TypedAddress<T> {}
impl<T> PartialEq<[u8; 32]> for TypedAddress<T> {
#[inline(always)]
fn eq(&self, other: &[u8; 32]) -> bool {
crate::check::keys_eq_fast(&self.bytes, other)
}
}
impl<T> AsRef<[u8; 32]> for TypedAddress<T> {
#[inline(always)]
fn as_ref(&self) -> &[u8; 32] {
&self.bytes
}
}
impl<T> AsRef<[u8]> for TypedAddress<T> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
&self.bytes
}
}
impl<T> From<[u8; 32]> for TypedAddress<T> {
#[inline(always)]
fn from(bytes: [u8; 32]) -> Self {
Self::new(bytes)
}
}
impl<T> From<TypedAddress<T>> for [u8; 32] {
#[inline(always)]
fn from(addr: TypedAddress<T>) -> [u8; 32] {
addr.bytes
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct UntypedAddress(pub [u8; 32]);
const _: () = assert!(core::mem::size_of::<UntypedAddress>() == 32);
const _: () = assert!(core::mem::align_of::<UntypedAddress>() == 1);
#[cfg(feature = "hopper-native-backend")]
unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Zeroable for UntypedAddress {}
#[cfg(feature = "hopper-native-backend")]
unsafe impl ::hopper_runtime::__hopper_native::bytemuck::Pod for UntypedAddress {}
unsafe impl crate::account::Pod for UntypedAddress {}
unsafe impl ::hopper_runtime::__sealed::HopperZeroCopySealed for UntypedAddress {}
impl crate::account::FixedLayout for UntypedAddress {
const SIZE: usize = 32;
}
impl UntypedAddress {
#[inline(always)]
pub const fn typed<T>(self) -> TypedAddress<T> {
TypedAddress::new(self.0)
}
#[inline(always)]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl AsRef<[u8; 32]> for UntypedAddress {
#[inline(always)]
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl From<[u8; 32]> for UntypedAddress {
#[inline(always)]
fn from(bytes: [u8; 32]) -> Self {
Self(bytes)
}
}
#[derive(Clone, Copy)]
pub struct Authority;
#[derive(Clone, Copy)]
pub struct Mint;
#[derive(Clone, Copy)]
pub struct TokenAccount;
pub type Token = TokenAccount;
#[derive(Clone, Copy)]
pub struct Program;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn typed_address_size_and_align() {
assert_eq!(core::mem::size_of::<TypedAddress<Authority>>(), 32);
assert_eq!(core::mem::align_of::<TypedAddress<Authority>>(), 1);
assert_eq!(core::mem::size_of::<TypedAddress<Mint>>(), 32);
assert_eq!(core::mem::size_of::<UntypedAddress>(), 32);
}
#[test]
fn typed_address_equality() {
let bytes = [42u8; 32];
let a: TypedAddress<Authority> = TypedAddress::new(bytes);
let b: TypedAddress<Authority> = TypedAddress::new(bytes);
assert_eq!(a, b);
assert!(a.eq_bytes(&bytes));
}
#[test]
fn typed_address_zero_check() {
let zero: TypedAddress<Mint> = TypedAddress::new([0u8; 32]);
assert!(zero.is_zero());
let nonzero: TypedAddress<Mint> = TypedAddress::new([1u8; 32]);
assert!(!nonzero.is_zero());
}
#[test]
fn typed_address_cast() {
let mint_addr: TypedAddress<Mint> = TypedAddress::new([7u8; 32]);
let _generic: TypedAddress<()> = mint_addr.cast();
}
#[test]
fn typed_untyped_roundtrip() {
let bytes = [99u8; 32];
let typed: TypedAddress<TokenAccount> = TypedAddress::new(bytes);
let untyped = typed.untyped();
let retyped: TypedAddress<TokenAccount> = untyped.typed();
assert_eq!(typed, retyped);
}
}