jiminy-core 0.17.0

Core systems layer for Jiminy: account layout, zero-copy IO, validation, PDA, sysvar access, math, time checks. Declarative macros for error codes, instruction dispatch, and account uniqueness. no_std, no_alloc, no proc macros, BPF-safe.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
//! Account and instruction validation checks.
//!
//! Every function returns `ProgramResult`: `Ok(())` on pass,
//! an appropriate `ProgramError` variant on failure.

pub mod pda;

use hopper_runtime::{ProgramError, AccountView, Address, ProgramResult};

#[cfg(feature = "programs")]
use crate::programs;

// ── Identity & permissions ───────────────────────────────────────────────────

/// The canonical system program address (all-zero pubkey).
const SYSTEM_PROGRAM_ID: Address = Address::new_from_array([0u8; 32]);

/// Verify the account signed the transaction.
#[inline(always)]
pub fn check_signer(account: &AccountView) -> ProgramResult {
    if !account.is_signer() {
        return Err(ProgramError::MissingRequiredSignature);
    }
    Ok(())
}

/// Verify the account is marked writable in the transaction.
#[inline(always)]
pub fn check_writable(account: &AccountView) -> ProgramResult {
    if !account.is_writable() {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

/// Verify the account is owned by `program_id`.
#[inline(always)]
pub fn check_owner(account: &AccountView, program_id: &Address) -> ProgramResult {
    if !account.owned_by(program_id) {
        return Err(ProgramError::IncorrectProgramId);
    }
    Ok(())
}

/// Verify the account's address equals the expected PDA.
#[inline(always)]
pub fn check_pda(account: &AccountView, expected: &Address) -> ProgramResult {
    if *account.address() != *expected {
        return Err(ProgramError::InvalidSeeds);
    }
    Ok(())
}

/// Verify the account is the canonical system program.
#[inline(always)]
pub fn check_system_program(account: &AccountView) -> ProgramResult {
    if *account.address() != SYSTEM_PROGRAM_ID {
        return Err(ProgramError::IncorrectProgramId);
    }
    Ok(())
}

/// Verify the account has no data (uninitialized). Prevents reinitialization attacks.
#[inline(always)]
pub fn check_uninitialized(account: &AccountView) -> ProgramResult {
    if !account.is_data_empty() {
        return Err(ProgramError::AccountAlreadyInitialized);
    }
    Ok(())
}

/// Verify the account is an executable program.
#[inline(always)]
pub fn check_executable(account: &AccountView) -> ProgramResult {
    if !account.executable() {
        return Err(ProgramError::IncorrectProgramId);
    }
    Ok(())
}

// ── Data shape ───────────────────────────────────────────────────────────────

/// Verify account data is at least `min_len` bytes.
#[inline(always)]
pub fn check_size(data: &[u8], min_len: usize) -> ProgramResult {
    if data.len() < min_len {
        return Err(ProgramError::AccountDataTooSmall);
    }
    Ok(())
}

/// Verify the first byte of account data matches the expected discriminator.
#[inline(always)]
pub fn check_discriminator(data: &[u8], expected: u8) -> ProgramResult {
    if data.is_empty() || data[0] != expected {
        return Err(ProgramError::InvalidAccountData);
    }
    Ok(())
}

/// Combined check: ownership + minimum size + discriminator.
#[inline(always)]
pub fn check_account(
    account: &AccountView,
    program_id: &Address,
    discriminator: u8,
    min_len: usize,
) -> ProgramResult {
    check_owner(account, program_id)?;
    let data = account.try_borrow()?;
    check_size(&data, min_len)?;
    check_discriminator(&data, discriminator)?;
    Ok(())
}

/// Verify the header version byte (`data[1]`) meets a minimum version.
#[inline(always)]
pub fn check_version(data: &[u8], min_version: u8) -> ProgramResult {
    if data.len() < 2 {
        return Err(ProgramError::AccountDataTooSmall);
    }
    if data[1] < min_version {
        return Err(ProgramError::InvalidAccountData);
    }
    Ok(())
}

// ── Keys & addresses ─────────────────────────────────────────────────────────

/// Verify two addresses are equal.
#[inline(always)]
pub fn check_keys_eq(a: &Address, b: &Address) -> ProgramResult {
    if *a != *b {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

/// Verify that a stored address field matches an account's actual address.
///
/// Runtime equivalent of Anchor's `has_one` constraint.
#[inline(always)]
pub fn check_has_one(stored: &Address, account: &AccountView) -> ProgramResult {
    if stored != account.address() {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

// ── Rent & lamports ──────────────────────────────────────────────────────────

/// Approximate minimum lamports for rent exemption at the current mainnet rate.
///
/// Formula: `(ACCOUNT_STORAGE_OVERHEAD + data_len) * lamports_per_byte_year * exemption_threshold`
/// which on mainnet is `(128 + data_len) * 3480 * 2 = (128 + data_len) * 6960`.
///
/// `saturating_*` is deliberately avoided: silently capping at `u64::MAX`
/// would let a caller under-fund an account and then not detect the problem
/// until the runtime rejects the CPI with an opaque rent error. For any
/// data size Solana actually permits (≤10 MiB) the arithmetic cannot
/// overflow u64, so `checked_*` is free in the happy path and correct in
/// the hostile one.
#[inline(always)]
pub fn rent_exempt_min(data_len: usize) -> u64 {
    // `usize as u64` is lossless on every Solana target (32-bit sbf-v1
    // or 64-bit host tests). Any `checked_*` failure indicates the caller
    // passed a nonsensical `data_len` well beyond any account limit.
    128u64
        .checked_add(data_len as u64)
        .and_then(|n| n.checked_mul(6960))
        .unwrap_or(u64::MAX)
}

/// Verify an account holds enough lamports to be rent-exempt for its data size.
#[inline(always)]
pub fn check_rent_exempt(account: &AccountView) -> ProgramResult {
    let data = account.try_borrow()?;
    let min = rent_exempt_min(data.len());
    drop(data);
    if account.lamports() < min {
        return Err(ProgramError::InsufficientFunds);
    }
    Ok(())
}

/// Verify `account` holds at least `min_lamports`.
#[inline(always)]
pub fn check_lamports_gte(account: &AccountView, min_lamports: u64) -> ProgramResult {
    if account.lamports() < min_lamports {
        return Err(ProgramError::InsufficientFunds);
    }
    Ok(())
}

/// Verify an account is fully closed: zero lamports and empty data.
#[inline(always)]
pub fn check_closed(account: &AccountView) -> ProgramResult {
    if account.lamports() != 0 || !account.is_data_empty() {
        return Err(ProgramError::InvalidAccountData);
    }
    Ok(())
}

// ── Instruction data ─────────────────────────────────────────────────────────

/// Verify instruction data is exactly the expected length.
#[inline(always)]
pub fn check_instruction_data_len(data: &[u8], expected_len: usize) -> ProgramResult {
    if data.len() != expected_len {
        return Err(ProgramError::InvalidInstructionData);
    }
    Ok(())
}

/// Verify instruction data has at least N bytes.
#[inline(always)]
pub fn check_instruction_data_min(data: &[u8], min_len: usize) -> ProgramResult {
    if data.len() < min_len {
        return Err(ProgramError::InvalidInstructionData);
    }
    Ok(())
}

// ── Uniqueness ───────────────────────────────────────────────────────────────

/// Verify two accounts have different addresses.
#[inline(always)]
pub fn check_accounts_unique_2(a: &AccountView, b: &AccountView) -> ProgramResult {
    if a.address() == b.address() {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

/// Verify three accounts all have different addresses.
#[inline(always)]
pub fn check_accounts_unique_3(
    a: &AccountView,
    b: &AccountView,
    c: &AccountView,
) -> ProgramResult {
    if a.address() == b.address()
        || a.address() == c.address()
        || b.address() == c.address()
    {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

/// Verify four accounts all have different addresses.
#[inline(always)]
pub fn check_accounts_unique_4(
    a: &AccountView,
    b: &AccountView,
    c: &AccountView,
    d: &AccountView,
) -> ProgramResult {
    if a.address() == b.address()
        || a.address() == c.address()
        || a.address() == d.address()
        || b.address() == c.address()
        || b.address() == d.address()
        || c.address() == d.address()
    {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

// ── Assert helpers (folded from asserts.rs) ──────────────────────────────────

/// Derive a PDA from seeds, verify it matches the account, return the bump.
///
/// Calls `find_program_address` (syscall on-chain).
#[inline(always)]
pub fn assert_pda(
    account: &AccountView,
    seeds: &[&[u8]],
    program_id: &Address,
) -> Result<u8, ProgramError> {
    #[cfg(target_os = "solana")]
    {
        let (derived, bump) = Address::find_program_address(seeds, program_id);
        if derived != *account.address() {
            return Err(ProgramError::InvalidSeeds);
        }
        Ok(bump)
    }
    #[cfg(not(target_os = "solana"))]
    {
        let _ = (account, seeds, program_id);
        Err(ProgramError::InvalidSeeds)
    }
}

/// Verify a PDA matches when the bump is already known. Cheaper, single derivation.
#[inline(always)]
pub fn assert_pda_with_bump(
    account: &AccountView,
    seeds: &[&[u8]],
    bump: u8,
    program_id: &Address,
) -> ProgramResult {
    #[cfg(target_os = "solana")]
    {
        let bump_bytes = [bump];
        let n = seeds.len();
        let mut all_seeds: [&[u8]; 17] = [&[]; 17];
        let mut i = 0;
        while i < n {
            all_seeds[i] = seeds[i];
            i += 1;
        }
        all_seeds[n] = &bump_bytes;

        let derived = Address::create_program_address(&all_seeds[..n + 1], program_id)
            .map_err(|_| ProgramError::InvalidSeeds)?;
        if derived != *account.address() {
            return Err(ProgramError::InvalidSeeds);
        }
        Ok(())
    }
    #[cfg(not(target_os = "solana"))]
    {
        let _ = (account, seeds, bump, program_id);
        Err(ProgramError::InvalidSeeds)
    }
}

/// Verify a PDA derived from an external program's seeds. Returns the bump.
#[inline(always)]
pub fn assert_pda_external(
    account: &AccountView,
    seeds: &[&[u8]],
    program_id: &Address,
) -> Result<u8, ProgramError> {
    assert_pda(account, seeds, program_id)
}

/// Verify the account is the SPL Token program (Token or Token-2022).
#[cfg(feature = "programs")]
#[inline(always)]
pub fn assert_token_program(account: &AccountView) -> ProgramResult {
    if *account.address() != programs::TOKEN && *account.address() != programs::TOKEN_2022 {
        return Err(ProgramError::IncorrectProgramId);
    }
    Ok(())
}

/// Verify an account's address matches an expected address exactly.
#[inline(always)]
pub fn assert_address(account: &AccountView, expected: &Address) -> ProgramResult {
    if *account.address() != *expected {
        return Err(ProgramError::InvalidArgument);
    }
    Ok(())
}

/// Verify an account's address matches a known program id + is executable.
#[inline(always)]
pub fn assert_program(account: &AccountView, expected_program: &Address) -> ProgramResult {
    if *account.address() != *expected_program {
        return Err(ProgramError::IncorrectProgramId);
    }
    if !account.executable() {
        return Err(ProgramError::IncorrectProgramId);
    }
    Ok(())
}

/// Verify an account has never been initialized (lamports == 0).
#[inline(always)]
pub fn assert_not_initialized(account: &AccountView) -> ProgramResult {
    if account.lamports() != 0 {
        return Err(ProgramError::AccountAlreadyInitialized);
    }
    Ok(())
}

// ── Program allowlist ────────────────────────────────────────────────────────

/// Verify the account is owned by one of the programs in `allowed`.
///
/// Use this when accepting accounts from a known set of trusted programs.
/// Rejects anything not in the list.
///
/// ```rust,ignore
/// const ALLOWED: &[Address] = &[PROGRAM_A, PROGRAM_B];
/// check_program_allowed(account, ALLOWED)?;
/// ```
#[inline(always)]
pub fn check_program_allowed(
    account: &AccountView,
    allowed: &[Address],
) -> ProgramResult {
    // `owned_by` is the safe wrapper around the unsafe `owner()` accessor;
    // the hopper runtime already handles the "no aliasing mutable borrow"
    // invariant internally so we never reach for raw `unsafe` here.
    let mut i = 0;
    while i < allowed.len() {
        if account.owned_by(&allowed[i]) {
            return Ok(());
        }
        i += 1;
    }
    Err(ProgramError::IncorrectProgramId)
}