Skip to main content

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}