Skip to main content

ifx_sdk/
ix.rs

1//! Solana `Instruction` builders for Ifx frame instructions.
2
3use anchor_lang::AnchorSerialize;
4use ifx_core::wire::{Cpi, Expr, IfElseArm, LetArgs};
5use ifx_core::U8LenVec;
6use solana_sdk::instruction::{AccountMeta, Instruction};
7use solana_sdk::pubkey::Pubkey;
8use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
9
10use crate::constants::{
11    DEFAULT_IFX_PROGRAM_ID, IX_DISC_ASSERT, IX_DISC_ASSERT_MULTI, IX_DISC_CLOSE_FRAME,
12    IX_DISC_CREATE_FRAME, IX_DISC_IF_ELSE, IX_DISC_LET, IX_DISC_PATCHED_CPI, IX_DISC_RESET_FRAME,
13    MAX_ASSERT_MULTI_CONDS, RECOMMENDED_ASSERT_MULTI_MAX,
14};
15use crate::cpi::encode_cpi;
16use crate::error::ScratchError;
17use crate::frame::{encode_create_frame_args, frame_pda};
18use crate::frame_authority::prepend_write_authority_remaining;
19use crate::wire_ix::{AssertMultiArgs, IfElseArgs};
20
21#[derive(Clone, Copy, Debug, Default)]
22pub struct IxOpts {
23    pub program_id: Option<Pubkey>,
24}
25
26fn program_id(opts: IxOpts) -> Pubkey {
27    opts.program_id.unwrap_or(DEFAULT_IFX_PROGRAM_ID)
28}
29
30pub struct CreateFrameParams<'a> {
31    pub payer: Pubkey,
32    pub frame_id: &'a [u8; 32],
33    pub authority: Pubkey,
34    pub tape_len: u32,
35    pub opts: IxOpts,
36}
37
38pub struct CreateFrameResult {
39    pub instruction: Instruction,
40    pub frame: Pubkey,
41    pub frame_bump: u8,
42}
43
44pub fn build_ix_create_frame(params: CreateFrameParams<'_>) -> Result<CreateFrameResult, ScratchError> {
45    let program_id = program_id(params.opts);
46    let (frame, bump) = frame_pda(&program_id, &params.payer, params.frame_id);
47    let body = encode_create_frame_args(params.frame_id, &params.authority, params.tape_len)?;
48    let mut data = Vec::with_capacity(1 + body.len());
49    data.push(IX_DISC_CREATE_FRAME);
50    data.extend(body);
51    Ok(CreateFrameResult {
52        instruction: Instruction {
53            program_id,
54            accounts: vec![
55                AccountMeta::new(params.payer, true),
56                AccountMeta::new(frame, false),
57                AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
58            ],
59            data,
60        },
61        frame,
62        frame_bump: bump,
63    })
64}
65
66pub fn build_ix_reset_frame(
67    frame: Pubkey,
68    authority: Pubkey,
69    opts: IxOpts,
70) -> Instruction {
71    let program_id = program_id(opts);
72    Instruction {
73        program_id,
74        accounts: {
75            let mut accounts = vec![AccountMeta::new(frame, false)];
76            accounts.extend(prepend_write_authority_remaining(&authority, &[]));
77            accounts
78        },
79        data: vec![IX_DISC_RESET_FRAME],
80    }
81}
82
83pub fn build_ix_let(
84    frame: Pubkey,
85    authority: Pubkey,
86    args: &LetArgs,
87    remaining: &[AccountMeta],
88    opts: IxOpts,
89) -> Result<Instruction, ScratchError> {
90    let program_id = program_id(opts);
91    let mut body = Vec::new();
92    args.serialize(&mut body)
93        .map_err(|e| ScratchError::Encode(e.to_string()))?;
94    let mut data = Vec::with_capacity(1 + body.len());
95    data.push(IX_DISC_LET);
96    data.extend(body);
97    Ok(Instruction {
98        program_id,
99        accounts: {
100            let mut accounts = vec![AccountMeta::new(frame, false)];
101            accounts.extend(prepend_write_authority_remaining(&authority, remaining));
102            accounts
103        },
104        data,
105    })
106}
107
108pub fn build_ix_assert(
109    frame: Pubkey,
110    cond: &Expr,
111    opts: IxOpts,
112) -> Result<Instruction, ScratchError> {
113    let program_id = program_id(opts);
114    let mut body = Vec::new();
115    cond.serialize(&mut body)
116        .map_err(|e| ScratchError::Encode(e.to_string()))?;
117    let mut data = Vec::with_capacity(1 + body.len());
118    data.push(IX_DISC_ASSERT);
119    data.extend(body);
120    Ok(Instruction {
121        program_id,
122        accounts: vec![AccountMeta::new_readonly(frame, false)],
123        data,
124    })
125}
126
127pub fn build_ix_assert_multi(
128    frame: Pubkey,
129    args: &AssertMultiArgs,
130    opts: IxOpts,
131) -> Result<Instruction, ScratchError> {
132    let n = args.conds.len();
133    if n == 0 {
134        return Err(ScratchError::Encode(
135            "ifx_assert_multi requires at least one condition".into(),
136        ));
137    }
138    if n > MAX_ASSERT_MULTI_CONDS as usize {
139        return Err(ScratchError::Encode(format!(
140            "ifx_assert_multi supports at most {} conditions (got {n})",
141            MAX_ASSERT_MULTI_CONDS
142        )));
143    }
144    if n > RECOMMENDED_ASSERT_MULTI_MAX as usize {
145        eprintln!(
146            "[ifx] ifx_assert_multi: {n} conditions exceeds recommended \
147             {RECOMMENDED_ASSERT_MULTI_MAX} per ix — split guards or expect higher tx CU"
148        );
149    }
150    let program_id = program_id(opts);
151    let mut body = Vec::new();
152    args.serialize(&mut body)
153        .map_err(|e| ScratchError::Encode(e.to_string()))?;
154    let mut data = Vec::with_capacity(1 + body.len());
155    data.push(IX_DISC_ASSERT_MULTI);
156    data.extend(body);
157    Ok(Instruction {
158        program_id,
159        accounts: vec![AccountMeta::new_readonly(frame, false)],
160        data,
161    })
162}
163
164pub fn build_ix_close_frame(frame: Pubkey, authority: Pubkey, opts: IxOpts) -> Instruction {
165    let program_id = program_id(opts);
166    Instruction {
167        program_id,
168        accounts: vec![
169            AccountMeta::new(authority, true),
170            AccountMeta::new(frame, false),
171        ],
172        data: vec![IX_DISC_CLOSE_FRAME],
173    }
174}
175
176pub fn build_ix_cpi(
177    frame: Pubkey,
178    cpi: &Cpi,
179    remaining: &[AccountMeta],
180    opts: IxOpts,
181) -> Result<Instruction, ScratchError> {
182    let program_id = program_id(opts);
183    let body = encode_cpi(cpi)?;
184    let mut data = Vec::with_capacity(1 + body.len());
185    data.push(IX_DISC_PATCHED_CPI);
186    data.extend(body);
187    let mut accounts = vec![AccountMeta::new(frame, false)];
188    accounts.extend_from_slice(remaining);
189    Ok(Instruction {
190        program_id,
191        accounts,
192        data,
193    })
194}
195
196pub fn build_ix_if_else(
197    frame: Pubkey,
198    args: &IfElseArgs,
199    remaining: &[AccountMeta],
200    opts: IxOpts,
201) -> Result<Instruction, ScratchError> {
202    let program_id = program_id(opts);
203    let mut body = Vec::new();
204    args.serialize(&mut body)
205        .map_err(|e| ScratchError::Encode(e.to_string()))?;
206    let mut data = Vec::with_capacity(1 + body.len());
207    data.push(IX_DISC_IF_ELSE);
208    data.extend(body);
209    let mut accounts = vec![AccountMeta::new_readonly(frame, false)];
210    accounts.extend_from_slice(remaining);
211    Ok(Instruction {
212        program_id,
213        accounts,
214        data,
215    })
216}
217
218pub fn if_else_args_skip_skip() -> IfElseArgs {
219    IfElseArgs {
220        cond: Expr::ConstBool(true),
221        then_arm: IfElseArm::Skip,
222        else_arm: IfElseArm::Skip,
223    }
224}
225
226pub fn let_args_from_bindings(bindings: &[ifx_core::wire::LetBinding]) -> LetArgs {
227    LetArgs {
228        bindings: U8LenVec(bindings.to_vec()),
229    }
230}