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]`. Hopper owns the canonical public-key type across the
5//! runtime.
6
7// ── Constants ────────────────────────────────────────────────────────
8
9/// Number of bytes in an address.
10pub const ADDRESS_BYTES: usize = 32;
11
12/// Maximum length of a single PDA seed.
13pub const MAX_SEED_LEN: usize = 32;
14
15/// Maximum number of seeds for PDA derivation.
16pub const MAX_SEEDS: usize = 16;
17
18/// Marker appended to PDA hash inputs: `"ProgramDerivedAddress"`.
19pub const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
20
21// ── Address ──────────────────────────────────────────────────────────
22
23/// A Solana address (public key): 32 bytes, transparent layout.
24///
25/// This is part of the Hopper runtime type surface.
26///
27/// `PartialEq`/`Eq` are implemented manually (see below) so every
28/// `Address == Address` in the runtime and in user programs compiles
29/// to the 4 x u64 word compare in [`address_eq`] rather than a
30/// bytewise loop. `PartialOrd`/`Ord` stay derived: word-equality and
31/// byte-equality decide the same pairs equal, so the derived ordering
32/// remains consistent with the manual equality.
33#[repr(transparent)]
34#[derive(Clone, Copy, Default, PartialOrd, Ord)]
35pub struct Address(pub(crate) [u8; 32]);
36
37// SAFETY: `Address` is `#[repr(transparent)]` over `[u8; 32]`, so it
38// inherits the POD contract of its inner type exactly:
39// - Every byte pattern is valid (no niches).
40// - Alignment is 1 (inherits `[u8; 32]`'s alignment).
41// - No padding, no drop glue, no interior pointers.
42unsafe impl crate::pod::Zeroable for Address {}
43unsafe impl crate::pod::Pod for Address {}
44// This framework-owned wire primitive is part of the sealed zero-copy set.
45unsafe impl crate::zerocopy::__sealed::HopperZeroCopySealed for Address {}
46
47impl Address {
48    /// Construct from a raw byte array.
49    #[inline(always)]
50    pub const fn new(bytes: [u8; 32]) -> Self {
51        Self(bytes)
52    }
53
54    /// Construct from a raw byte array (alias for compatibility).
55    #[inline(always)]
56    pub const fn new_from_array(bytes: [u8; 32]) -> Self {
57        Self(bytes)
58    }
59
60    /// Return the underlying bytes by value.
61    #[inline(always)]
62    pub const fn to_bytes(&self) -> [u8; 32] {
63        self.0
64    }
65
66    /// Borrow the underlying byte array.
67    #[inline(always)]
68    pub const fn as_array(&self) -> &[u8; 32] {
69        &self.0
70    }
71
72    /// Borrow the underlying bytes.
73    #[inline(always)]
74    pub const fn as_bytes(&self) -> &[u8; 32] {
75        &self.0
76    }
77
78    /// Find a program-derived address and its bump seed.
79    ///
80    /// Iterates bump values from 255 to 0, returning the first valid PDA.
81    /// Only available on-chain (`target_os = "solana"`).
82    #[cfg(target_os = "solana")]
83    pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
84        crate::native_boundary::find_program_address(seeds, program_id)
85    }
86
87    /// Create a program-derived address from seeds.
88    ///
89    /// This is the cheaper PDA path when the bump is already known.
90    #[cfg(target_os = "solana")]
91    pub fn create_program_address(
92        seeds: &[&[u8]],
93        program_id: &Address,
94    ) -> Result<Address, crate::ProgramError> {
95        crate::native_boundary::create_program_address(seeds, program_id)
96    }
97}
98
99// ── Trait implementations ────────────────────────────────────────────
100
101impl From<[u8; 32]> for Address {
102    #[inline(always)]
103    fn from(bytes: [u8; 32]) -> Self {
104        Self(bytes)
105    }
106}
107
108impl From<Address> for [u8; 32] {
109    #[inline(always)]
110    fn from(addr: Address) -> [u8; 32] {
111        addr.0
112    }
113}
114
115impl TryFrom<&[u8]> for Address {
116    type Error = core::array::TryFromSliceError;
117
118    #[inline]
119    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
120        let arr: [u8; 32] = slice.try_into()?;
121        Ok(Self(arr))
122    }
123}
124
125impl AsRef<[u8]> for Address {
126    #[inline(always)]
127    fn as_ref(&self) -> &[u8] {
128        &self.0
129    }
130}
131
132impl AsMut<[u8]> for Address {
133    #[inline(always)]
134    fn as_mut(&mut self) -> &mut [u8] {
135        &mut self.0
136    }
137}
138
139impl AsRef<[u8; 32]> for Address {
140    #[inline(always)]
141    fn as_ref(&self) -> &[u8; 32] {
142        &self.0
143    }
144}
145
146impl PartialEq for Address {
147    /// Word-compare equality: delegates to [`address_eq`] so the
148    /// `==` operator is exactly as fast as the free function.
149    #[inline(always)]
150    fn eq(&self, other: &Self) -> bool {
151        address_eq(self, other)
152    }
153}
154
155// Word-equality is an equivalence relation: it decides equal exactly
156// when all 32 bytes match, same as the previously-derived impl.
157impl Eq for Address {}
158
159impl core::hash::Hash for Address {
160    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
161        self.0.hash(state);
162    }
163}
164
165impl core::fmt::Debug for Address {
166    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
167        write!(f, "Address({:?})", &self.0[..4])
168    }
169}
170
171impl core::fmt::Display for Address {
172    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
173        // Hex-encoded short form for no_std Display
174        for byte in &self.0[..4] {
175            write!(f, "{byte:02x}")?;
176        }
177        write!(f, "..")
178    }
179}
180
181// ── Fast equality ────────────────────────────────────────────────────
182
183/// Fast address equality using 4 x u64 comparison.
184#[inline(always)]
185pub fn address_eq(a: &Address, b: &Address) -> bool {
186    keys_eq(&a.0, &b.0)
187}
188
189/// Fast 32-byte key equality using 4 x u64 word comparison.
190///
191/// Short-circuits on the first differing 8-byte chunk. Equivalent to
192/// `a == b` on the arrays but avoids the bytewise loop; this is the
193/// single word-compare body every key check in the runtime routes
194/// through ([`address_eq`], `Address == Address`, the
195/// `require_keys_eq!` / `require_keys_neq!` macros, and the token
196/// precondition helpers).
197#[inline(always)]
198pub fn keys_eq(a: &[u8; 32], b: &[u8; 32]) -> bool {
199    let a_ptr = a.as_ptr() as *const u64;
200    let b_ptr = b.as_ptr() as *const u64;
201    // SAFETY: Both inputs are [u8; 32] = 4 x u64, so all four reads are
202    // in bounds. Use unaligned reads because [u8; 32] is only
203    // byte-aligned.
204    unsafe {
205        core::ptr::read_unaligned(a_ptr) == core::ptr::read_unaligned(b_ptr)
206            && core::ptr::read_unaligned(a_ptr.add(1)) == core::ptr::read_unaligned(b_ptr.add(1))
207            && core::ptr::read_unaligned(a_ptr.add(2)) == core::ptr::read_unaligned(b_ptr.add(2))
208            && core::ptr::read_unaligned(a_ptr.add(3)) == core::ptr::read_unaligned(b_ptr.add(3))
209    }
210}
211
212/// Fast key equality between an arbitrary byte slice and a 32-byte key.
213///
214/// Returns `false` unless `a.len() == 32`; otherwise performs the same
215/// 4 x u64 word comparison as [`keys_eq`]. This serves call sites that
216/// hold a slice view into account data (e.g. an SPL token account's
217/// `owner` field at `data[32..64]`) and want to compare against an
218/// expected key without first copying 32 bytes into a temporary array.
219#[inline(always)]
220pub fn keys_eq_bytes(a: &[u8], b: &[u8; 32]) -> bool {
221    if a.len() != 32 {
222        return false;
223    }
224    let a_ptr = a.as_ptr() as *const u64;
225    let b_ptr = b.as_ptr() as *const u64;
226    // SAFETY: `a.len() == 32` was checked above and `b` is [u8; 32], so
227    // all four 8-byte reads on each side are in bounds. Use unaligned
228    // reads because both buffers are only byte-aligned.
229    unsafe {
230        core::ptr::read_unaligned(a_ptr) == core::ptr::read_unaligned(b_ptr)
231            && core::ptr::read_unaligned(a_ptr.add(1)) == core::ptr::read_unaligned(b_ptr.add(1))
232            && core::ptr::read_unaligned(a_ptr.add(2)) == core::ptr::read_unaligned(b_ptr.add(2))
233            && core::ptr::read_unaligned(a_ptr.add(3)) == core::ptr::read_unaligned(b_ptr.add(3))
234    }
235}
236
237/// Fast is-zero check: OR-fold the address's 4 u64 words.
238///
239/// Cheaper than comparing against an all-zero constant because only one
240/// operand is loaded. Useful for system-program / default-address
241/// checks (the system program id is the all-zero address).
242#[inline(always)]
243pub fn address_is_zero(a: &Address) -> bool {
244    let ptr = a.0.as_ptr() as *const u64;
245    // SAFETY: Address is 32 bytes = 4 x u64, so all four reads are in
246    // bounds. Use unaligned reads because Address is only byte-aligned.
247    unsafe {
248        (core::ptr::read_unaligned(ptr)
249            | core::ptr::read_unaligned(ptr.add(1))
250            | core::ptr::read_unaligned(ptr.add(2))
251            | core::ptr::read_unaligned(ptr.add(3)))
252            == 0
253    }
254}
255
256// ── Tests ────────────────────────────────────────────────────────────
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    /// Edge patterns exercised by every equality test below.
263    fn edge_patterns() -> [[u8; 32]; 6] {
264        let mut ramp = [0u8; 32];
265        for (i, b) in ramp.iter_mut().enumerate() {
266            *b = i as u8;
267        }
268        let mut last_hi = [0u8; 32];
269        last_hi[31] = 0xFF;
270        let mut first_hi = [0u8; 32];
271        first_hi[0] = 0xFF;
272        [
273            [0u8; 32],
274            [0xFFu8; 32],
275            ramp,
276            last_hi,
277            first_hi,
278            [0xA5u8; 32],
279        ]
280    }
281
282    #[test]
283    fn keys_eq_matches_bytewise_on_equal_arrays() {
284        for pat in edge_patterns() {
285            let copy = pat;
286            assert!(keys_eq(&pat, &copy));
287            assert!(address_eq(&Address::new(pat), &Address::new(pat)));
288            assert_eq!(Address::new(pat), Address::new(pat));
289        }
290    }
291
292    #[test]
293    fn keys_eq_detects_single_byte_difference_at_every_index() {
294        for base in edge_patterns() {
295            for idx in 0..32 {
296                let mut other = base;
297                other[idx] ^= 0x01;
298                assert!(!keys_eq(&base, &other), "missed diff at byte {idx}");
299                assert!(!address_eq(&Address::new(base), &Address::new(other)));
300                assert_ne!(Address::new(base), Address::new(other));
301                // Word compare must agree with bytewise compare.
302                assert_eq!(keys_eq(&base, &other), base == other);
303            }
304        }
305    }
306
307    #[test]
308    fn keys_eq_differs_only_in_last_byte() {
309        let a = [7u8; 32];
310        let mut b = a;
311        b[31] = 8;
312        assert!(!keys_eq(&a, &b));
313        assert_ne!(Address::new(a), Address::new(b));
314    }
315
316    #[test]
317    fn keys_eq_bytes_matches_slice_semantics() {
318        for pat in edge_patterns() {
319            assert!(keys_eq_bytes(&pat[..], &pat));
320            for idx in 0..32 {
321                let mut other = pat;
322                other[idx] ^= 0x80;
323                assert_eq!(keys_eq_bytes(&other[..], &pat), other == pat);
324            }
325        }
326        // Wrong-length slices never compare equal.
327        let key = [0u8; 32];
328        assert!(!keys_eq_bytes(&[], &key));
329        assert!(!keys_eq_bytes(&key[..31], &key));
330        let long = [0u8; 33];
331        assert!(!keys_eq_bytes(&long[..], &key));
332    }
333
334    #[test]
335    fn eq_is_consistent_with_derived_ord() {
336        use core::cmp::Ordering;
337        let patterns = edge_patterns();
338        for a in patterns {
339            for b in patterns {
340                let (aa, ab) = (Address::new(a), Address::new(b));
341                // Manual PartialEq must agree with derived Ord.
342                assert_eq!(aa == ab, aa.cmp(&ab) == Ordering::Equal);
343                // ...and with bytewise equality on the raw arrays.
344                assert_eq!(aa == ab, a == b);
345                for idx in 0..32 {
346                    let mut c = a;
347                    c[idx] = c[idx].wrapping_add(1);
348                    let ac = Address::new(c);
349                    assert_eq!(aa == ac, aa.cmp(&ac) == Ordering::Equal);
350                    assert_eq!(aa == ac, a == c);
351                }
352            }
353        }
354    }
355
356    #[test]
357    fn address_is_zero_or_fold() {
358        assert!(address_is_zero(&Address::new([0u8; 32])));
359        assert!(address_is_zero(&Address::default()));
360        for idx in 0..32 {
361            let mut bytes = [0u8; 32];
362            bytes[idx] = 1;
363            assert!(!address_is_zero(&Address::new(bytes)));
364        }
365        assert!(!address_is_zero(&Address::new([0xFFu8; 32])));
366    }
367}