Skip to main content

hopper_runtime/
system.rs

1//! Hopper-native System Program CPI builders.
2//!
3//! The API is Hopper-owned (builder pattern over `AccountView` / `Address` /
4//! `Signer`) and execution flows through Hopper's checked native CPI semantics.
5//!
6//! Provides the full System program surface: CreateAccount, Transfer,
7//! Assign, Allocate, the `*WithSeed` variants, and the durable-nonce
8//! family, plus a typed [`NonceState`] reader.
9
10use crate::account::AccountView;
11use crate::address::Address;
12use crate::error::ProgramError;
13use crate::instruction::{InstructionAccount, InstructionView, Signer};
14use crate::ProgramResult;
15
16/// System program address: 11111111111111111111111111111111
17pub const SYSTEM_PROGRAM_ID: Address = Address::new_from_array([
18    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,
19]);
20
21pub use hopper_native::system::{
22    NonceState, MAX_SEED_LEN, NONCE_ACCOUNT_LEN, NONCE_STATE_INITIALIZED, NONCE_VERSION_CURRENT,
23    RECENT_BLOCKHASHES_ID, RENT_SYSVAR_ID,
24};
25
26/// Byte-exact instruction-data encoders for the System program CPI wire
27/// format. System instruction discriminators are 4-byte `u32`
28/// little-endian tags (only the low byte is nonzero for these instructions).
29///
30/// # Why this module is `pub`
31///
32/// The fixed-size System builders, [`CreateAccount`], [`Transfer`],
33/// [`Assign`], [`Allocate`], and the entire durable-nonce family
34/// ([`AdvanceNonceAccount`], [`WithdrawNonceAccount`],
35/// [`InitializeNonceAccount`], [`AuthorizeNonceAccount`],
36/// [`UpgradeNonceAccount`]), each construct their instruction-data buffer by
37/// calling exactly one of these functions before handing the bytes to
38/// [`crate::cpi`]. They are the shipped source of truth for the fixed-size
39/// System wire formats, the exact bytes that leave the program on a CPI.
40///
41/// They are exposed as `#[doc(hidden)] pub` for one reason: so the Kani layout
42/// proofs in the `hopper-token` crate can call the shipped encoders directly
43/// and prove, over fully symbolic inputs, that the emitted bytes carry the
44/// canonical 4-byte discriminator, field offsets, endianness, and total
45/// length. This is deliberately **not** a stability surface: the module is
46/// `#[doc(hidden)]` and may change at any time. Each function is
47/// `#[inline(always)]`, so delegating to it is zero-cost and byte-identical to
48/// the previous inline construction.
49///
50/// Only the variable-length `*WithSeed` instructions
51/// ([`CreateAccountWithSeed`], [`AllocateWithSeed`], [`AssignWithSeed`],
52/// [`TransferWithSeed`]) are outside this proof surface: each splices a
53/// runtime `seed` slice of caller-chosen length into the middle of its
54/// buffer, so its total length is not fixed and it is built inline by its
55/// builder. The durable-nonce family, by contrast, is entirely fixed-size and
56/// seed-free (its authority pubkeys and lamports travel at fixed offsets), so
57/// it is modelled here and proven byte-for-byte like the other fixed-size
58/// instructions.
59#[doc(hidden)]
60pub mod encoders {
61    /// `CreateAccount { lamports, space, owner }`,
62    /// `[0u32 LE][lamports: u64 LE][space: u64 LE][owner: 32 bytes]`
63    /// (52 bytes).
64    #[inline(always)]
65    pub fn encode_create_account(lamports: u64, space: u64, owner: &[u8; 32]) -> [u8; 52] {
66        let mut data = [0u8; 52];
67        data[0..4].copy_from_slice(&0u32.to_le_bytes());
68        data[4..12].copy_from_slice(&lamports.to_le_bytes());
69        data[12..20].copy_from_slice(&space.to_le_bytes());
70        data[20..52].copy_from_slice(owner);
71        data
72    }
73
74    /// `CreateAccountAllowPrefund { lamports, space, owner }`,
75    /// `[13u32 LE][lamports: u64 LE][space: u64 LE][owner: 32 bytes]`
76    /// (52 bytes). Same body as `CreateAccount`; only the tag differs.
77    #[inline(always)]
78    pub fn encode_create_account_allow_prefund(
79        lamports: u64,
80        space: u64,
81        owner: &[u8; 32],
82    ) -> [u8; 52] {
83        let mut data = [0u8; 52];
84        data[0..4].copy_from_slice(&13u32.to_le_bytes());
85        data[4..12].copy_from_slice(&lamports.to_le_bytes());
86        data[12..20].copy_from_slice(&space.to_le_bytes());
87        data[20..52].copy_from_slice(owner);
88        data
89    }
90
91    /// `Transfer { lamports }`, `[2u32 LE][lamports: u64 LE]` (12 bytes).
92    #[inline(always)]
93    pub fn encode_transfer(lamports: u64) -> [u8; 12] {
94        let mut data = [0u8; 12];
95        data[0..4].copy_from_slice(&2u32.to_le_bytes());
96        data[4..12].copy_from_slice(&lamports.to_le_bytes());
97        data
98    }
99
100    /// `Assign { owner }`, `[1u32 LE][owner: 32 bytes]` (36 bytes).
101    #[inline(always)]
102    pub fn encode_assign(owner: &[u8; 32]) -> [u8; 36] {
103        let mut data = [0u8; 36];
104        data[0..4].copy_from_slice(&1u32.to_le_bytes());
105        data[4..36].copy_from_slice(owner);
106        data
107    }
108
109    /// `Allocate { space }`, `[8u32 LE][space: u64 LE]` (12 bytes).
110    #[inline(always)]
111    pub fn encode_allocate(space: u64) -> [u8; 12] {
112        let mut data = [0u8; 12];
113        data[0..4].copy_from_slice(&8u32.to_le_bytes());
114        data[4..12].copy_from_slice(&space.to_le_bytes());
115        data
116    }
117
118    // ── Durable-nonce family (fixed-size, seed-free) ────────────────
119    // Canonical `SystemInstruction` tags: AdvanceNonceAccount = 4,
120    // WithdrawNonceAccount = 5, InitializeNonceAccount = 6,
121    // AuthorizeNonceAccount = 7, UpgradeNonceAccount = 12.
122
123    /// `AdvanceNonceAccount`, `[4u32 LE]` (4 bytes). No instruction-data
124    /// fields; the nonce/blockhashes/authority travel in the account-meta
125    /// list.
126    #[inline(always)]
127    pub fn encode_advance_nonce_account() -> [u8; 4] {
128        4u32.to_le_bytes()
129    }
130
131    /// `WithdrawNonceAccount { lamports }`, `[5u32 LE][lamports: u64 LE]`
132    /// (12 bytes).
133    #[inline(always)]
134    pub fn encode_withdraw_nonce_account(lamports: u64) -> [u8; 12] {
135        let mut data = [0u8; 12];
136        data[0..4].copy_from_slice(&5u32.to_le_bytes());
137        data[4..12].copy_from_slice(&lamports.to_le_bytes());
138        data
139    }
140
141    /// `InitializeNonceAccount { authority }`,
142    /// `[6u32 LE][authority: 32 bytes]` (36 bytes).
143    #[inline(always)]
144    pub fn encode_initialize_nonce_account(authority: &[u8; 32]) -> [u8; 36] {
145        let mut data = [0u8; 36];
146        data[0..4].copy_from_slice(&6u32.to_le_bytes());
147        data[4..36].copy_from_slice(authority);
148        data
149    }
150
151    /// `AuthorizeNonceAccount { new_authority }`,
152    /// `[7u32 LE][new_authority: 32 bytes]` (36 bytes).
153    #[inline(always)]
154    pub fn encode_authorize_nonce_account(new_authority: &[u8; 32]) -> [u8; 36] {
155        let mut data = [0u8; 36];
156        data[0..4].copy_from_slice(&7u32.to_le_bytes());
157        data[4..36].copy_from_slice(new_authority);
158        data
159    }
160
161    /// `UpgradeNonceAccount`, `[12u32 LE]` (4 bytes). No instruction-data
162    /// fields; the nonce account travels in the account-meta list.
163    #[inline(always)]
164    pub fn encode_upgrade_nonce_account() -> [u8; 4] {
165        12u32.to_le_bytes()
166    }
167}
168
169// ---------------------------------------------------------------------
170
171/// Builder for the system program's CreateAccount instruction.
172pub struct CreateAccount<'a, 'b> {
173    pub from: &'a AccountView<'a>,
174    pub to: &'a AccountView<'a>,
175    pub lamports: u64,
176    pub space: u64,
177    pub owner: &'b Address,
178}
179
180impl CreateAccount<'_, '_> {
181    #[inline]
182    pub fn invoke(&self) -> ProgramResult {
183        self.invoke_signed(&[])
184    }
185
186    #[inline]
187    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
188        let data =
189            encoders::encode_create_account(self.lamports, self.space, self.owner.as_array());
190
191        let accounts = [
192            InstructionAccount::writable_signer(self.from.address()),
193            InstructionAccount::writable_signer(self.to.address()),
194        ];
195        let views = [self.from, self.to];
196        let instruction = InstructionView {
197            program_id: &SYSTEM_PROGRAM_ID,
198            data: &data,
199            accounts: &accounts,
200        };
201
202        crate::cpi::invoke_signed(&instruction, &views, signers)
203    }
204}
205
206// ---------------------------------------------------------------------
207
208/// Builder for the system program's `CreateAccountAllowPrefund` instruction
209/// (tag 13).
210///
211/// Unlike [`CreateAccount`], the target may already hold lamports: the
212/// System Program allocates, assigns, and then transfers `lamports` from the
213/// funding account as a delta on top of the existing balance, so callers pass
214/// `required.saturating_sub(current)`. The account order is `[to, from]`,
215/// the reverse of `CreateAccount`. A funding account is sent whenever one is
216/// given, even with a zero delta (the System Program checks for one account
217/// and ignores the second then), so `hopper_init!`, which always names the
218/// payer, links one CPI body instead of a funded and a pre-funded copy (1.4
219/// KiB of `.text` per program, measured 2026-09-21); `from` is left out only
220/// when `funding` is `None`. `to` must sign (or be a PDA in `signers`) and
221/// must be System-owned with no data. The feature gate is active on mainnet-beta, devnet, and
222/// testnet; the System Program rejects the tag with
223/// `InvalidInstructionData` where it is not.
224pub struct CreateAccountAllowPrefund<'a, 'b> {
225    pub to: &'a AccountView<'a>,
226    /// Funding account and lamport delta. `None` omits the payer from the
227    /// instruction; a zero delta keeps it (ignored by the System Program).
228    pub funding: Option<(&'a AccountView<'a>, u64)>,
229    pub space: u64,
230    pub owner: &'b Address,
231}
232
233impl CreateAccountAllowPrefund<'_, '_> {
234    #[inline]
235    pub fn invoke(&self) -> ProgramResult {
236        self.invoke_signed(&[])
237    }
238
239    #[inline]
240    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
241        let data = encoders::encode_create_account_allow_prefund(
242            self.funding.map_or(0, |(_, lamports)| lamports),
243            self.space,
244            self.owner.as_array(),
245        );
246
247        match self.funding {
248            Some((from, _)) => {
249                let accounts = [
250                    InstructionAccount::writable_signer(self.to.address()),
251                    InstructionAccount::writable_signer(from.address()),
252                ];
253                let views = [self.to, from];
254                let instruction = InstructionView {
255                    program_id: &SYSTEM_PROGRAM_ID,
256                    data: &data,
257                    accounts: &accounts,
258                };
259                crate::cpi::invoke_signed(&instruction, &views, signers)
260            }
261            None => {
262                let accounts = [InstructionAccount::writable_signer(self.to.address())];
263                let views = [self.to];
264                let instruction = InstructionView {
265                    program_id: &SYSTEM_PROGRAM_ID,
266                    data: &data,
267                    accounts: &accounts,
268                };
269                crate::cpi::invoke_signed(&instruction, &views, signers)
270            }
271        }
272    }
273}
274
275// ---------------------------------------------------------------------
276
277/// Builder for the system program's Transfer instruction.
278pub struct Transfer<'a> {
279    pub from: &'a AccountView<'a>,
280    pub to: &'a AccountView<'a>,
281    pub lamports: u64,
282}
283
284impl Transfer<'_> {
285    #[inline]
286    pub fn invoke(&self) -> ProgramResult {
287        self.invoke_signed(&[])
288    }
289
290    #[inline]
291    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
292        let data = encoders::encode_transfer(self.lamports);
293
294        let accounts = [
295            InstructionAccount::writable_signer(self.from.address()),
296            InstructionAccount::writable(self.to.address()),
297        ];
298        let views = [self.from, self.to];
299        let instruction = InstructionView {
300            program_id: &SYSTEM_PROGRAM_ID,
301            data: &data,
302            accounts: &accounts,
303        };
304
305        crate::cpi::invoke_signed(&instruction, &views, signers)
306    }
307}
308
309// ---------------------------------------------------------------------
310
311/// Builder for the system program's Assign instruction.
312pub struct Assign<'a, 'b> {
313    pub account: &'a AccountView<'a>,
314    pub owner: &'b Address,
315}
316
317impl Assign<'_, '_> {
318    #[inline]
319    pub fn invoke(&self) -> ProgramResult {
320        self.invoke_signed(&[])
321    }
322
323    #[inline]
324    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
325        let data = encoders::encode_assign(self.owner.as_array());
326
327        let accounts = [InstructionAccount::writable_signer(self.account.address())];
328        let views = [self.account];
329        let instruction = InstructionView {
330            program_id: &SYSTEM_PROGRAM_ID,
331            data: &data,
332            accounts: &accounts,
333        };
334
335        crate::cpi::invoke_signed(&instruction, &views, signers)
336    }
337}
338
339// ---------------------------------------------------------------------
340
341/// Builder for the system program's Allocate instruction.
342pub struct Allocate<'a> {
343    pub account: &'a AccountView<'a>,
344    pub space: u64,
345}
346
347impl Allocate<'_> {
348    #[inline]
349    pub fn invoke(&self) -> ProgramResult {
350        self.invoke_signed(&[])
351    }
352
353    #[inline]
354    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
355        let data = encoders::encode_allocate(self.space);
356
357        let accounts = [InstructionAccount::writable_signer(self.account.address())];
358        let views = [self.account];
359        let instruction = InstructionView {
360            program_id: &SYSTEM_PROGRAM_ID,
361            data: &data,
362            accounts: &accounts,
363        };
364
365        crate::cpi::invoke_signed(&instruction, &views, signers)
366    }
367}
368
369// ---------------------------------------------------------------------
370//  WithSeed variants
371// ---------------------------------------------------------------------
372
373/// Builder for `CreateAccountWithSeed`.
374pub struct CreateAccountWithSeed<'a, 'b> {
375    pub from: &'a AccountView<'a>,
376    pub to: &'a AccountView<'a>,
377    pub base: &'a AccountView<'a>,
378    pub seed: &'b [u8],
379    pub lamports: u64,
380    pub space: u64,
381    pub owner: &'b Address,
382}
383
384impl CreateAccountWithSeed<'_, '_> {
385    #[inline]
386    pub fn invoke(&self) -> ProgramResult {
387        self.invoke_signed(&[])
388    }
389
390    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
391        if self.seed.len() > MAX_SEED_LEN {
392            return Err(ProgramError::MaxSeedLengthExceeded);
393        }
394        let mut data = [0u8; 4 + 32 + 8 + MAX_SEED_LEN + 8 + 8 + 32];
395        data[0] = 3;
396        let mut n = 4;
397        data[n..n + 32].copy_from_slice(self.base.address().as_array());
398        n += 32;
399        data[n..n + 8].copy_from_slice(&(self.seed.len() as u64).to_le_bytes());
400        n += 8;
401        data[n..n + self.seed.len()].copy_from_slice(self.seed);
402        n += self.seed.len();
403        data[n..n + 8].copy_from_slice(&self.lamports.to_le_bytes());
404        n += 8;
405        data[n..n + 8].copy_from_slice(&self.space.to_le_bytes());
406        n += 8;
407        data[n..n + 32].copy_from_slice(self.owner.as_array());
408        n += 32;
409
410        let accounts = [
411            InstructionAccount::writable_signer(self.from.address()),
412            InstructionAccount::writable(self.to.address()),
413            InstructionAccount::readonly_signer(self.base.address()),
414        ];
415        let views = [self.from, self.to, self.base];
416        let instruction = InstructionView {
417            program_id: &SYSTEM_PROGRAM_ID,
418            data: &data[..n],
419            accounts: &accounts,
420        };
421        crate::cpi::invoke_signed(&instruction, &views, signers)
422    }
423}
424
425/// Builder for `AllocateWithSeed`.
426pub struct AllocateWithSeed<'a, 'b> {
427    pub account: &'a AccountView<'a>,
428    pub base: &'a AccountView<'a>,
429    pub seed: &'b [u8],
430    pub space: u64,
431    pub owner: &'b Address,
432}
433
434impl AllocateWithSeed<'_, '_> {
435    #[inline]
436    pub fn invoke(&self) -> ProgramResult {
437        self.invoke_signed(&[])
438    }
439
440    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
441        if self.seed.len() > MAX_SEED_LEN {
442            return Err(ProgramError::MaxSeedLengthExceeded);
443        }
444        let mut data = [0u8; 4 + 32 + 8 + MAX_SEED_LEN + 8 + 32];
445        data[0] = 9;
446        let mut n = 4;
447        data[n..n + 32].copy_from_slice(self.base.address().as_array());
448        n += 32;
449        data[n..n + 8].copy_from_slice(&(self.seed.len() as u64).to_le_bytes());
450        n += 8;
451        data[n..n + self.seed.len()].copy_from_slice(self.seed);
452        n += self.seed.len();
453        data[n..n + 8].copy_from_slice(&self.space.to_le_bytes());
454        n += 8;
455        data[n..n + 32].copy_from_slice(self.owner.as_array());
456        n += 32;
457
458        let accounts = [
459            InstructionAccount::writable(self.account.address()),
460            InstructionAccount::readonly_signer(self.base.address()),
461        ];
462        let views = [self.account, self.base];
463        let instruction = InstructionView {
464            program_id: &SYSTEM_PROGRAM_ID,
465            data: &data[..n],
466            accounts: &accounts,
467        };
468        crate::cpi::invoke_signed(&instruction, &views, signers)
469    }
470}
471
472/// Builder for `AssignWithSeed`.
473pub struct AssignWithSeed<'a, 'b> {
474    pub account: &'a AccountView<'a>,
475    pub base: &'a AccountView<'a>,
476    pub seed: &'b [u8],
477    pub owner: &'b Address,
478}
479
480impl AssignWithSeed<'_, '_> {
481    #[inline]
482    pub fn invoke(&self) -> ProgramResult {
483        self.invoke_signed(&[])
484    }
485
486    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
487        if self.seed.len() > MAX_SEED_LEN {
488            return Err(ProgramError::MaxSeedLengthExceeded);
489        }
490        let mut data = [0u8; 4 + 32 + 8 + MAX_SEED_LEN + 32];
491        data[0] = 10;
492        let mut n = 4;
493        data[n..n + 32].copy_from_slice(self.base.address().as_array());
494        n += 32;
495        data[n..n + 8].copy_from_slice(&(self.seed.len() as u64).to_le_bytes());
496        n += 8;
497        data[n..n + self.seed.len()].copy_from_slice(self.seed);
498        n += self.seed.len();
499        data[n..n + 32].copy_from_slice(self.owner.as_array());
500        n += 32;
501
502        let accounts = [
503            InstructionAccount::writable(self.account.address()),
504            InstructionAccount::readonly_signer(self.base.address()),
505        ];
506        let views = [self.account, self.base];
507        let instruction = InstructionView {
508            program_id: &SYSTEM_PROGRAM_ID,
509            data: &data[..n],
510            accounts: &accounts,
511        };
512        crate::cpi::invoke_signed(&instruction, &views, signers)
513    }
514}
515
516/// Builder for `TransferWithSeed`.
517pub struct TransferWithSeed<'a, 'b> {
518    pub from: &'a AccountView<'a>,
519    pub base: &'a AccountView<'a>,
520    pub to: &'a AccountView<'a>,
521    pub lamports: u64,
522    pub from_seed: &'b [u8],
523    pub from_owner: &'b Address,
524}
525
526impl TransferWithSeed<'_, '_> {
527    #[inline]
528    pub fn invoke(&self) -> ProgramResult {
529        self.invoke_signed(&[])
530    }
531
532    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
533        if self.from_seed.len() > MAX_SEED_LEN {
534            return Err(ProgramError::MaxSeedLengthExceeded);
535        }
536        let mut data = [0u8; 4 + 8 + 8 + MAX_SEED_LEN + 32];
537        data[0] = 11;
538        let mut n = 4;
539        data[n..n + 8].copy_from_slice(&self.lamports.to_le_bytes());
540        n += 8;
541        data[n..n + 8].copy_from_slice(&(self.from_seed.len() as u64).to_le_bytes());
542        n += 8;
543        data[n..n + self.from_seed.len()].copy_from_slice(self.from_seed);
544        n += self.from_seed.len();
545        data[n..n + 32].copy_from_slice(self.from_owner.as_array());
546        n += 32;
547
548        let accounts = [
549            InstructionAccount::writable(self.from.address()),
550            InstructionAccount::readonly_signer(self.base.address()),
551            InstructionAccount::writable(self.to.address()),
552        ];
553        let views = [self.from, self.base, self.to];
554        let instruction = InstructionView {
555            program_id: &SYSTEM_PROGRAM_ID,
556            data: &data[..n],
557            accounts: &accounts,
558        };
559        crate::cpi::invoke_signed(&instruction, &views, signers)
560    }
561}
562
563// ---------------------------------------------------------------------
564//  Durable nonce family
565// ---------------------------------------------------------------------
566
567/// Builder for `AdvanceNonceAccount`.
568pub struct AdvanceNonceAccount<'a> {
569    pub nonce: &'a AccountView<'a>,
570    pub recent_blockhashes: &'a AccountView<'a>,
571    pub authority: &'a AccountView<'a>,
572}
573
574impl AdvanceNonceAccount<'_> {
575    #[inline]
576    pub fn invoke(&self) -> ProgramResult {
577        self.invoke_signed(&[])
578    }
579
580    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
581        let data = encoders::encode_advance_nonce_account();
582        let accounts = [
583            InstructionAccount::writable(self.nonce.address()),
584            InstructionAccount::readonly(self.recent_blockhashes.address()),
585            InstructionAccount::readonly_signer(self.authority.address()),
586        ];
587        let views = [self.nonce, self.recent_blockhashes, self.authority];
588        let instruction = InstructionView {
589            program_id: &SYSTEM_PROGRAM_ID,
590            data: &data,
591            accounts: &accounts,
592        };
593        crate::cpi::invoke_signed(&instruction, &views, signers)
594    }
595}
596
597/// Builder for `WithdrawNonceAccount`.
598pub struct WithdrawNonceAccount<'a> {
599    pub nonce: &'a AccountView<'a>,
600    pub to: &'a AccountView<'a>,
601    pub recent_blockhashes: &'a AccountView<'a>,
602    pub rent: &'a AccountView<'a>,
603    pub authority: &'a AccountView<'a>,
604    pub lamports: u64,
605}
606
607impl WithdrawNonceAccount<'_> {
608    #[inline]
609    pub fn invoke(&self) -> ProgramResult {
610        self.invoke_signed(&[])
611    }
612
613    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
614        let data = encoders::encode_withdraw_nonce_account(self.lamports);
615        let accounts = [
616            InstructionAccount::writable(self.nonce.address()),
617            InstructionAccount::writable(self.to.address()),
618            InstructionAccount::readonly(self.recent_blockhashes.address()),
619            InstructionAccount::readonly(self.rent.address()),
620            InstructionAccount::readonly_signer(self.authority.address()),
621        ];
622        let views = [
623            self.nonce,
624            self.to,
625            self.recent_blockhashes,
626            self.rent,
627            self.authority,
628        ];
629        let instruction = InstructionView {
630            program_id: &SYSTEM_PROGRAM_ID,
631            data: &data,
632            accounts: &accounts,
633        };
634        crate::cpi::invoke_signed(&instruction, &views, signers)
635    }
636}
637
638/// Builder for `InitializeNonceAccount`.
639pub struct InitializeNonceAccount<'a, 'b> {
640    pub nonce: &'a AccountView<'a>,
641    pub recent_blockhashes: &'a AccountView<'a>,
642    pub rent: &'a AccountView<'a>,
643    pub authority: &'b Address,
644}
645
646impl InitializeNonceAccount<'_, '_> {
647    #[inline]
648    pub fn invoke(&self) -> ProgramResult {
649        self.invoke_signed(&[])
650    }
651
652    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
653        let data = encoders::encode_initialize_nonce_account(self.authority.as_array());
654        let accounts = [
655            InstructionAccount::writable(self.nonce.address()),
656            InstructionAccount::readonly(self.recent_blockhashes.address()),
657            InstructionAccount::readonly(self.rent.address()),
658        ];
659        let views = [self.nonce, self.recent_blockhashes, self.rent];
660        let instruction = InstructionView {
661            program_id: &SYSTEM_PROGRAM_ID,
662            data: &data,
663            accounts: &accounts,
664        };
665        crate::cpi::invoke_signed(&instruction, &views, signers)
666    }
667}
668
669/// Builder for `AuthorizeNonceAccount`.
670pub struct AuthorizeNonceAccount<'a, 'b> {
671    pub nonce: &'a AccountView<'a>,
672    pub authority: &'a AccountView<'a>,
673    pub new_authority: &'b Address,
674}
675
676impl AuthorizeNonceAccount<'_, '_> {
677    #[inline]
678    pub fn invoke(&self) -> ProgramResult {
679        self.invoke_signed(&[])
680    }
681
682    pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
683        let data = encoders::encode_authorize_nonce_account(self.new_authority.as_array());
684        let accounts = [
685            InstructionAccount::writable(self.nonce.address()),
686            InstructionAccount::readonly_signer(self.authority.address()),
687        ];
688        let views = [self.nonce, self.authority];
689        let instruction = InstructionView {
690            program_id: &SYSTEM_PROGRAM_ID,
691            data: &data,
692            accounts: &accounts,
693        };
694        crate::cpi::invoke_signed(&instruction, &views, signers)
695    }
696}
697
698/// Builder for `UpgradeNonceAccount`.
699pub struct UpgradeNonceAccount<'a> {
700    pub nonce: &'a AccountView<'a>,
701}
702
703impl UpgradeNonceAccount<'_> {
704    #[inline]
705    pub fn invoke(&self) -> ProgramResult {
706        let data = encoders::encode_upgrade_nonce_account();
707        let accounts = [InstructionAccount::writable(self.nonce.address())];
708        let views = [self.nonce];
709        let instruction = InstructionView {
710            program_id: &SYSTEM_PROGRAM_ID,
711            data: &data,
712            accounts: &accounts,
713        };
714        crate::cpi::invoke_signed(&instruction, &views, &[])
715    }
716}
717
718/// Legacy module-path re-exports.
719pub mod instructions {
720    pub use super::{
721        AdvanceNonceAccount, Allocate, AllocateWithSeed, Assign, AssignWithSeed,
722        AuthorizeNonceAccount, CreateAccount, CreateAccountAllowPrefund, CreateAccountWithSeed,
723        InitializeNonceAccount, Transfer, TransferWithSeed, UpgradeNonceAccount,
724        WithdrawNonceAccount,
725    };
726}
727
728#[cfg(test)]
729mod tests {
730    //! Byte-identity guard for the extracted [`encoders`] module: each shipped
731    //! System encoder must reproduce the exact bytes the builders wrote inline
732    //! before the refactor. The System program dispatches on the 4-byte `u32`
733    //! LE discriminator, so a wrong tag or offset silently routes to a
734    //! different instruction.
735    use super::*;
736
737    const OWNER: [u8; 32] = [
738        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
739        25, 26, 27, 28, 29, 30, 31,
740    ];
741
742    #[test]
743    fn create_account_encoder_matches_pre_refactor_golden_bytes() {
744        // Pre-refactor inline bytes: [0,0,0,0][lamports LE][space LE][owner].
745        let d = encoders::encode_create_account(1, 165, &OWNER);
746        assert_eq!(d.len(), 52);
747        assert_eq!(&d[0..4], &[0, 0, 0, 0]);
748        assert_eq!(&d[4..12], &1u64.to_le_bytes());
749        assert_eq!(&d[12..20], &165u64.to_le_bytes());
750        assert_eq!(&d[20..52], &OWNER);
751    }
752
753    #[test]
754    fn transfer_encoder_matches_pre_refactor_golden_bytes() {
755        // disc 2 (u32 LE) then lamports = 1 (u64 LE).
756        assert_eq!(
757            encoders::encode_transfer(1),
758            [2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
759        );
760    }
761
762    #[test]
763    fn assign_encoder_matches_pre_refactor_golden_bytes() {
764        let d = encoders::encode_assign(&OWNER);
765        assert_eq!(d.len(), 36);
766        assert_eq!(&d[0..4], &[1, 0, 0, 0]);
767        assert_eq!(&d[4..36], &OWNER);
768    }
769
770    #[test]
771    fn allocate_encoder_matches_pre_refactor_golden_bytes() {
772        // disc 8 (u32 LE) then space = 1 (u64 LE).
773        assert_eq!(
774            encoders::encode_allocate(1),
775            [8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
776        );
777    }
778
779    #[test]
780    fn advance_nonce_account_encoder_matches_pre_refactor_golden_bytes() {
781        // Pre-refactor inline bytes: [4, 0, 0, 0].
782        assert_eq!(encoders::encode_advance_nonce_account(), [4, 0, 0, 0]);
783    }
784
785    #[test]
786    fn withdraw_nonce_account_encoder_matches_pre_refactor_golden_bytes() {
787        // disc 5 (u32 LE) then lamports = 1 (u64 LE).
788        assert_eq!(
789            encoders::encode_withdraw_nonce_account(1),
790            [5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
791        );
792    }
793
794    #[test]
795    fn initialize_nonce_account_encoder_matches_pre_refactor_golden_bytes() {
796        // Pre-refactor inline bytes: [6,0,0,0][authority: 32 bytes].
797        let d = encoders::encode_initialize_nonce_account(&OWNER);
798        assert_eq!(d.len(), 36);
799        assert_eq!(&d[0..4], &[6, 0, 0, 0]);
800        assert_eq!(&d[4..36], &OWNER);
801    }
802
803    #[test]
804    fn authorize_nonce_account_encoder_matches_pre_refactor_golden_bytes() {
805        // Pre-refactor inline bytes: [7,0,0,0][new_authority: 32 bytes].
806        let d = encoders::encode_authorize_nonce_account(&OWNER);
807        assert_eq!(d.len(), 36);
808        assert_eq!(&d[0..4], &[7, 0, 0, 0]);
809        assert_eq!(&d[4..36], &OWNER);
810    }
811
812    #[test]
813    fn upgrade_nonce_account_encoder_matches_pre_refactor_golden_bytes() {
814        // Pre-refactor inline bytes: [12, 0, 0, 0].
815        assert_eq!(encoders::encode_upgrade_nonce_account(), [12, 0, 0, 0]);
816    }
817}