Skip to main content

hopper_runtime/
context.rs

1//! Execution context for Hopper programs.
2//!
3//! `Context` is the canonical execution object that Hopper handlers receive.
4//! It provides structured access to the program_id, accounts, and instruction
5//! data, with indexed access and validation helpers.
6//!
7//! Keep it boring: `Context` is the container for accounts, instruction data,
8//! and the instruction-scoped segment borrow registry. `AccountView` owns the
9//! actual access operations.
10
11use crate::account::AccountView;
12use crate::audit::AccountAudit;
13use crate::address::Address;
14use crate::error::ProgramError;
15use crate::layout::LayoutContract;
16use crate::segment_borrow::SegmentBorrowRegistry;
17use crate::ProgramResult;
18
19/// Execution context for a Hopper instruction handler.
20///
21/// Wraps the program_id, account slice, and instruction data into a single
22/// object with structured access patterns.
23///
24/// # Authored flow
25///
26/// ```ignore
27/// pub fn deposit(ctx: &Context, amount: u64) -> ProgramResult {
28///     let authority = ctx.account(0)?;
29///     let vault = ctx.account(1)?;
30///
31///     authority.require_signer()?;
32///     vault.require_writable()?;
33///     vault.check_disc(1)?;
34///
35///     let mut state = vault.load_mut::<VaultState>()?;
36///     state.balance = state.balance.checked_add(amount).ok_or(ProgramError::ArithmeticOverflow)?;
37///     Ok(())
38/// }
39/// ```
40pub struct Context<'a> {
41    /// The program's own address.
42    pub program_id: &'a Address,
43    /// All accounts passed to this instruction.
44    accounts: &'a [AccountView],
45    /// Raw instruction data (past the discriminator byte, if applicable).
46    pub instruction_data: &'a [u8],
47    /// Segment-level borrow tracking for fine-grained access control.
48    ///
49    /// Enables safe concurrent mutable access to non-overlapping regions
50    /// of the same account. This is what makes Hopper strictly safer than
51    /// raw Pinocchio without adding meaningful CU overhead.
52    /// Prefer the `borrows()` / `borrows_mut()` accessors in new code.
53    pub(crate) segment_borrows: SegmentBorrowRegistry,
54}
55
56impl<'a> Context<'a> {
57    /// Create a new context from the entrypoint parameters.
58    #[inline(always)]
59    pub fn new(
60        program_id: &'a Address,
61        accounts: &'a [AccountView],
62        instruction_data: &'a [u8],
63    ) -> Self {
64        Self {
65            program_id,
66            accounts,
67            instruction_data,
68            segment_borrows: SegmentBorrowRegistry::new(),
69        }
70    }
71
72    /// Program ID.
73    #[inline(always)]
74    pub fn program_id(&self) -> &Address {
75        self.program_id
76    }
77
78    /// Raw instruction data.
79    #[inline(always)]
80    pub fn instruction_data(&self) -> &'a [u8] {
81        self.instruction_data
82    }
83
84    /// Get an account by index.
85    #[inline(always)]
86    pub fn account(&self, index: usize) -> Result<&AccountView, ProgramError> {
87        self.accounts.get(index).ok_or(ProgramError::NotEnoughAccountKeys)
88    }
89
90    /// Get an account by index (mutation-intent variant).
91    ///
92    /// Functionally identical to `account()` since `AccountView` uses
93    /// interior mutability for data access (`overlay_mut`, `load_mut`,
94    /// `try_borrow_mut`). The distinct name signals that the caller
95    /// intends to write through the returned reference.
96    #[inline(always)]
97    pub fn account_mut(&self, index: usize) -> Result<&AccountView, ProgramError> {
98        self.accounts.get(index).ok_or(ProgramError::NotEnoughAccountKeys)
99    }
100
101    /// Get the total number of accounts.
102    #[inline(always)]
103    pub fn num_accounts(&self) -> usize {
104        self.accounts.len()
105    }
106
107    /// Get all accounts as a slice.
108    #[inline(always)]
109    pub fn accounts(&self) -> &[AccountView] {
110        self.accounts
111    }
112
113    /// Access the instruction-scoped segment borrow registry.
114    #[inline(always)]
115    pub fn borrows(&self) -> &SegmentBorrowRegistry {
116        &self.segment_borrows
117    }
118
119    /// Mutably access the instruction-scoped segment borrow registry.
120    #[inline(always)]
121    pub fn borrows_mut(&mut self) -> &mut SegmentBorrowRegistry {
122        &mut self.segment_borrows
123    }
124
125    /// Inspect the instruction account slice for duplicate aliases.
126    #[inline(always)]
127    pub fn audit_accounts(&self) -> AccountAudit<'a> {
128        AccountAudit::new(self.accounts)
129    }
130
131    /// Get the remaining accounts starting at `from`.
132    #[inline(always)]
133    pub fn remaining_accounts(&self, from: usize) -> &[AccountView] {
134        if from >= self.accounts.len() {
135            &[]
136        } else {
137            &self.accounts[from..]
138        }
139    }
140
141    /// Require at least `n` accounts are present.
142    #[inline(always)]
143    pub fn require_accounts(&self, n: usize) -> ProgramResult {
144        if self.accounts.len() >= n {
145            Ok(())
146        } else {
147            Err(ProgramError::NotEnoughAccountKeys)
148        }
149    }
150
151    /// Require all account addresses to be unique.
152    #[inline(always)]
153    pub fn require_unique_accounts(&self) -> ProgramResult {
154        self.audit_accounts().require_all_unique()
155    }
156
157    /// Require that no duplicated account is writable in this instruction.
158    #[inline(always)]
159    pub fn require_unique_writable_accounts(&self) -> ProgramResult {
160        self.audit_accounts().require_unique_writable()
161    }
162
163    /// Require that no duplicated account is used as a signer role.
164    #[inline(always)]
165    pub fn require_unique_signer_accounts(&self) -> ProgramResult {
166        self.audit_accounts().require_unique_signers()
167    }
168
169    /// Require at least `n` bytes of instruction data.
170    #[inline(always)]
171    pub fn require_data_len(&self, n: usize) -> ProgramResult {
172        if self.instruction_data.len() >= n {
173            Ok(())
174        } else {
175            Err(ProgramError::InvalidInstructionData)
176        }
177    }
178
179    // --- Whole-Layout Typed Access ----------------------------------
180
181    /// Validate-and-load the full typed layout for an account.
182    ///
183    /// This is the indexed shortcut for `ctx.account(idx)?.load::<T>()`.
184    /// It's the canonical "Tier A" access path: the runtime checks the
185    /// Hopper header, validates the data length, and projects the typed
186    /// view in one inlined call. no extra cost over the spelled-out form.
187    #[inline(always)]
188    pub fn load<T: LayoutContract>(
189        &self,
190        index: usize,
191    ) -> Result<crate::Ref<'_, T>, ProgramError> {
192        self.account(index)?.load::<T>()
193    }
194
195    /// Validate-and-load a mutable typed layout for an account.
196    ///
197    /// Indexed shortcut for `ctx.account(idx)?.load_mut::<T>()`. The
198    /// returned guard holds the account-level exclusive borrow until
199    /// it drops.
200    #[inline(always)]
201    pub fn load_mut<T: LayoutContract>(
202        &self,
203        index: usize,
204    ) -> Result<crate::RefMut<'_, T>, ProgramError> {
205        self.account(index)?.load_mut::<T>()
206    }
207
208    /// Cross-program load: validate ABI fingerprint without ownership check.
209    ///
210    /// Use this when reading an account whose owner is another program but
211    /// whose layout is published as a Hopper layout contract.
212    #[inline(always)]
213    pub fn load_cross_program<T: LayoutContract>(
214        &self,
215        index: usize,
216    ) -> Result<crate::Ref<'_, T>, ProgramError> {
217        self.account(index)?.load_cross_program::<T>()
218    }
219
220    // --- Segment-Level Access (fine-grained borrow tracking) --------
221
222    /// Register a read borrow for a segment of an account and return a
223    /// [`SegRef<T>`](crate::SegRef) that releases both the account-level
224    /// byte guard **and** the segment registry lease on drop.
225    ///
226    /// `index` is the account index. `abs_offset` is the absolute byte
227    /// offset within the account data (including header bytes).
228    ///
229    /// # Type Safety
230    ///
231    /// `T` must implement `Pod` (substrate-level "safe to overlay on
232    /// raw bytes" contract: every bit pattern valid, align-1, no
233    /// padding, no interior pointers). Segment borrow tracking
234    /// prevents conflicting write access to the same byte range for
235    /// the guard's lifetime.
236    ///
237    /// # Canonical path (audit ST1 / winning-architecture spec)
238    ///
239    /// Three variants exist for different offset sources:
240    ///
241    /// | Variant | Use when |
242    /// |---|---|
243    /// | [`segment_ref_typed`](Self::segment_ref_typed) (canonical) | Offset is a compile-time constant (the common case). The `const OFFSET: u32` generic becomes an immediate in the pointer arithmetic. |
244    /// | [`segment_ref_const`](Self::segment_ref_const) | Offset comes from a runtime [`Segment`] value (dispatching dynamically between named fields). |
245    /// | `segment_ref` (this method) | Offset is fully dynamic (iterating segments in a loop, for example). |
246    ///
247    /// `#[hopper::context]`-generated accessors default to the canonical
248    /// typed path; reach for the others only when the use case
249    /// genuinely needs a runtime offset.
250    #[inline(always)]
251    pub fn segment_ref<'b, T: crate::Pod>(
252        &'b mut self,
253        index: usize,
254        abs_offset: u32,
255    ) -> Result<crate::SegRef<'b, T>, ProgramError> {
256        let view = self.accounts.get(index)
257            .ok_or(ProgramError::NotEnoughAccountKeys)?;
258        view.segment_ref::<T>(&mut self.segment_borrows, abs_offset, core::mem::size_of::<T>() as u32)
259    }
260
261    /// Register a write borrow for a segment of an account.
262    ///
263    /// Validates bounds, checks writable, and registers a leased
264    /// exclusive borrow, then returns a [`SegRefMut<T>`](crate::SegRefMut)
265    /// that releases on drop.
266    ///
267    /// This is the primitive that enables safe concurrent mutation of
268    /// non-overlapping account regions. Hopper's core innovation . 
269    /// and the lease model (added post-audit) makes sequential
270    /// same-region borrows inside one instruction work correctly.
271    #[inline(always)]
272    pub fn segment_mut<'b, T: crate::Pod>(
273        &'b mut self,
274        index: usize,
275        abs_offset: u32,
276    ) -> Result<crate::SegRefMut<'b, T>, ProgramError> {
277        let view = self.accounts.get(index)
278            .ok_or(ProgramError::NotEnoughAccountKeys)?;
279        view.segment_mut::<T>(&mut self.segment_borrows, abs_offset, core::mem::size_of::<T>() as u32)
280    }
281
282    /// Const-driven segment read: pass a compile-time [`Segment`] and the
283    /// account index. Lowers to the same pointer-plus-const-offset shape
284    /// as `segment_ref` but without the caller hand-rolling the offset +
285    /// size arguments.
286    #[inline(always)]
287    pub fn segment_ref_const<'b, T: crate::Pod>(
288        &'b mut self,
289        index: usize,
290        segment: crate::Segment,
291    ) -> Result<crate::SegRef<'b, T>, ProgramError> {
292        let view = self.accounts.get(index)
293            .ok_or(ProgramError::NotEnoughAccountKeys)?;
294        view.segment_ref_const::<T>(&mut self.segment_borrows, segment)
295    }
296
297    /// Const-driven exclusive segment access. Pair with
298    /// `#[hopper::state]` constants for zero-overhead field writes.
299    #[inline(always)]
300    pub fn segment_mut_const<'b, T: crate::Pod>(
301        &'b mut self,
302        index: usize,
303        segment: crate::Segment,
304    ) -> Result<crate::SegRefMut<'b, T>, ProgramError> {
305        let view = self.accounts.get(index)
306            .ok_or(ProgramError::NotEnoughAccountKeys)?;
307        view.segment_mut_const::<T>(&mut self.segment_borrows, segment)
308    }
309
310    /// Typed-segment read: the type and offset are both compile-time
311    /// constants, baked into a [`TypedSegment`] zero-sized marker.
312    #[inline(always)]
313    pub fn segment_ref_typed<'b, T: crate::Pod, const OFFSET: u32>(
314        &'b mut self,
315        index: usize,
316        segment: crate::TypedSegment<T, OFFSET>,
317    ) -> Result<crate::SegRef<'b, T>, ProgramError> {
318        let view = self.accounts.get(index)
319            .ok_or(ProgramError::NotEnoughAccountKeys)?;
320        view.segment_ref_typed::<T, OFFSET>(&mut self.segment_borrows, segment)
321    }
322
323    /// Typed-segment write. Mirrors [`segment_ref_typed`] for the
324    /// exclusive path.
325    #[inline(always)]
326    pub fn segment_mut_typed<'b, T: crate::Pod, const OFFSET: u32>(
327        &'b mut self,
328        index: usize,
329        segment: crate::TypedSegment<T, OFFSET>,
330    ) -> Result<crate::SegRefMut<'b, T>, ProgramError> {
331        let view = self.accounts.get(index)
332            .ok_or(ProgramError::NotEnoughAccountKeys)?;
333        view.segment_mut_typed::<T, OFFSET>(&mut self.segment_borrows, segment)
334    }
335
336    /// Explicit unsafe whole-account typed read.
337    #[inline(always)]
338    pub unsafe fn raw_ref<T: crate::Pod>(
339        &self,
340        index: usize,
341    ) -> Result<crate::Ref<'_, T>, ProgramError> {
342        let view = self.accounts.get(index)
343            .ok_or(ProgramError::NotEnoughAccountKeys)?;
344        unsafe { view.raw_ref::<T>() }
345    }
346
347    /// Explicit unsafe whole-account typed write.
348    #[inline(always)]
349    pub unsafe fn raw_mut<T: crate::Pod>(
350        &self,
351        index: usize,
352    ) -> Result<crate::RefMut<'_, T>, ProgramError> {
353        let view = self.accounts.get(index)
354            .ok_or(ProgramError::NotEnoughAccountKeys)?;
355        unsafe { view.raw_mut::<T>() }
356    }
357
358    /// Explicit unsafe escape hatch for whole-account typed projection.
359    ///
360    /// This bypasses segment borrow tracking. The caller is responsible for
361    /// alias safety and for using a type that matches the account bytes.
362    #[inline(always)]
363    pub unsafe fn raw_unchecked<T: crate::Pod>(
364        &self,
365        index: usize,
366    ) -> Result<crate::RefMut<'_, T>, ProgramError> {
367        unsafe { self.raw_mut::<T>(index) }
368    }
369
370    /// Canonical raw-pointer escape hatch to an account's data buffer.
371    ///
372    /// Returns a pointer to the first byte of `accounts[index]`'s data
373    /// region (after the runtime account header, before any Hopper
374    /// 16-byte layout header). The pointer is valid for reads and
375    /// writes for the lifetime of the account view and carries no
376    /// borrow-tracking obligations. Dereferencing it is `unsafe`
377    /// because the caller takes over alias-safety responsibility
378    /// that the segment registry normally upholds.
379    ///
380    /// This is the explicit power-user primitive the audit asks for:
381    /// safe code reaches for `segment_ref_typed` / `segment_mut_typed`
382    /// / the generated `ctx.<field>_segment_mut(...)` accessors; raw
383    /// code drops to `unsafe { ctx.as_mut_ptr(0)?.add(offset) as *mut T }`.
384    ///
385    /// # Safety
386    ///
387    /// The caller must guarantee no aliasing mutable borrow is held
388    /// on the same account for the duration of any write through the
389    /// returned pointer. The returned pointer must be dereferenced
390    /// within the `'info` lifetime of the account view; reading past
391    /// `AccountView::data_len()` is undefined behaviour.
392    #[cfg(feature = "hopper-native-backend")]
393    #[inline(always)]
394    pub unsafe fn as_mut_ptr(&self, index: usize) -> Result<*mut u8, ProgramError> {
395        let view = self
396            .accounts
397            .get(index)
398            .ok_or(ProgramError::NotEnoughAccountKeys)?;
399        view.require_writable()?;
400        // SAFETY: the account view is live for `'info` and
401        // `data_ptr` yields a pointer inside the loader-provided
402        // per-account buffer. Returning the untyped pointer transfers
403        // alias-safety to the caller as documented above.
404        Ok(view.data_ptr_unchecked())
405    }
406
407    /// Immutable sibling of [`as_mut_ptr`]. Returns a `*const u8`.
408    ///
409    /// Shared-borrow checking still runs, so calling this while an
410    /// exclusive borrow is live on the same account fails with
411    /// `AccountBorrowFailed`. The return value is safe to obtain; the
412    /// caller only needs `unsafe` to dereference it.
413    ///
414    /// [`as_mut_ptr`]: Self::as_mut_ptr
415    #[cfg(feature = "hopper-native-backend")]
416    #[inline(always)]
417    pub fn as_ptr(&self, index: usize) -> Result<*const u8, ProgramError> {
418        let view = self
419            .accounts
420            .get(index)
421            .ok_or(ProgramError::NotEnoughAccountKeys)?;
422        view.check_borrow()?;
423        Ok(view.data_ptr_unchecked() as *const u8)
424    }
425
426    /// Read instruction data as a typed value (unaligned, little-endian safe).
427    ///
428    /// Reads `size_of::<T>()` bytes starting at `offset` via `read_unaligned`.
429    /// Caller must ensure `T` is a plain-old-data type where all bit patterns
430    /// are valid.
431    #[inline(always)]
432    pub fn read_data<T: crate::Pod>(&self, offset: usize) -> Result<T, ProgramError> {
433        let end = offset.checked_add(core::mem::size_of::<T>())
434            .ok_or(ProgramError::ArithmeticOverflow)?;
435        if self.instruction_data.len() < end {
436            return Err(ProgramError::InvalidInstructionData);
437        }
438        // SAFETY: bounds checked; `T: Pod` guarantees every bit
439        // pattern is valid and the type has no drop glue, so
440        // `read_unaligned` into instruction data is sound.
441        Ok(unsafe {
442            core::ptr::read_unaligned(self.instruction_data.as_ptr().add(offset) as *const T)
443        })
444    }
445
446    /// Get a byte slice from instruction data.
447    #[inline(always)]
448    pub fn data_slice(&self, offset: usize, len: usize) -> Result<&[u8], ProgramError> {
449        let end = offset.checked_add(len).ok_or(ProgramError::ArithmeticOverflow)?;
450        if self.instruction_data.len() < end {
451            return Err(ProgramError::InvalidInstructionData);
452        }
453        Ok(&self.instruction_data[offset..end])
454    }
455
456    /// Read the first byte of instruction data as an instruction tag.
457    ///
458    /// Common pattern for byte-tag dispatch.
459    #[inline(always)]
460    pub fn instruction_tag(&self) -> Result<u8, ProgramError> {
461        self.instruction_data.first().copied().ok_or(ProgramError::InvalidInstructionData)
462    }
463}