1use crate::address::Address;
4use crate::error::ProgramError;
5use crate::AccountView;
6
7#[inline]
12pub fn create_program_address(
13 seeds: &[&[u8]],
14 program_id: &Address,
15) -> Result<Address, ProgramError> {
16 crate::native_boundary::create_program_address(seeds, program_id)
17}
18
19#[inline]
32pub fn find_program_address(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
33 #[cfg(target_os = "solana")]
34 {
35 crate::native_boundary::find_program_address(seeds, program_id)
36 }
37 #[cfg(not(target_os = "solana"))]
38 {
39 let _ = (seeds, program_id);
40 panic!(
41 "hopper: find_program_address requires the SVM sha256 syscall; \
42 run PDA paths under the SVM harness (target_os = \"solana\")"
43 );
44 }
45}
46
47#[inline(always)]
49pub fn derive(seeds: &[&[u8]], program_id: &Address) -> (Address, u8) {
50 find_program_address(seeds, program_id)
51}
52
53pub const fn const_program_address(program_id: &Address, seeds: &[&[u8]], bump: u8) -> Address {
71 let backend = hopper_native::address::Address::new_from_array(*program_id.as_array());
72 Address::new_from_array(
73 hopper_native::pda::program_address_const(seeds, bump, &backend).to_bytes(),
74 )
75}
76
77#[inline]
89pub fn verify_pda_address(
90 seeds: &[&[u8]],
91 program_id: &Address,
92 expected: &Address,
93) -> Result<(), ProgramError> {
94 #[cfg(target_os = "solana")]
95 {
96 hopper_native::pda::verify_program_address(
97 seeds,
98 crate::native_boundary::as_backend_address(program_id),
99 crate::native_boundary::as_backend_address(expected),
100 )
101 .map_err(ProgramError::from)
102 }
103 #[cfg(not(target_os = "solana"))]
104 {
105 let _ = (seeds, program_id, expected);
106 Err(ProgramError::InvalidSeeds)
107 }
108}
109
110#[cold]
118#[inline(never)]
119pub fn verify_pda_address_cold(
120 seeds: &[&[u8]],
121 program_id: &Address,
122 expected: &Address,
123) -> Result<(), ProgramError> {
124 verify_pda_address(seeds, program_id, expected)
125}
126
127#[inline]
130pub fn verify_pda_address_checked(
131 seeds: &[&[u8]],
132 program_id: &Address,
133 expected: &Address,
134) -> Result<(), ProgramError> {
135 let derived = create_program_address(seeds, program_id)?;
136 if crate::address::address_eq(&derived, expected) {
137 Ok(())
138 } else {
139 Err(ProgramError::InvalidSeeds)
140 }
141}
142
143#[inline]
154pub fn find_bump_for_address(
155 seeds: &[&[u8]],
156 program_id: &Address,
157 expected: &Address,
158) -> Result<u8, ProgramError> {
159 #[cfg(target_os = "solana")]
160 {
161 hopper_native::pda::find_bump_for_address(
162 seeds,
163 crate::native_boundary::as_backend_address(program_id),
164 crate::native_boundary::as_backend_address(expected),
165 )
166 .map_err(ProgramError::from)
167 }
168 #[cfg(not(target_os = "solana"))]
169 {
170 let _ = (seeds, program_id, expected);
171 Err(ProgramError::InvalidSeeds)
172 }
173}
174
175#[inline]
178pub fn find_canonical_bump_checked(
179 seeds: &[&[u8]],
180 program_id: &Address,
181 expected: &Address,
182) -> Result<u8, ProgramError> {
183 #[cfg(target_os = "solana")]
184 let (derived, bump) = hopper_native::pda::based_try_find_program_address(
185 seeds,
186 crate::native_boundary::as_backend_address(program_id),
187 )
188 .map(|(address, bump)| (Address::new_from_array(address.to_bytes()), bump))
189 .map_err(ProgramError::from)?;
190 #[cfg(not(target_os = "solana"))]
191 let (derived, bump) = find_program_address(seeds, program_id);
192 if crate::address::address_eq(&derived, expected) {
193 Ok(bump)
194 } else {
195 Err(ProgramError::InvalidSeeds)
196 }
197}
198
199#[inline]
201pub fn verify_pda(
202 account: &AccountView<'_>,
203 seeds: &[&[u8]],
204 program_id: &Address,
205) -> Result<(), ProgramError> {
206 #[cfg(target_os = "solana")]
207 {
208 hopper_native::pda::verify_pda(
209 account.as_backend(),
210 seeds,
211 crate::native_boundary::as_backend_address(program_id),
212 )
213 .map_err(ProgramError::from)
214 }
215
216 #[cfg(not(target_os = "solana"))]
217 {
218 let expected = create_program_address(seeds, program_id)?;
219 if crate::address::address_eq(account.address(), &expected) {
220 Ok(())
221 } else {
222 Err(ProgramError::InvalidSeeds)
223 }
224 }
225}
226
227#[inline]
229pub fn verify_pda_with_bump(
230 account: &AccountView<'_>,
231 seeds: &[&[u8]],
232 bump: u8,
233 program_id: &Address,
234) -> Result<(), ProgramError> {
235 #[cfg(target_os = "solana")]
236 {
237 hopper_native::pda::verify_pda_with_bump(
238 account.as_backend(),
239 seeds,
240 bump,
241 crate::native_boundary::as_backend_address(program_id),
242 )
243 .map_err(ProgramError::from)
244 }
245
246 #[cfg(not(target_os = "solana"))]
247 {
248 if seeds.len() >= 16 {
249 return Err(ProgramError::InvalidSeeds);
250 }
251 let mut full_seeds: [&[u8]; 16] = [&[]; 16];
252 let num = seeds.len();
253 let mut i = 0;
254 while i < num {
255 full_seeds[i] = seeds[i];
256 i += 1;
257 }
258 let bump_bytes = [bump];
259 full_seeds[num] = &bump_bytes;
260
261 let expected = create_program_address(&full_seeds[..num + 1], program_id)?;
262 if crate::address::address_eq(account.address(), &expected) {
263 Ok(())
264 } else {
265 Err(ProgramError::InvalidSeeds)
266 }
267 }
268}
269
270#[inline]
282pub fn find_and_verify_pda(
283 account: &AccountView<'_>,
284 seeds: &[&[u8]],
285 program_id: &Address,
286) -> Result<u8, ProgramError> {
287 #[cfg(target_os = "solana")]
288 {
289 let expected_addr = account.as_backend().address();
290 let backend_expected =
291 unsafe { &*(expected_addr as *const hopper_native::address::Address) };
293 verify_pda_sha256_loop(backend_expected, seeds, program_id)
294 }
295
296 #[cfg(not(target_os = "solana"))]
297 {
298 let (expected, bump) = find_program_address(seeds, program_id);
299 if crate::address::address_eq(account.address(), &expected) {
300 Ok(bump)
301 } else {
302 Err(ProgramError::InvalidSeeds)
303 }
304 }
305}
306
307#[inline]
311pub fn verify_pda_strict(
312 expected: &Address,
313 seeds: &[&[u8]],
314 program_id: &Address,
315) -> Result<(), ProgramError> {
316 #[cfg(target_os = "solana")]
317 {
318 let backend_expected =
319 unsafe { &*(expected as *const Address as *const hopper_native::address::Address) };
321 verify_pda_sha256_loop(backend_expected, seeds, program_id).map(|_| ())
322 }
323
324 #[cfg(not(target_os = "solana"))]
325 {
326 let (derived, _) = find_program_address(seeds, program_id);
327 if crate::address::address_eq(&derived, expected) {
328 Ok(())
329 } else {
330 Err(ProgramError::InvalidSeeds)
331 }
332 }
333}
334
335#[cfg(target_os = "solana")]
350#[inline(always)]
351fn verify_pda_sha256_loop(
352 expected: &hopper_native::address::Address,
353 seeds: &[&[u8]],
354 program_id: &Address,
355) -> Result<u8, ProgramError> {
356 hopper_native::pda::find_bump_for_address(
359 seeds,
360 crate::native_boundary::as_backend_address(program_id),
361 expected,
362 )
363 .map_err(ProgramError::from)
364}
365
366#[inline]
371pub fn verify_pda_from_stored_bump(
372 account: &AccountView<'_>,
373 seeds: &[&[u8]],
374 bump_offset: usize,
375 program_id: &Address,
376) -> Result<(), ProgramError> {
377 #[cfg(target_os = "solana")]
378 {
379 hopper_native::verify_pda_from_stored_bump(
380 account.as_backend(),
381 seeds,
382 bump_offset,
383 crate::native_boundary::as_backend_address(program_id),
384 )
385 .map_err(ProgramError::from)
386 }
387
388 #[cfg(not(target_os = "solana"))]
389 {
390 let data = account.try_borrow()?;
392 if bump_offset >= data.len() {
393 return Err(ProgramError::AccountDataTooSmall);
394 }
395 let bump = data[bump_offset];
396 if seeds.len() >= 16 {
397 return Err(ProgramError::InvalidSeeds);
398 }
399 let mut full_seeds: [&[u8]; 16] = [&[]; 16];
400 let num = seeds.len();
401 let mut i = 0;
402 while i < num {
403 full_seeds[i] = seeds[i];
404 i += 1;
405 }
406 let bump_bytes = [bump];
407 full_seeds[num] = &bump_bytes;
408
409 let expected = create_program_address(&full_seeds[..num + 1], program_id)?;
410 if crate::address::address_eq(account.address(), &expected) {
411 Ok(())
412 } else {
413 Err(ProgramError::InvalidSeeds)
414 }
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
427 fn const_program_address_matches_devnet_created_pdas() {
428 const PROGRAM: Address = crate::address!("F4Um7PWsnZfN7y8WFzu1aPYJwqGduJTa4zuCGY9EUqMy");
429 const PAYER: Address = crate::address!("4sbBUbY71JFeA4kJckBmNnTADiFu4jtu84Gzev52ZEhn");
430 const AUTHORITY_C: Address =
431 crate::address!("7Qj28pSptq3YEdppwTmxDEP4jLsS1o67D1ZfKQJB9SE2");
432 const PINA_COUNTER: Address =
433 crate::address!("GJQcuWrT2f3f4KNuJcXhhwUa1ZQTYbxzzJ1hotzKu8hS");
434
435 const PDA_A: Address = crate::const_pda!(PROGRAM, [b"counter", PAYER.as_array()], 252);
436 const PDA_C: Address =
437 crate::const_pda!(PROGRAM, [b"counter", AUTHORITY_C.as_array()], 254);
438 const PDA_PINA: Address =
439 const_program_address(&PINA_COUNTER, &[b"counter", PAYER.as_array()], 253);
440
441 assert_eq!(
442 PDA_A,
443 crate::address!("Cn3JBYNBEctRDGuotxM7c3Fz3QgCZgRXkKV1G7h1qZKn")
444 );
445 assert_eq!(
446 PDA_C,
447 crate::address!("6vh34eBGs3gvwdaJ3fgXDLQMtNfqUwSJYrHqZ3FgCwYP")
448 );
449 assert_eq!(
450 PDA_PINA,
451 crate::address!("CW1z5aL4hTAFFubWKVKw1ANkdYurNEAiWbqxKsDCaERH")
452 );
453 assert_ne!(
455 const_program_address(&PROGRAM, &[b"counter", PAYER.as_array()], 251),
456 PDA_A
457 );
458 }
459}