Skip to main content

hopper_runtime/
address.rs

1//! Hopper-owned address type for Solana programs.
2//!
3//! `Address` is a 32-byte public key with `#[repr(transparent)]` layout
4//! over `[u8; 32]`. This enables zero-cost reference casting from any
5//! backend address type that shares the same representation.
6//!
7//! Hopper owns the canonical type. Backend-specific conversions live in
8//! compatibility modules so the core identity remains framework-owned.
9
10// ── Constants ────────────────────────────────────────────────────────
11
12/// Number of bytes in an address.
13pub const ADDRESS_BYTES: usize = 32;
14
15/// Maximum length of a single PDA seed.
16pub const MAX_SEED_LEN: usize = 32;
17
18/// Maximum number of seeds for PDA derivation.
19pub const MAX_SEEDS: usize = 16;
20
21/// Marker appended to PDA hash inputs: `"ProgramDerivedAddress"`.
22pub const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
23
24// ── Address ──────────────────────────────────────────────────────────
25
26/// A Solana address (public key): 32 bytes, transparent layout.
27///
28/// This is part of the Hopper runtime type surface. Backends convert
29/// to and from this type at system boundaries.
30#[repr(transparent)]
31#[derive(Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)]
32pub struct Address(pub(crate) [u8; 32]);
33
34// SAFETY: `Address` is `#[repr(transparent)]` over `[u8; 32]`, so it
35// inherits the POD contract of its inner type exactly:
36// - Every byte pattern is valid (no niches).
37// - Alignment is 1 (inherits `[u8; 32]`'s alignment).
38// - No padding, no drop glue, no interior pointers.
39// Under `feature = "hopper-native-backend"` (default) `hopper_native::Pod`
40// is a sub-trait of `bytemuck::Pod + bytemuck::Zeroable`, so we
41// satisfy those two as well with the same justification.
42#[cfg(feature = "hopper-native-backend")]
43unsafe impl bytemuck::Zeroable for Address {}
44#[cfg(feature = "hopper-native-backend")]
45unsafe impl bytemuck::Pod for Address {}
46
47unsafe impl crate::pod::Pod for Address {}
48// Audit Step 5 seal: Hopper-authored primitive.
49unsafe impl crate::zerocopy::__sealed::HopperZeroCopySealed for Address {}
50
51impl Address {
52    /// Construct from a raw byte array.
53    #[inline(always)]
54    pub const fn new(bytes: [u8; 32]) -> Self {
55        Self(bytes)
56    }
57
58    /// Construct from a raw byte array (alias for compatibility).
59    #[inline(always)]
60    pub const fn new_from_array(bytes: [u8; 32]) -> Self {
61        Self(bytes)
62    }
63
64    /// Return the underlying bytes by value.
65    #[inline(always)]
66    pub const fn to_bytes(&self) -> [u8; 32] {
67        self.0
68    }
69
70    /// Borrow the underlying byte array.
71    #[inline(always)]
72    pub const fn as_array(&self) -> &[u8; 32] {
73        &self.0
74    }
75
76    /// Borrow the underlying bytes.
77    #[inline(always)]
78    pub const fn as_bytes(&self) -> &[u8; 32] {
79        &self.0
80    }
81
82    /// Find a program-derived address and its bump seed.
83    ///
84    /// Iterates bump values from 255 to 0, returning the first valid PDA.
85    /// Only available on-chain (`target_os = "solana"`).
86    #[cfg(target_os = "solana")]
87    pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
88        crate::compat::find_program_address(seeds, program_id)
89    }
90
91    /// Create a program-derived address from seeds.
92    ///
93    /// This is the cheaper PDA path when the bump is already known.
94    #[cfg(target_os = "solana")]
95    pub fn create_program_address(
96        seeds: &[&[u8]],
97        program_id: &Address,
98    ) -> Result<Address, crate::ProgramError> {
99        crate::compat::create_program_address(seeds, program_id)
100    }
101}
102
103// ── Trait implementations ────────────────────────────────────────────
104
105impl From<[u8; 32]> for Address {
106    #[inline(always)]
107    fn from(bytes: [u8; 32]) -> Self {
108        Self(bytes)
109    }
110}
111
112impl From<Address> for [u8; 32] {
113    #[inline(always)]
114    fn from(addr: Address) -> [u8; 32] {
115        addr.0
116    }
117}
118
119impl TryFrom<&[u8]> for Address {
120    type Error = core::array::TryFromSliceError;
121
122    #[inline]
123    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
124        let arr: [u8; 32] = slice.try_into()?;
125        Ok(Self(arr))
126    }
127}
128
129impl AsRef<[u8]> for Address {
130    #[inline(always)]
131    fn as_ref(&self) -> &[u8] {
132        &self.0
133    }
134}
135
136impl AsMut<[u8]> for Address {
137    #[inline(always)]
138    fn as_mut(&mut self) -> &mut [u8] {
139        &mut self.0
140    }
141}
142
143impl AsRef<[u8; 32]> for Address {
144    #[inline(always)]
145    fn as_ref(&self) -> &[u8; 32] {
146        &self.0
147    }
148}
149
150impl core::hash::Hash for Address {
151    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
152        self.0.hash(state);
153    }
154}
155
156impl core::fmt::Debug for Address {
157    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
158        write!(f, "Address({:?})", &self.0[..4])
159    }
160}
161
162impl core::fmt::Display for Address {
163    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
164        // Hex-encoded short form for no_std Display
165        for byte in &self.0[..4] {
166            write!(f, "{byte:02x}")?;
167        }
168        write!(f, "..")
169    }
170}
171
172// ── Fast equality ────────────────────────────────────────────────────
173
174/// Fast address equality using 4 x u64 comparison.
175#[inline(always)]
176pub fn address_eq(a: &Address, b: &Address) -> bool {
177    let a_ptr = a.0.as_ptr() as *const u64;
178    let b_ptr = b.0.as_ptr() as *const u64;
179    // SAFETY: Address is 32 bytes = 4 x u64. Use unaligned reads because
180    // Address itself is only byte-aligned.
181    unsafe {
182        core::ptr::read_unaligned(a_ptr) == core::ptr::read_unaligned(b_ptr)
183            && core::ptr::read_unaligned(a_ptr.add(1)) == core::ptr::read_unaligned(b_ptr.add(1))
184            && core::ptr::read_unaligned(a_ptr.add(2)) == core::ptr::read_unaligned(b_ptr.add(2))
185            && core::ptr::read_unaligned(a_ptr.add(3)) == core::ptr::read_unaligned(b_ptr.add(3))
186    }
187}