1use 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, ¶ms.payer, params.frame_id);
47 let body = encode_create_frame_args(params.frame_id, ¶ms.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}