Skip to main content

hopper/
guards.rs

1//! Validation guards for Hopper programs.
2//!
3//! These functions define the "sentence structure" of Hopper handlers.
4//! They support the canonical authored flow:
5//!
6//! **Validate -> Load -> Mutate -> Emit**
7//!
8//! Each guard reads as a clear, auditable assertion. Functions return
9//! `ProgramResult` (i.e. `Result<(), ProgramError>`) so they compose
10//! naturally with `?`.
11//!
12//! ```ignore
13//! use hopper::prelude::*;
14//!
15//! fn deposit(ctx: &Context, amount: u64) -> ProgramResult {
16//!     let authority = ctx.account(0)?;
17//!     let vault = ctx.account(1)?;
18//!
19//!     require_signer(authority)?;
20//!     require_writable(vault)?;
21//!     require_disc(vault, 1)?;
22//!     require_owner(vault, ctx.program_id)?;
23//!
24//!     let state = vault.load_mut::<VaultState>()?;
25//!     // ...
26//!     Ok(())
27//! }
28//! ```
29
30use hopper_runtime::{AccountView, Address, LayoutContract, ProgramError, ProgramResult};
31
32/// Require a boolean condition, returning `err` if false.
33#[inline(always)]
34pub fn require(cond: bool, err: ProgramError) -> ProgramResult {
35    if cond {
36        Ok(())
37    } else {
38        Err(err)
39    }
40}
41
42/// Require two values to be equal.
43#[inline(always)]
44pub fn require_eq<T: PartialEq>(a: T, b: T, err: ProgramError) -> ProgramResult {
45    if a == b {
46        Ok(())
47    } else {
48        Err(err)
49    }
50}
51
52/// Require two values to be different.
53#[inline(always)]
54pub fn require_neq<T: PartialEq>(a: T, b: T, err: ProgramError) -> ProgramResult {
55    if a != b {
56        Ok(())
57    } else {
58        Err(err)
59    }
60}
61
62/// Require a >= b.
63#[inline(always)]
64pub fn require_gte<T: PartialOrd>(a: T, b: T, err: ProgramError) -> ProgramResult {
65    if a >= b {
66        Ok(())
67    } else {
68        Err(err)
69    }
70}
71
72/// Require a > b.
73#[inline(always)]
74pub fn require_gt<T: PartialOrd>(a: T, b: T, err: ProgramError) -> ProgramResult {
75    if a > b {
76        Ok(())
77    } else {
78        Err(err)
79    }
80}
81
82/// Require that the account signed the transaction.
83#[inline(always)]
84pub fn require_signer(account: &AccountView) -> ProgramResult {
85    account.require_signer()
86}
87
88/// Require that the account is writable.
89#[inline(always)]
90pub fn require_writable(account: &AccountView) -> ProgramResult {
91    account.require_writable()
92}
93
94/// Require both signer and writable (common payer pattern).
95#[inline(always)]
96pub fn require_payer(account: &AccountView) -> ProgramResult {
97    account.require_payer()
98}
99
100/// Require that the account is owned by the given program.
101#[inline(always)]
102pub fn require_owner(account: &AccountView, owner: &Address) -> ProgramResult {
103    account.require_owned_by(owner)
104}
105
106/// Require that the account has the given address.
107#[inline(always)]
108pub fn require_address(account: &AccountView, expected: &Address) -> ProgramResult {
109    if hopper_runtime::address::address_eq(account.address(), expected) {
110        Ok(())
111    } else {
112        Err(ProgramError::InvalidArgument)
113    }
114}
115
116/// Require two addresses to be equal.
117#[inline(always)]
118pub fn require_keys_eq(a: &Address, b: &Address, err: ProgramError) -> ProgramResult {
119    if hopper_runtime::address::address_eq(a, b) {
120        Ok(())
121    } else {
122        Err(err)
123    }
124}
125
126/// Require two addresses to be different.
127#[inline(always)]
128pub fn require_keys_neq(a: &Address, b: &Address, err: ProgramError) -> ProgramResult {
129    if !hopper_runtime::address::address_eq(a, b) {
130        Ok(())
131    } else {
132        Err(err)
133    }
134}
135
136/// Require the account has the given discriminator byte.
137#[inline(always)]
138pub fn require_disc(account: &AccountView, expected: u8) -> ProgramResult {
139    account.require_disc(expected)
140}
141
142/// Require the account passes a full layout contract check (disc + version + layout_id).
143#[inline(always)]
144pub fn require_layout<T: LayoutContract>(account: &AccountView) -> ProgramResult {
145    account.check_layout::<T>().map(|_| ())
146}
147
148/// Require the account has non-empty data.
149#[inline(always)]
150pub fn require_has_data(account: &AccountView) -> ProgramResult {
151    if !account.is_data_empty() {
152        Ok(())
153    } else {
154        Err(ProgramError::AccountDataTooSmall)
155    }
156}
157
158/// Require the account has at least `min_len` bytes of data.
159#[inline(always)]
160pub fn require_data_len(account: &AccountView, min_len: usize) -> ProgramResult {
161    if account.data_len() >= min_len {
162        Ok(())
163    } else {
164        Err(ProgramError::AccountDataTooSmall)
165    }
166}
167
168/// Require that `n` accounts are different (pairwise uniqueness, up to 6).
169///
170/// For more than 6 accounts, use `check_accounts_unique!` macro from jiminy-core.
171#[inline(always)]
172pub fn require_unique_2(a: &AccountView, b: &AccountView) -> ProgramResult {
173    if hopper_runtime::address::address_eq(a.address(), b.address()) {
174        Err(ProgramError::InvalidArgument)
175    } else {
176        Ok(())
177    }
178}
179
180/// Require the account's version byte matches the layout contract's VERSION.
181#[inline(always)]
182pub fn require_version<T: LayoutContract>(account: &AccountView) -> ProgramResult {
183    account.check_version(T::VERSION).map(|_| ())
184}
185
186/// Require 3 accounts are pairwise unique.
187#[inline(always)]
188pub fn require_unique_3(a: &AccountView, b: &AccountView, c: &AccountView) -> ProgramResult {
189    require_unique_2(a, b)?;
190    require_unique_2(a, c)?;
191    require_unique_2(b, c)
192}