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