jiminy_core/interface.rs
1//! Cross-program ABI interface for read-only foreign account access.
2//!
3//! The `jiminy_interface!` macro generates a lightweight,
4//! read-only struct that can decode accounts owned by another program.
5//! It produces the same `LAYOUT_ID` as a matching `zero_copy_layout!`
6//! declaration, enabling cross-program layout verification without
7//! sharing crate dependencies.
8//!
9//! ## Usage
10//!
11//! Program B wants to read Program A's `Vault` account:
12//!
13//! ```rust,ignore
14//! use jiminy_core::jiminy_interface;
15//! use jiminy_core::account::{AccountHeader, Pod, FixedLayout};
16//! use jiminy_core::abi::LeU64;
17//! use pinocchio::Address;
18//!
19//! const PROGRAM_A: Address = [0u8; 32]; // Program A's address
20//!
21//! jiminy_interface! {
22//! /// Read-only view of Program A's Vault account.
23//! pub struct Vault for PROGRAM_A {
24//! header: AccountHeader = 16,
25//! balance: LeU64 = 8,
26//! authority: Address = 32,
27//! }
28//! }
29//!
30//! // In your instruction handler:
31//! fn process(accounts: &[AccountView]) -> ProgramResult {
32//! let verified = Vault::load_foreign(&accounts[0])?;
33//! let vault = verified.get();
34//! // read vault.balance, vault.authority, etc.
35//! Ok(())
36//! }
37//! ```
38//!
39//! ## What gets generated
40//!
41//! - `#[repr(C)]` struct with typed fields
42//! - `LAYOUT_ID` matching the original `zero_copy_layout!` definition
43//! - `LEN` constant
44//! - `overlay` / `read` (immutable only, no mutable access)
45//! - `load_foreign` with Tier 2 owner + layout_id validation
46//! - Const field offsets and `split_fields` (immutable only)
47//!
48//! ## Version
49//!
50//! By default, `version = 1` is used in the `LAYOUT_ID` hash. If the
51//! foreign program uses a different version, specify it explicitly:
52//!
53//! ```rust,ignore
54//! jiminy_interface! {
55//! pub struct PoolV2 for PROGRAM_A, version = 2 { ... }
56//! }
57//! ```
58//!
59//! ## Design
60//!
61//! Interface types are intentionally restricted:
62//! - **No `load` (Tier 1)** — you don't own this account
63//! - **No `overlay_mut`** — foreign accounts are read-only
64//! - **No `split_fields_mut`** — same reason
65//! - **No `load_checked`** — discriminator/version are owner concerns
66
67/// Declare a read-only interface for a foreign program's account layout.
68///
69/// Generates a `#[repr(C)]` struct with the same `LAYOUT_ID` as the
70/// foreign program's `zero_copy_layout!` definition, plus a
71/// `load_foreign` method that validates owner + layout_id.
72///
73/// The struct name must match the original account name for the
74/// `LAYOUT_ID` hash to agree. If you want a local alias, use
75/// `type VaultView = Vault;` after the macro invocation.
76///
77/// ## Version
78///
79/// By default, the interface assumes the foreign program uses
80/// `version = 1`. If the foreign layout uses a different version,
81/// specify it explicitly so the `LAYOUT_ID` hash matches:
82///
83/// ```rust,ignore
84/// jiminy_interface! {
85/// pub struct PoolV2 for PROGRAM_A, version = 2 {
86/// header: AccountHeader = 16,
87/// authority: Address = 32,
88/// reserve: LeU64 = 8,
89/// fee_bps: LeU16 = 2,
90/// }
91/// }
92/// ```
93#[macro_export]
94macro_rules! jiminy_interface {
95 // ── Public arm: no version (defaults to 1) ───────────────────
96 (
97 $(#[$meta:meta])*
98 $vis:vis struct $name:ident for $owner:path {
99 $( $(#[$fmeta:meta])* $field:ident : $fty:ident = $fsize:expr ),+ $(,)?
100 }
101 ) => {
102 $crate::jiminy_interface! {
103 @impl version = 1,
104 $(#[$meta])*
105 $vis struct $name for $owner {
106 $( $(#[$fmeta])* $field : $fty = $fsize ),+
107 }
108 }
109 };
110
111 // ── Public arm: explicit version ─────────────────────────────
112 (
113 $(#[$meta:meta])*
114 $vis:vis struct $name:ident for $owner:path, version = $ver:literal {
115 $( $(#[$fmeta:meta])* $field:ident : $fty:ident = $fsize:expr ),+ $(,)?
116 }
117 ) => {
118 $crate::jiminy_interface! {
119 @impl version = $ver,
120 $(#[$meta])*
121 $vis struct $name for $owner {
122 $( $(#[$fmeta])* $field : $fty = $fsize ),+
123 }
124 }
125 };
126
127 // ── Internal implementation arm ──────────────────────────────
128 (
129 @impl version = $ver:literal,
130 $(#[$meta:meta])*
131 $vis:vis struct $name:ident for $owner:path {
132 $( $(#[$fmeta:meta])* $field:ident : $fty:ident = $fsize:expr ),+ $(,)?
133 }
134 ) => {
135 $(#[$meta])*
136 #[repr(C)]
137 #[derive(Clone, Copy)]
138 $vis struct $name {
139 $( $(#[$fmeta])* pub $field: $fty ),+
140 }
141
142 // SAFETY: repr(C) + Copy + all fields are Pod.
143 unsafe impl $crate::account::Pod for $name {}
144
145 impl $crate::account::FixedLayout for $name {
146 const SIZE: usize = 0 $( + $fsize )+;
147 }
148
149 // Compile-time assertion: actual size must match declared sum.
150 const _: () = assert!(
151 core::mem::size_of::<$name>() == 0 $( + $fsize )+,
152 "size_of does not match declared LEN — check field sizes"
153 );
154
155 // Compile-time assertion: alignment must not exceed 8 bytes.
156 const _: () = assert!(
157 core::mem::align_of::<$name>() <= 8,
158 "layout alignment exceeds 8 bytes — use Le* wrappers for u128 fields"
159 );
160
161 impl $name {
162 /// Total byte size of this account layout.
163 pub const LEN: usize = 0 $( + $fsize )+;
164
165 /// Deterministic ABI fingerprint (first 8 bytes of SHA-256).
166 ///
167 /// The version used in the hash input matches the foreign
168 /// program's `zero_copy_layout!` version. When no version is
169 /// specified in the macro invocation, version 1 is assumed.
170 pub const LAYOUT_ID: [u8; 8] = {
171 const INPUT: &str = concat!(
172 "jiminy:v1:",
173 stringify!($name), ":",
174 stringify!($ver), ":",
175 $( stringify!($field), ":", $crate::__canonical_type!($fty), ":", stringify!($fsize), ",", )+
176 );
177 const HASH: [u8; 32] = $crate::__sha256_const(INPUT.as_bytes());
178 [HASH[0], HASH[1], HASH[2], HASH[3], HASH[4], HASH[5], HASH[6], HASH[7]]
179 };
180
181 /// Expected owner program for this interface.
182 pub const OWNER: &'static $crate::pinocchio::Address = &$owner;
183
184 /// Overlay an immutable reference onto borrowed account data.
185 #[inline(always)]
186 pub fn overlay(data: &[u8]) -> Result<&Self, $crate::pinocchio::error::ProgramError> {
187 $crate::account::pod_from_bytes::<Self>(data)
188 }
189
190 /// Read a copy of this struct from a byte slice (alignment-safe).
191 #[inline(always)]
192 pub fn read(data: &[u8]) -> Result<Self, $crate::pinocchio::error::ProgramError> {
193 $crate::account::pod_read::<Self>(data)
194 }
195
196 /// **Tier 2 — Cross-program read.** Validate owner + layout_id
197 /// + exact size, then borrow.
198 ///
199 /// The owner is checked against the program address passed to
200 /// `jiminy_interface!` via the `for` clause.
201 ///
202 /// Returns a `VerifiedAccount` whose `get()` is infallible.
203 #[inline(always)]
204 pub fn load_foreign<'a>(
205 account: &'a $crate::pinocchio::AccountView,
206 ) -> Result<$crate::account::VerifiedAccount<'a, Self>, $crate::pinocchio::error::ProgramError> {
207 let data = $crate::account::view::validate_foreign(
208 account, &$owner, &Self::LAYOUT_ID, Self::LEN,
209 )?;
210 $crate::account::VerifiedAccount::new(data)
211 }
212
213 // ── Const field offsets ──────────────────────────────────────
214
215 $crate::__gen_offsets!( $( $field = $fsize ),+ );
216
217 // ── Immutable borrow-splitting ───────────────────────────────
218
219 /// Split borrowed data into per-field `FieldRef` slices.
220 #[inline]
221 #[allow(unused_variables)]
222 pub fn split_fields(data: &[u8]) -> Result<( $( $crate::__field_ref_type!($field), )+ ), $crate::pinocchio::error::ProgramError> {
223 if data.len() < Self::LEN {
224 return Err($crate::pinocchio::error::ProgramError::AccountDataTooSmall);
225 }
226 let mut _pos = 0usize;
227 Ok(( $({
228 let start = _pos;
229 _pos += $fsize;
230 $crate::abi::FieldRef::new(&data[start..start + $fsize])
231 }, )+ ))
232 }
233 }
234 };
235}