alea_sdk/cpi.rs
1//! CPI wrapper for Alea's `verify` instruction.
2//!
3//! Consumer programs should call [`verify`] instead of invoking the raw
4//! Anchor-generated `alea_verifier::cpi::verify` directly — this helper
5//! captures the return data in the same expression, preventing the ordering
6//! footgun described below.
7//!
8//! # CRITICAL: Return data ordering
9//!
10//! Solana's return data is single-slot — each CPI call that sets return data
11//! overwrites the previous value. Consumer programs MUST read the randomness
12//! into a local variable immediately after this call, BEFORE making any other
13//! CPI calls (token transfers, system program, etc.):
14//!
15//! ```rust,ignore
16//! // CORRECT — capture first, then downstream CPIs are safe
17//! let randomness = alea_sdk::cpi::verify(/* args */)?.into_inner();
18//! token::transfer(transfer_ctx, amount)?;
19//!
20//! // WRONG — token::transfer overwrites Alea's return data
21//! token::transfer(transfer_ctx, amount)?;
22//! let randomness = alea_sdk::cpi::verify(/* args */)?.into_inner(); // stale/empty
23//! ```
24
25use anchor_lang::prelude::*;
26
27/// Captured drand randomness from a successful [`verify`] CPI.
28///
29/// Wraps the raw `[u8; 32]` randomness in a `#[must_use]` newtype. This
30/// closes a class of consumer bugs that `?` extraction on a bare
31/// `Result<[u8; 32]>` cannot catch: writing
32/// `alea_sdk::cpi::verify(...)?;` discards the randomness silently with
33/// no compiler warning, because `?` extracts the `Ok` variant and Rust
34/// happily drops array return values.
35///
36/// By wrapping in a `#[must_use]` struct, `alea_sdk::cpi::verify(...)?;`
37/// produces a compile-time `unused_must_use` warning — the consumer is
38/// forced to capture the result. Phase 4.5 T1-17 integration-audit fix.
39///
40/// # Usage
41///
42/// ```rust,ignore
43/// // Preferred — capture the bytes:
44/// let randomness = alea_sdk::cpi::verify(/* … */)?.into_inner();
45///
46/// // Or keep the wrapper and read without moving:
47/// let verified = alea_sdk::cpi::verify(/* … */)?;
48/// let first_u64 = u64::from_le_bytes(
49/// verified.as_bytes()[0..8].try_into().unwrap()
50/// );
51/// ```
52#[must_use = "Alea's return data is single-slot — capture randomness into a local before any other CPI"]
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct VerifiedRandomness([u8; 32]);
55
56impl VerifiedRandomness {
57 /// Returns the raw 32-byte randomness and consumes the wrapper.
58 #[inline]
59 pub fn into_inner(self) -> [u8; 32] {
60 self.0
61 }
62
63 /// Borrows the underlying 32 bytes without consuming the wrapper.
64 #[inline]
65 pub fn as_bytes(&self) -> &[u8; 32] {
66 &self.0
67 }
68}
69
70impl From<VerifiedRandomness> for [u8; 32] {
71 #[inline]
72 fn from(v: VerifiedRandomness) -> Self {
73 v.0
74 }
75}
76
77impl AsRef<[u8]> for VerifiedRandomness {
78 #[inline]
79 fn as_ref(&self) -> &[u8] {
80 &self.0
81 }
82}
83
84/// Verify a drand beacon via CPI to Alea and receive 32 bytes of randomness.
85///
86/// Pattern A (auto-deserialize) per ADR 0030. Anchor 0.30.x's generated
87/// `alea_verifier::cpi::verify(...)` returns `Result<Return<[u8; 32]>>`;
88/// `.get()` unwraps to the deserialized `[u8; 32]` directly. No
89/// `get_return_data()` call is required.
90///
91/// Returns a [`VerifiedRandomness`] wrapper (must_use) rather than a bare
92/// `[u8; 32]` so that `alea_sdk::cpi::verify(...)?;` without binding
93/// produces a compile warning instead of silently dropping the bytes.
94/// Call `.into_inner()` or `.as_bytes()` to reach the raw randomness.
95///
96/// # Arguments
97/// * `alea_program` — the Alea program (must be `ALEAydzHd…` per
98/// [`crate::PROGRAM_ID`])
99/// * `config` — the Alea Config PDA (must be checked in the consumer's
100/// Accounts struct with `seeds::program = alea_program.key()` — see
101/// the `lib.rs` doc example and ADR 0034)
102/// * `payer` — signer, passed through to Alea's Verify accounts struct
103/// * `round` — drand round number
104/// * `signature` — 64-byte G1 point (uncompressed, x || y big-endian)
105///
106/// # Errors
107/// Returns Alea's on-chain error codes (6000–6012) for signature,
108/// chain-hash, or field-arithmetic failures. See [`crate::AleaError`].
109pub fn verify<'info>(
110 alea_program: AccountInfo<'info>,
111 config: AccountInfo<'info>,
112 payer: AccountInfo<'info>,
113 round: u64,
114 signature: [u8; 64],
115) -> Result<VerifiedRandomness> {
116 // Defense-in-depth (Phase 4.5 T1-08): the mandatory `seeds::program`
117 // guard lives in the consumer's #[derive(Accounts)], which is the
118 // strong defense for Anchor-idiomatic callers. Non-Anchor callers
119 // (raw process_instruction handlers, governance relays, CPI
120 // forwarders) bypass that check. A runtime owner assertion here
121 // closes the gap at ~200 CU cost (0.04% of the 900K budget).
122 require_keys_eq!(
123 *config.owner,
124 crate::PROGRAM_ID,
125 crate::AleaError::WrongPubkey
126 );
127 let accounts = alea_verifier::cpi::accounts::Verify {
128 config: config.clone(),
129 payer: payer.clone(),
130 };
131 let cpi_ctx = CpiContext::new(alea_program, accounts);
132 let randomness = alea_verifier::cpi::verify(cpi_ctx, round, signature)?.get();
133 Ok(VerifiedRandomness(randomness))
134}