Skip to main content

hopper_native/
system.rs

1//! System program CPI instructions.
2//!
3//! Full coverage of the System program instruction set: account
4//! creation (`CreateAccount`, `CreateAccountWithSeed`), ownership
5//! (`Assign`, `AssignWithSeed`), allocation (`Allocate`,
6//! `AllocateWithSeed`), transfers (`Transfer`, `TransferWithSeed`), and
7//! the durable-nonce family (`AdvanceNonceAccount`,
8//! `WithdrawNonceAccount`, `InitializeNonceAccount`,
9//! `AuthorizeNonceAccount`, `UpgradeNonceAccount`). All builders invoke
10//! via `sol_invoke_signed_c` with zero heap allocation.
11
12use crate::account_view::AccountView;
13use crate::address::Address;
14use crate::error::ProgramError;
15use crate::instruction::{CpiAccount, Signer};
16use crate::ProgramResult;
17
18/// System program address: 11111111111111111111111111111111
19pub const SYSTEM_PROGRAM_ID: Address = Address::new_from_array([
20    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
21]);
22
23/// Recent blockhashes sysvar address (`SysvarRecentB1ockHashes...`),
24/// required by the nonce instructions.
25pub const RECENT_BLOCKHASHES_ID: Address =
26    crate::address!("SysvarRecentB1ockHashes11111111111111111111");
27
28/// Rent sysvar address, required by some nonce instructions.
29pub const RENT_SYSVAR_ID: Address = crate::address!("SysvarRent111111111111111111111111111111111");
30
31/// Maximum byte length of a System-program seed string (Solana
32/// `MAX_SEED_LEN`). `*WithSeed` builders reject longer seeds.
33pub const MAX_SEED_LEN: usize = 32;
34
35// (writable mask, signer mask), indexed in each instruction's account order.
36const CREATE_ACCOUNT_META: (usize, usize) = (0b11, 0b11);
37// `[to, from]`: the reverse of CreateAccount. Both sign; `from` is left out
38// only when no funding account is given (a zero delta keeps it, and the
39// System Program ignores it then).
40const CREATE_ACCOUNT_ALLOW_PREFUND_META: (usize, usize) = (0b11, 0b11);
41const CREATE_ACCOUNT_ALLOW_PREFUND_UNFUNDED_META: (usize, usize) = (0b1, 0b1);
42const TRANSFER_META: (usize, usize) = (0b11, 0b01);
43const ASSIGN_META: (usize, usize) = (0b1, 0b1);
44const ALLOCATE_META: (usize, usize) = (0b1, 0b1);
45const CREATE_ACCOUNT_WITH_SEED_META: (usize, usize) = (0b011, 0b101);
46const ALLOCATE_WITH_SEED_META: (usize, usize) = (0b01, 0b10);
47const ASSIGN_WITH_SEED_META: (usize, usize) = (0b01, 0b10);
48const TRANSFER_WITH_SEED_META: (usize, usize) = (0b101, 0b010);
49const ADVANCE_NONCE_META: (usize, usize) = (0b001, 0b100);
50const WITHDRAW_NONCE_META: (usize, usize) = (0b00011, 0b10000);
51const INITIALIZE_NONCE_META: (usize, usize) = (0b001, 0);
52const AUTHORIZE_NONCE_META: (usize, usize) = (0b01, 0b10);
53const UPGRADE_NONCE_META: (usize, usize) = (0b1, 0);
54
55// ---------------------------------------------------------------------
56
57/// Builder for the system program's CreateAccount instruction.
58pub struct CreateAccount<'a, 'b> {
59    pub from: &'a AccountView<'a>,
60    pub to: &'a AccountView<'a>,
61    pub lamports: u64,
62    pub space: u64,
63    pub owner: &'b Address,
64}
65
66impl CreateAccount<'_, '_> {
67    /// Invoke the CreateAccount instruction (no PDA signers).
68    #[inline]
69    pub fn invoke(&self) -> ProgramResult {
70        self.invoke_signed(&[])
71    }
72
73    /// Invoke the CreateAccount instruction with PDA signers.
74    #[inline]
75    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
76        // Instruction data: u32(0) + u64(lamports) + u64(space) + [u8;32](owner)
77        let mut data = [0u8; 52];
78        // index 0 = CreateAccount (already zero)
79        data[4..12].copy_from_slice(&self.lamports.to_le_bytes());
80        data[12..20].copy_from_slice(&self.space.to_le_bytes());
81        data[20..52].copy_from_slice(self.owner.as_array());
82
83        let accounts = [CpiAccount::from(self.from), CpiAccount::from(self.to)];
84
85        invoke_system(
86            &data,
87            &accounts,
88            CREATE_ACCOUNT_META.0,
89            CREATE_ACCOUNT_META.1,
90            signers,
91        )
92    }
93}
94
95// ---------------------------------------------------------------------
96
97/// Builder for the system program's `CreateAccountAllowPrefund` instruction
98/// (tag 13).
99///
100/// Unlike [`CreateAccount`], the target may already hold lamports: the
101/// System Program allocates, assigns, and then transfers `lamports` from the
102/// funding account as a delta on top of the existing balance, so callers pass
103/// `required.saturating_sub(current)`. The account order is `[to, from]`,
104/// the reverse of `CreateAccount`. A funding account is sent whenever one is
105/// given, even with a zero delta (the System Program ignores it then), so a
106/// caller that always names a payer links one CPI body; `from` is left out
107/// of the instruction only when `funding` is `None`. The `to` account must
108/// sign (or be a PDA in `signers`) and must be System-owned with no data. Feature gate `6sPDzwyARRExKH52LECxcGoqziH8G7SZofwuxi8Ja331`,
109/// active on mainnet-beta since slot 422,928,004 (2026-05-29) and on devnet
110/// and testnet; the System Program rejects the tag where it is inactive.
111pub struct CreateAccountAllowPrefund<'a, 'b> {
112    pub to: &'a AccountView<'a>,
113    /// Funding account and lamport delta. `None` omits the payer from the
114    /// instruction; a zero delta keeps it (ignored by the System Program).
115    pub funding: Option<(&'a AccountView<'a>, u64)>,
116    pub space: u64,
117    pub owner: &'b Address,
118}
119
120impl CreateAccountAllowPrefund<'_, '_> {
121    /// Invoke without PDA signers.
122    #[inline]
123    pub fn invoke(&self) -> ProgramResult {
124        self.invoke_signed(&[])
125    }
126
127    /// Invoke with PDA signers.
128    #[inline]
129    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
130        let (from, lamports) = match self.funding {
131            Some((from, lamports)) => (Some(from), lamports),
132            None => (None, 0),
133        };
134        // Instruction data: u32(13) + u64(lamports) + u64(space) + [u8;32](owner)
135        let mut data = [0u8; 52];
136        data[0..4].copy_from_slice(&13u32.to_le_bytes());
137        data[4..12].copy_from_slice(&lamports.to_le_bytes());
138        data[12..20].copy_from_slice(&self.space.to_le_bytes());
139        data[20..52].copy_from_slice(self.owner.as_array());
140
141        match from {
142            Some(from) => {
143                let accounts = [CpiAccount::from(self.to), CpiAccount::from(from)];
144                invoke_system(
145                    &data,
146                    &accounts,
147                    CREATE_ACCOUNT_ALLOW_PREFUND_META.0,
148                    CREATE_ACCOUNT_ALLOW_PREFUND_META.1,
149                    signers,
150                )
151            }
152            None => {
153                let accounts = [CpiAccount::from(self.to)];
154                invoke_system(
155                    &data,
156                    &accounts,
157                    CREATE_ACCOUNT_ALLOW_PREFUND_UNFUNDED_META.0,
158                    CREATE_ACCOUNT_ALLOW_PREFUND_UNFUNDED_META.1,
159                    signers,
160                )
161            }
162        }
163    }
164}
165
166// ---------------------------------------------------------------------
167
168/// Builder for the system program's Transfer instruction.
169pub struct Transfer<'a> {
170    pub from: &'a AccountView<'a>,
171    pub to: &'a AccountView<'a>,
172    pub lamports: u64,
173}
174
175impl Transfer<'_> {
176    /// Invoke the Transfer instruction (no PDA signers).
177    #[inline]
178    pub fn invoke(&self) -> ProgramResult {
179        self.invoke_signed(&[])
180    }
181
182    /// Invoke the Transfer instruction with PDA signers.
183    #[inline]
184    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
185        // Instruction data: u32(2) + u64(lamports)
186        let mut data = [0u8; 12];
187        data[0] = 2;
188        data[4..12].copy_from_slice(&self.lamports.to_le_bytes());
189
190        let accounts = [CpiAccount::from(self.from), CpiAccount::from(self.to)];
191
192        invoke_system(&data, &accounts, TRANSFER_META.0, TRANSFER_META.1, signers)
193    }
194}
195
196// ---------------------------------------------------------------------
197
198/// Builder for the system program's Assign instruction.
199pub struct Assign<'a, 'b> {
200    pub account: &'a AccountView<'a>,
201    pub owner: &'b Address,
202}
203
204impl Assign<'_, '_> {
205    /// Invoke the Assign instruction (no PDA signers).
206    #[inline]
207    pub fn invoke(&self) -> ProgramResult {
208        self.invoke_signed(&[])
209    }
210
211    /// Invoke the Assign instruction with PDA signers.
212    #[inline]
213    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
214        // Instruction data: u32(1) + [u8;32](owner)
215        let mut data = [0u8; 36];
216        data[0] = 1;
217        data[4..36].copy_from_slice(self.owner.as_array());
218
219        let accounts = [CpiAccount::from(self.account)];
220
221        invoke_system(&data, &accounts, ASSIGN_META.0, ASSIGN_META.1, signers)
222    }
223}
224
225// ---------------------------------------------------------------------
226
227/// Builder for the system program's Allocate instruction.
228pub struct Allocate<'a> {
229    pub account: &'a AccountView<'a>,
230    pub space: u64,
231}
232
233impl Allocate<'_> {
234    /// Invoke the Allocate instruction (no PDA signers).
235    #[inline]
236    pub fn invoke(&self) -> ProgramResult {
237        self.invoke_signed(&[])
238    }
239
240    /// Invoke the Allocate instruction with PDA signers.
241    #[inline]
242    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
243        // Instruction data: u32(8) + u64(space)
244        let mut data = [0u8; 12];
245        data[0] = 8;
246        data[4..12].copy_from_slice(&self.space.to_le_bytes());
247
248        let accounts = [CpiAccount::from(self.account)];
249
250        invoke_system(&data, &accounts, ALLOCATE_META.0, ALLOCATE_META.1, signers)
251    }
252}
253
254// ---------------------------------------------------------------------
255//  Seeded variants (CreateAccountWithSeed / AllocateWithSeed /
256//  AssignWithSeed / TransferWithSeed). The seed is a bincode `String`:
257//  a u64-LE length prefix followed by the UTF-8 bytes.
258// ---------------------------------------------------------------------
259
260/// Builder for `CreateAccountWithSeed`.
261///
262/// `to` must equal `create_with_seed(base.key, seed, owner)`. The `base`
263/// account is a read-only signer used to derive the new address.
264pub struct CreateAccountWithSeed<'a, 'b> {
265    pub from: &'a AccountView<'a>,
266    pub to: &'a AccountView<'a>,
267    pub base: &'a AccountView<'a>,
268    pub seed: &'b [u8],
269    pub lamports: u64,
270    pub space: u64,
271    pub owner: &'b Address,
272}
273
274impl CreateAccountWithSeed<'_, '_> {
275    /// Invoke (no PDA signers).
276    #[inline]
277    pub fn invoke(&self) -> ProgramResult {
278        self.invoke_signed(&[])
279    }
280
281    /// Invoke with PDA signers.
282    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
283        if self.seed.len() > MAX_SEED_LEN {
284            return Err(ProgramError::MaxSeedLengthExceeded);
285        }
286        // u32(3) + base[32] + u64(seed_len) + seed + u64(lamports)
287        //   + u64(space) + owner[32]
288        let mut data = [0u8; 4 + 32 + 8 + MAX_SEED_LEN + 8 + 8 + 32];
289        data[0] = 3;
290        let mut n = 4;
291        data[n..n + 32].copy_from_slice(self.base.address().as_array());
292        n += 32;
293        data[n..n + 8].copy_from_slice(&(self.seed.len() as u64).to_le_bytes());
294        n += 8;
295        data[n..n + self.seed.len()].copy_from_slice(self.seed);
296        n += self.seed.len();
297        data[n..n + 8].copy_from_slice(&self.lamports.to_le_bytes());
298        n += 8;
299        data[n..n + 8].copy_from_slice(&self.space.to_le_bytes());
300        n += 8;
301        data[n..n + 32].copy_from_slice(self.owner.as_array());
302        n += 32;
303
304        let accounts = [
305            CpiAccount::from(self.from),
306            CpiAccount::from(self.to),
307            CpiAccount::from(self.base),
308        ];
309        invoke_system(
310            &data[..n],
311            &accounts,
312            CREATE_ACCOUNT_WITH_SEED_META.0,
313            CREATE_ACCOUNT_WITH_SEED_META.1,
314            signers,
315        )
316    }
317}
318
319/// Builder for `AllocateWithSeed`.
320pub struct AllocateWithSeed<'a, 'b> {
321    pub account: &'a AccountView<'a>,
322    pub base: &'a AccountView<'a>,
323    pub seed: &'b [u8],
324    pub space: u64,
325    pub owner: &'b Address,
326}
327
328impl AllocateWithSeed<'_, '_> {
329    /// Invoke (no PDA signers).
330    #[inline]
331    pub fn invoke(&self) -> ProgramResult {
332        self.invoke_signed(&[])
333    }
334
335    /// Invoke with PDA signers.
336    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
337        if self.seed.len() > MAX_SEED_LEN {
338            return Err(ProgramError::MaxSeedLengthExceeded);
339        }
340        // u32(9) + base[32] + u64(seed_len) + seed + u64(space) + owner[32]
341        let mut data = [0u8; 4 + 32 + 8 + MAX_SEED_LEN + 8 + 32];
342        data[0] = 9;
343        let mut n = 4;
344        data[n..n + 32].copy_from_slice(self.base.address().as_array());
345        n += 32;
346        data[n..n + 8].copy_from_slice(&(self.seed.len() as u64).to_le_bytes());
347        n += 8;
348        data[n..n + self.seed.len()].copy_from_slice(self.seed);
349        n += self.seed.len();
350        data[n..n + 8].copy_from_slice(&self.space.to_le_bytes());
351        n += 8;
352        data[n..n + 32].copy_from_slice(self.owner.as_array());
353        n += 32;
354
355        let accounts = [CpiAccount::from(self.account), CpiAccount::from(self.base)];
356        invoke_system(
357            &data[..n],
358            &accounts,
359            ALLOCATE_WITH_SEED_META.0,
360            ALLOCATE_WITH_SEED_META.1,
361            signers,
362        )
363    }
364}
365
366/// Builder for `AssignWithSeed`.
367pub struct AssignWithSeed<'a, 'b> {
368    pub account: &'a AccountView<'a>,
369    pub base: &'a AccountView<'a>,
370    pub seed: &'b [u8],
371    pub owner: &'b Address,
372}
373
374impl AssignWithSeed<'_, '_> {
375    /// Invoke (no PDA signers).
376    #[inline]
377    pub fn invoke(&self) -> ProgramResult {
378        self.invoke_signed(&[])
379    }
380
381    /// Invoke with PDA signers.
382    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
383        if self.seed.len() > MAX_SEED_LEN {
384            return Err(ProgramError::MaxSeedLengthExceeded);
385        }
386        // u32(10) + base[32] + u64(seed_len) + seed + owner[32]
387        let mut data = [0u8; 4 + 32 + 8 + MAX_SEED_LEN + 32];
388        data[0] = 10;
389        let mut n = 4;
390        data[n..n + 32].copy_from_slice(self.base.address().as_array());
391        n += 32;
392        data[n..n + 8].copy_from_slice(&(self.seed.len() as u64).to_le_bytes());
393        n += 8;
394        data[n..n + self.seed.len()].copy_from_slice(self.seed);
395        n += self.seed.len();
396        data[n..n + 32].copy_from_slice(self.owner.as_array());
397        n += 32;
398
399        let accounts = [CpiAccount::from(self.account), CpiAccount::from(self.base)];
400        invoke_system(
401            &data[..n],
402            &accounts,
403            ASSIGN_WITH_SEED_META.0,
404            ASSIGN_WITH_SEED_META.1,
405            signers,
406        )
407    }
408}
409
410/// Builder for `TransferWithSeed`.
411///
412/// Moves lamports from a `from` account that is itself derived from
413/// `base` + `from_seed` + `from_owner`.
414pub struct TransferWithSeed<'a, 'b> {
415    pub from: &'a AccountView<'a>,
416    pub base: &'a AccountView<'a>,
417    pub to: &'a AccountView<'a>,
418    pub lamports: u64,
419    pub from_seed: &'b [u8],
420    pub from_owner: &'b Address,
421}
422
423impl TransferWithSeed<'_, '_> {
424    /// Invoke (no PDA signers).
425    #[inline]
426    pub fn invoke(&self) -> ProgramResult {
427        self.invoke_signed(&[])
428    }
429
430    /// Invoke with PDA signers.
431    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
432        if self.from_seed.len() > MAX_SEED_LEN {
433            return Err(ProgramError::MaxSeedLengthExceeded);
434        }
435        // u32(11) + u64(lamports) + u64(seed_len) + seed + from_owner[32]
436        let mut data = [0u8; 4 + 8 + 8 + MAX_SEED_LEN + 32];
437        data[0] = 11;
438        let mut n = 4;
439        data[n..n + 8].copy_from_slice(&self.lamports.to_le_bytes());
440        n += 8;
441        data[n..n + 8].copy_from_slice(&(self.from_seed.len() as u64).to_le_bytes());
442        n += 8;
443        data[n..n + self.from_seed.len()].copy_from_slice(self.from_seed);
444        n += self.from_seed.len();
445        data[n..n + 32].copy_from_slice(self.from_owner.as_array());
446        n += 32;
447
448        let accounts = [
449            CpiAccount::from(self.from),
450            CpiAccount::from(self.base),
451            CpiAccount::from(self.to),
452        ];
453        invoke_system(
454            &data[..n],
455            &accounts,
456            TRANSFER_WITH_SEED_META.0,
457            TRANSFER_WITH_SEED_META.1,
458            signers,
459        )
460    }
461}
462
463// ---------------------------------------------------------------------
464//  Durable nonce family.
465// ---------------------------------------------------------------------
466
467/// Builder for `AdvanceNonceAccount` (instruction 4).
468///
469/// Accounts: `[nonce (writable), recent_blockhashes_sysvar, authority (signer)]`.
470pub struct AdvanceNonceAccount<'a> {
471    pub nonce: &'a AccountView<'a>,
472    pub recent_blockhashes: &'a AccountView<'a>,
473    pub authority: &'a AccountView<'a>,
474}
475
476impl AdvanceNonceAccount<'_> {
477    /// Invoke (no PDA signers).
478    #[inline]
479    pub fn invoke(&self) -> ProgramResult {
480        self.invoke_signed(&[])
481    }
482
483    /// Invoke with PDA signers.
484    #[inline]
485    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
486        let data = [4u8, 0, 0, 0];
487        let accounts = [
488            CpiAccount::from(self.nonce),
489            CpiAccount::from(self.recent_blockhashes),
490            CpiAccount::from(self.authority),
491        ];
492        invoke_system(
493            &data,
494            &accounts,
495            ADVANCE_NONCE_META.0,
496            ADVANCE_NONCE_META.1,
497            signers,
498        )
499    }
500}
501
502/// Builder for `WithdrawNonceAccount` (instruction 5).
503///
504/// Accounts: `[nonce (writable), to (writable), recent_blockhashes_sysvar,
505/// rent_sysvar, authority (signer)]`.
506pub struct WithdrawNonceAccount<'a> {
507    pub nonce: &'a AccountView<'a>,
508    pub to: &'a AccountView<'a>,
509    pub recent_blockhashes: &'a AccountView<'a>,
510    pub rent: &'a AccountView<'a>,
511    pub authority: &'a AccountView<'a>,
512    pub lamports: u64,
513}
514
515impl WithdrawNonceAccount<'_> {
516    /// Invoke (no PDA signers).
517    #[inline]
518    pub fn invoke(&self) -> ProgramResult {
519        self.invoke_signed(&[])
520    }
521
522    /// Invoke with PDA signers.
523    #[inline]
524    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
525        let mut data = [0u8; 12];
526        data[0] = 5;
527        data[4..12].copy_from_slice(&self.lamports.to_le_bytes());
528        let accounts = [
529            CpiAccount::from(self.nonce),
530            CpiAccount::from(self.to),
531            CpiAccount::from(self.recent_blockhashes),
532            CpiAccount::from(self.rent),
533            CpiAccount::from(self.authority),
534        ];
535        invoke_system(
536            &data,
537            &accounts,
538            WITHDRAW_NONCE_META.0,
539            WITHDRAW_NONCE_META.1,
540            signers,
541        )
542    }
543}
544
545/// Builder for `InitializeNonceAccount` (instruction 6).
546///
547/// Accounts: `[nonce (writable), recent_blockhashes_sysvar, rent_sysvar]`.
548pub struct InitializeNonceAccount<'a, 'b> {
549    pub nonce: &'a AccountView<'a>,
550    pub recent_blockhashes: &'a AccountView<'a>,
551    pub rent: &'a AccountView<'a>,
552    pub authority: &'b Address,
553}
554
555impl InitializeNonceAccount<'_, '_> {
556    /// Invoke (no PDA signers).
557    #[inline]
558    pub fn invoke(&self) -> ProgramResult {
559        self.invoke_signed(&[])
560    }
561
562    /// Invoke with PDA signers.
563    #[inline]
564    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
565        let mut data = [0u8; 36];
566        data[0] = 6;
567        data[4..36].copy_from_slice(self.authority.as_array());
568        let accounts = [
569            CpiAccount::from(self.nonce),
570            CpiAccount::from(self.recent_blockhashes),
571            CpiAccount::from(self.rent),
572        ];
573        invoke_system(
574            &data,
575            &accounts,
576            INITIALIZE_NONCE_META.0,
577            INITIALIZE_NONCE_META.1,
578            signers,
579        )
580    }
581}
582
583/// Builder for `AuthorizeNonceAccount` (instruction 7).
584///
585/// Accounts: `[nonce (writable), current_authority (signer)]`.
586pub struct AuthorizeNonceAccount<'a, 'b> {
587    pub nonce: &'a AccountView<'a>,
588    pub authority: &'a AccountView<'a>,
589    pub new_authority: &'b Address,
590}
591
592impl AuthorizeNonceAccount<'_, '_> {
593    /// Invoke (no PDA signers).
594    #[inline]
595    pub fn invoke(&self) -> ProgramResult {
596        self.invoke_signed(&[])
597    }
598
599    /// Invoke with PDA signers.
600    #[inline]
601    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
602        let mut data = [0u8; 36];
603        data[0] = 7;
604        data[4..36].copy_from_slice(self.new_authority.as_array());
605        let accounts = [
606            CpiAccount::from(self.nonce),
607            CpiAccount::from(self.authority),
608        ];
609        invoke_system(
610            &data,
611            &accounts,
612            AUTHORIZE_NONCE_META.0,
613            AUTHORIZE_NONCE_META.1,
614            signers,
615        )
616    }
617}
618
619/// Builder for `UpgradeNonceAccount` (instruction 12).
620///
621/// Accounts: `[nonce (writable)]`.
622pub struct UpgradeNonceAccount<'a> {
623    pub nonce: &'a AccountView<'a>,
624}
625
626impl UpgradeNonceAccount<'_> {
627    /// Invoke (no PDA signers).
628    #[inline]
629    pub fn invoke(&self) -> ProgramResult {
630        let data = [12u8, 0, 0, 0];
631        let accounts = [CpiAccount::from(self.nonce)];
632        invoke_system(
633            &data,
634            &accounts,
635            UPGRADE_NONCE_META.0,
636            UPGRADE_NONCE_META.1,
637            &[],
638        )
639    }
640}
641
642// ---------------------------------------------------------------------
643
644/// Build an InstructionView<'_, '_, '_, '_> to the system program and invoke.
645#[inline]
646fn invoke_system<'a, const ACCOUNTS: usize>(
647    data: &[u8],
648    accounts: &[CpiAccount<'a>; ACCOUNTS],
649    writable_mask: usize,
650    signer_mask: usize,
651    signers: &[Signer<'_, '_>],
652) -> ProgramResult {
653    crate::cpi::invoke_specialized_signed(
654        &SYSTEM_PROGRAM_ID,
655        data,
656        accounts,
657        writable_mask,
658        signer_mask,
659        signers,
660    )
661}
662
663/// Compatibility re-exports matching `pinocchio_system::instructions::*`,
664/// extended with Hopper's WithSeed and durable-nonce coverage.
665pub mod instructions {
666    pub use super::{
667        AdvanceNonceAccount, Allocate, AllocateWithSeed, Assign, AssignWithSeed,
668        AuthorizeNonceAccount, CreateAccount, CreateAccountAllowPrefund, CreateAccountWithSeed,
669        InitializeNonceAccount, Transfer, TransferWithSeed, UpgradeNonceAccount,
670        WithdrawNonceAccount,
671    };
672}
673
674// ---------------------------------------------------------------------
675//  Typed durable-nonce account reader.
676//
677//  The account is a versioned enum wrapping a state enum. The byte
678//  layout for the current (V1, Initialized) form is:
679//
680//    bytes 0..4    version tag  (u32 LE; 1 = Current)
681//    bytes 4..8    state tag    (u32 LE; 1 = Initialized)
682//    bytes 8..40   authority    (Pubkey)
683//    bytes 40..72  durable nonce (the stored blockhash)
684//    bytes 72..80  fee_calculator.lamports_per_signature (u64 LE)
685// ---------------------------------------------------------------------
686
687/// Minimum byte length of an initialized durable-nonce account.
688pub const NONCE_ACCOUNT_LEN: usize = 80;
689
690/// Nonce account version tag for the current format.
691pub const NONCE_VERSION_CURRENT: u32 = 1;
692
693/// Nonce state tag for the initialized form.
694pub const NONCE_STATE_INITIALIZED: u32 = 1;
695
696/// Typed, zero-copy view over an initialized durable-nonce account.
697#[derive(Clone, Copy, Debug)]
698pub struct NonceState<'a> {
699    data: &'a [u8],
700}
701
702impl<'a> NonceState<'a> {
703    /// Parse an initialized nonce account from raw account data.
704    ///
705    /// Returns `Err(InvalidAccountData)` if the buffer is too short, the
706    /// version is not `Current`, or the state is not `Initialized`.
707    #[inline]
708    pub fn from_account_data(data: &'a [u8]) -> Result<Self, ProgramError> {
709        if data.len() < NONCE_ACCOUNT_LEN {
710            return Err(ProgramError::AccountDataTooSmall);
711        }
712        let version = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
713        let state = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
714        if version != NONCE_VERSION_CURRENT || state != NONCE_STATE_INITIALIZED {
715            return Err(ProgramError::InvalidAccountData);
716        }
717        Ok(Self { data })
718    }
719
720    /// The nonce authority allowed to advance/withdraw.
721    #[inline]
722    pub fn authority(&self) -> &'a Address {
723        // SAFETY: `from_account_data` verified `data.len() >= 80`; bytes
724        // 8..40 are a 32-byte address with alignment 1.
725        unsafe { &*(self.data.as_ptr().add(8) as *const Address) }
726    }
727
728    /// The stored durable nonce (a recent blockhash, used as the tx nonce).
729    #[inline]
730    pub fn durable_nonce(&self) -> &'a [u8; 32] {
731        // SAFETY: bytes 40..72 are present per the length check above.
732        unsafe { &*(self.data.as_ptr().add(40) as *const [u8; 32]) }
733    }
734
735    /// The fee rate (`lamports_per_signature`) captured with the nonce.
736    #[inline]
737    pub fn lamports_per_signature(&self) -> u64 {
738        let b = &self.data[72..80];
739        u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
740    }
741}