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}