Skip to main content

jiminy_core/
lib.rs

1#![no_std]
2//! # jiminy-core
3//!
4//! Account layout, validation, math, PDA, and all the zero-copy primitives
5//! your pinocchio program needs before it touches a token.
6//!
7//! This is the systems layer. Everything here works with raw `AccountView`
8//! bytes and has zero dependencies beyond pinocchio itself. If your program
9//! never calls SPL Token, this crate is all you need.
10//!
11//! ```rust,ignore
12//! use jiminy_core::prelude::*;
13//! ```
14//!
15//! # Modules
16//!
17//! | Module | |
18//! |---|---|
19//! | [`account`] | Header, reader, writer, cursor, lifecycle, pod, overlay, collection, list, bits |
20//! | [`abi`] | Alignment-1 LE field types (`LeU64`, `LeBool`, …) and borrow-splitting refs |
21//! | [`check`] | Validation checks, asserts, PDA derivation & verification |
22//! | [`compat`] | Optional `solana-zero-copy` integration *(feature: `solana-zero-copy`)* |
23//! | [`instruction`] | Transaction introspection (sysvar Instructions) |
24//! | [`interface`] | Cross-program ABI interfaces (`jiminy_interface!`) |
25//! | [`math`] | Checked arithmetic, BPS, scaling |
26//! | [`sysvar`] | Clock & Rent sysvar readers |
27//! | [`state`] | State machine transition checks |
28//! | [`time`] | Deadline, cooldown, staleness checks |
29//! | [`event`] | Zero-alloc event emission via `sol_log_data` |
30//! | [`programs`] | Well-known program IDs *(feature: `programs`)* |
31//!
32//! # Macros
33//!
34//! All macros are declarative (`macro_rules!`). No proc macros.
35//!
36//! | Macro | |
37//! |---|---|
38//! | [`require!`] | `if !cond { return Err(e) }` -- the universal guard |
39//! | [`require_keys_eq!`] | Two addresses must match |
40//! | [`check_accounts_unique!`] | Pairwise uniqueness for any N accounts |
41//! | [`error_codes!`] | Define numbered error codes without a proc macro |
42//! | [`instruction_dispatch!`] | Byte-tag instruction routing |
43//! | [`jiminy_interface!`](crate::jiminy_interface) | Read-only interface for foreign program accounts |
44//! | [`impl_pod!`] | Batch `unsafe impl Pod` |
45//! | [`segmented_layout!`] | Fixed prefix + dynamic segments for variable-length accounts |
46//!
47//! # What does NOT belong here
48//!
49//! Token/mint readers, Token-2022 screening, CPI guards, Ed25519, Merkle,
50//! oracles, AMM math, lending, staking, vesting -- see `jiminy-solana`,
51//! `jiminy-finance`, and other domain crates.
52
53// ── Domain modules ───────────────────────────────────────────────────────────
54
55pub mod account;
56pub mod check;
57pub mod event;
58pub mod instruction;
59pub mod math;
60pub mod prelude;
61pub mod state;
62pub mod sysvar;
63pub mod time;
64
65#[cfg(feature = "log")]
66pub mod log;
67
68#[cfg(feature = "programs")]
69pub mod programs;
70
71pub mod abi;
72pub mod compat;
73pub mod interface;
74
75// ── Pinocchio re-exports ─────────────────────────────────────────────────────
76//
77// Downstream crates depend on just jiminy-core; they get pinocchio for free.
78
79pub use pinocchio;
80pub use pinocchio::{error::ProgramError, AccountView, Address, ProgramResult};
81
82// ── Internal helpers (used by macros, not public API) ────────────────────────
83
84/// Const SHA-256 helper for `zero_copy_layout!` layout ID generation.
85#[doc(hidden)]
86pub const fn __sha256_const(data: &[u8]) -> [u8; 32] {
87    sha2_const_stable::Sha256::new().update(data).finalize()
88}
89
90// ── Macros ───────────────────────────────────────────────────────────────────
91
92/// Require a boolean condition: return `$err` (converted via `Into`) if false.
93#[macro_export]
94macro_rules! require {
95    ($cond:expr, $err:expr) => {
96        if !($cond) {
97            return Err($err.into());
98        }
99    };
100}
101
102/// Require two [`Address`] values to be equal.
103#[macro_export]
104macro_rules! require_keys_eq {
105    ($a:expr, $b:expr, $err:expr) => {
106        if *$a != *$b {
107            return Err($err.into());
108        }
109    };
110}
111
112/// Require two [`Address`] values to be **different**.
113#[macro_export]
114macro_rules! require_keys_neq {
115    ($a:expr, $b:expr, $err:expr) => {
116        if *$a == *$b {
117            return Err($err.into());
118        }
119    };
120}
121
122/// Require two accounts to have **different** addresses.
123#[macro_export]
124macro_rules! require_accounts_ne {
125    ($a:expr, $b:expr, $err:expr) => {
126        if $a.address() == $b.address() {
127            return Err($err.into());
128        }
129    };
130}
131
132/// Require `a >= b`.
133#[macro_export]
134macro_rules! require_gte {
135    ($a:expr, $b:expr, $err:expr) => {
136        if $a < $b {
137            return Err($err.into());
138        }
139    };
140}
141
142/// Require `a > b`.
143#[macro_export]
144macro_rules! require_gt {
145    ($a:expr, $b:expr, $err:expr) => {
146        if $a <= $b {
147            return Err($err.into());
148        }
149    };
150}
151
152/// Require `a < b`.
153#[macro_export]
154macro_rules! require_lt {
155    ($a:expr, $b:expr, $err:expr) => {
156        if $a >= $b {
157            return Err($err.into());
158        }
159    };
160}
161
162/// Require `a <= b`.
163#[macro_export]
164macro_rules! require_lte {
165    ($a:expr, $b:expr, $err:expr) => {
166        if $a > $b {
167            return Err($err.into());
168        }
169    };
170}
171
172/// Require `a == b` for scalar types.
173#[macro_export]
174macro_rules! require_eq {
175    ($a:expr, $b:expr, $err:expr) => {
176        if $a != $b {
177            return Err($err.into());
178        }
179    };
180}
181
182/// Require `a != b` for scalar types.
183#[macro_export]
184macro_rules! require_neq {
185    ($a:expr, $b:expr, $err:expr) => {
186        if $a == $b {
187            return Err($err.into());
188        }
189    };
190}
191
192/// Require bit `n` to be set in `$byte`, else return `$err`.
193#[macro_export]
194macro_rules! require_flag {
195    ($byte:expr, $n:expr, $err:expr) => {
196        if ($byte >> $n) & 1 == 0 {
197            return Err($err.into());
198        }
199    };
200}
201
202/// Verify that all passed accounts have unique addresses.
203///
204/// Variadic - works with 2, 3, 4, or more accounts. Expands to
205/// pairwise `!=` checks at compile time. No heap, no loops.
206///
207/// Replaces `check_accounts_unique_2`, `check_accounts_unique_3`, etc.
208///
209/// ```rust,ignore
210/// check_accounts_unique!(payer, vault, mint);
211/// check_accounts_unique!(a, b, c, d);
212/// ```
213#[macro_export]
214macro_rules! check_accounts_unique {
215    // Base case: two accounts.
216    ($a:expr, $b:expr) => {
217        if $a.address() == $b.address() {
218            return Err($crate::pinocchio::error::ProgramError::InvalidArgument);
219        }
220    };
221    // Recursive: compare head against every tail, then recurse on tail.
222    ($head:expr, $($tail:expr),+ $(,)?) => {
223        $( if $head.address() == $tail.address() {
224            return Err($crate::pinocchio::error::ProgramError::InvalidArgument);
225        } )+
226        $crate::check_accounts_unique!($($tail),+);
227    };
228}
229
230/// Define numbered program error codes that map to `ProgramError::Custom`.
231///
232/// Replaces Anchor's `#[error_code]` proc macro. Each variant gets a
233/// `u32` discriminant offset from a base you provide. The macro emits
234/// constants and an `Into<ProgramError>` conversion.
235///
236/// ```rust,ignore
237/// error_codes! {
238///     base = 6000;
239///     Undercollateralized,   // 6000
240///     Expired,               // 6001
241///     InvalidOracle,         // 6002
242/// }
243///
244/// // Use in require! or return Err(...)
245/// require!(collateral >= min, Undercollateralized);
246/// ```
247#[macro_export]
248macro_rules! error_codes {
249    (
250        base = $base:expr;
251        $( $(#[$meta:meta])* $name:ident ),+ $(,)?
252    ) => {
253        /// Program-specific error codes.
254        #[allow(non_upper_case_globals)]
255        pub mod errors {
256            $crate::error_codes!(@count $base; $( $(#[$meta])* $name ),+ );
257        }
258
259        $(
260            impl From<errors::$name> for $crate::pinocchio::error::ProgramError {
261                #[inline(always)]
262                fn from(_: errors::$name) -> Self {
263                    $crate::pinocchio::error::ProgramError::Custom(errors::$name::CODE)
264                }
265            }
266        )+
267    };
268    // Internal counter arm - assigns sequential codes.
269    (@count $code:expr; $(#[$meta:meta])* $name:ident) => {
270        $(#[$meta])*
271        pub struct $name;
272        impl $name {
273            pub const CODE: u32 = $code;
274        }
275    };
276    (@count $code:expr; $(#[$meta:meta])* $name:ident, $( $(#[$rmeta:meta])* $rest:ident ),+ ) => {
277        $(#[$meta])*
278        pub struct $name;
279        impl $name {
280            pub const CODE: u32 = $code;
281        }
282        $crate::error_codes!(@count $code + 1; $( $(#[$rmeta])* $rest ),+ );
283    };
284}
285
286/// Route instruction data to handler functions based on a single-byte tag.
287///
288/// Replaces Anchor's `#[program]` proc macro. Reads byte 0 as the
289/// discriminator and dispatches to the matching handler. Returns
290/// `InvalidInstructionData` for unknown tags.
291///
292/// ```rust,ignore
293/// instruction_dispatch! {
294///     program_id, accounts, instruction_data;
295///     0 => process_init(program_id, accounts, ix),
296///     1 => process_deposit(program_id, accounts, ix),
297///     2 => process_withdraw(program_id, accounts, ix),
298/// }
299/// ```
300///
301/// Inside each arm, `ix` is a `SliceCursor` positioned after the tag byte.
302#[macro_export]
303macro_rules! instruction_dispatch {
304    (
305        $pid:expr, $accs:expr, $data:expr;
306        $( $tag:expr => $handler:expr ),+ $(,)?
307    ) => {{
308        let mut ix = $crate::account::SliceCursor::new($data);
309        let tag = ix.read_u8()?;
310        match tag {
311            $( $tag => { let _ = &ix; $handler } )+
312            _ => Err($crate::pinocchio::error::ProgramError::InvalidInstructionData),
313        }
314    }};
315}
316
317/// Initialize a Jiminy account: CPI CreateAccount, zero-init, write header.
318///
319/// Owns the full creation path so developers cannot forget zero_init or
320/// layout_id. Returns `Ok(())` after creating a fully-initialized account
321/// with a valid 16-byte Jiminy header and zero-filled body.
322///
323/// Follow with `Layout::load_checked_mut()` to get a mutable overlay
324/// for setting field values.
325///
326/// **Note:** Requires `pinocchio_system` in scope. This macro is re-exported
327/// by the root `jiminy` crate which provides it automatically.
328///
329/// ```rust,ignore
330/// init_account!(payer, account, program_id, Vault)?;
331///
332/// // Now set fields via overlay:
333/// let mut data = account.try_borrow_mut()?;
334/// let vault = Vault::overlay_mut(&mut data)?;
335/// vault.balance = 0;
336/// vault.authority = *authority;
337/// ```
338///
339/// Expands to:
340/// 1. `rent_exempt_min(Layout::LEN)` for lamports
341/// 2. CPI `CreateAccount` with correct space and owner
342/// 3. `zero_init` the full data slice
343/// 4. `write_header` with disc + version + layout_id
344#[macro_export]
345macro_rules! init_account {
346    ($payer:expr, $account:expr, $program_id:expr, $Layout:ty) => {{
347        let space = <$Layout>::LEN as u64;
348        let lamports = $crate::check::rent_exempt_min(<$Layout>::LEN);
349        pinocchio_system::instructions::CreateAccount {
350            from: $payer,
351            to: $account,
352            lamports,
353            space,
354            owner: $program_id,
355        }
356        .invoke()?;
357
358        let mut data = $account.try_borrow_mut()?;
359        $crate::account::zero_init(&mut data);
360        $crate::account::write_header(
361            &mut data,
362            <$Layout>::DISC,
363            <$Layout>::VERSION,
364            &<$Layout>::LAYOUT_ID,
365        )?;
366        Ok::<(), $crate::pinocchio::error::ProgramError>(())
367    }};
368}
369
370/// Close a Jiminy account: transfer lamports and write close sentinel.
371///
372/// Wraps `safe_close_with_sentinel` for a consistent one-liner.
373///
374/// ```rust,ignore
375/// close_account!(account, destination);
376/// ```
377#[macro_export]
378macro_rules! close_account {
379    ($account:expr, $destination:expr) => {
380        $crate::account::safe_close_with_sentinel($account, $destination)
381    };
382}
383
384/// Composable account constraint macro.
385///
386/// Validates a combination of account properties in a single call.
387/// Each keyword maps to a check function:
388///
389/// | Keyword | Check |
390/// |---------|-------|
391/// | `owner = $id` | `account.owner() == $id` |
392/// | `writable` | `account.is_writable()` |
393/// | `signer` | `account.is_signer()` |
394/// | `disc = $d` | `data[0] == $d` |
395/// | `version >= $v` | `data[1] >= $v` |
396/// | `layout_id = $id` | `data[4..12] == $id` |
397/// | `size >= $n` | `data.len() >= $n` |
398///
399/// ```rust,ignore
400/// check_account!(vault, owner = program_id, writable, disc = Vault::DISC,
401///                layout_id = &Vault::LAYOUT_ID, size >= Vault::LEN);
402/// ```
403#[macro_export]
404macro_rules! check_account {
405    ($account:expr, $($constraint:tt)*) => {{
406        $crate::__check_account_inner!($account, $($constraint)*)
407    }};
408}
409
410#[doc(hidden)]
411#[macro_export]
412macro_rules! __check_account_inner {
413    // Terminal: no more constraints
414    ($account:expr, ) => { Ok::<(), $crate::pinocchio::error::ProgramError>(()) };
415    ($account:expr $(,)?) => { Ok::<(), $crate::pinocchio::error::ProgramError>(()) };
416
417    // owner = $id
418    ($account:expr, owner = $id:expr $(, $($rest:tt)*)?) => {{
419        $crate::check::check_owner($account, $id)?;
420        $crate::__check_account_inner!($account, $($($rest)*)?)
421    }};
422
423    // writable
424    ($account:expr, writable $(, $($rest:tt)*)?) => {{
425        $crate::check::check_writable($account)?;
426        $crate::__check_account_inner!($account, $($($rest)*)?)
427    }};
428
429    // signer
430    ($account:expr, signer $(, $($rest:tt)*)?) => {{
431        $crate::check::check_signer($account)?;
432        $crate::__check_account_inner!($account, $($($rest)*)?)
433    }};
434
435    // disc = $d
436    ($account:expr, disc = $d:expr $(, $($rest:tt)*)?) => {{
437        {
438            let data = $account.try_borrow()?;
439            $crate::check::check_discriminator(&data, $d)?;
440        }
441        $crate::__check_account_inner!($account, $($($rest)*)?)
442    }};
443
444    // version >= $v
445    ($account:expr, version >= $v:expr $(, $($rest:tt)*)?) => {{
446        {
447            let data = $account.try_borrow()?;
448            $crate::check::check_version(&data, $v)?;
449        }
450        $crate::__check_account_inner!($account, $($($rest)*)?)
451    }};
452
453    // layout_id = $id
454    ($account:expr, layout_id = $id:expr $(, $($rest:tt)*)?) => {{
455        {
456            let data = $account.try_borrow()?;
457            $crate::account::check_layout_id(&data, $id)?;
458        }
459        $crate::__check_account_inner!($account, $($($rest)*)?)
460    }};
461
462    // size >= $n
463    ($account:expr, size >= $n:expr $(, $($rest:tt)*)?) => {{
464        {
465            let data = $account.try_borrow()?;
466            $crate::check::check_size(&data, $n)?;
467        }
468        $crate::__check_account_inner!($account, $($($rest)*)?)
469    }};
470}
471
472/// Strict account validation. Like [`check_account!`] but requires
473/// `owner`, `disc`, and `layout_id` as the first three arguments.
474/// Forgetting any of them is a compile error.
475///
476/// Additional optional constraints (`writable`, `signer`, `version >=`,
477/// `size >=`) can follow.
478///
479/// ```rust,ignore
480/// check_account_strict!(vault, owner = program_id, disc = Vault::DISC,
481///     layout_id = &Vault::LAYOUT_ID, writable, size >= Vault::LEN);
482/// ```
483#[macro_export]
484macro_rules! check_account_strict {
485    ($account:expr, owner = $owner:expr, disc = $disc:expr, layout_id = $lid:expr
486        $(, $($rest:tt)*)?) => {{
487        $crate::check::check_owner($account, $owner)?;
488        {
489            let data = $account.try_borrow()?;
490            $crate::check::check_discriminator(&data, $disc)?;
491            $crate::account::check_layout_id(&data, $lid)?;
492        }
493        $crate::__check_account_inner!($account, $($($rest)*)?)
494    }};
495}