1use crate::ephem::utils::accounts_to_indices;
2use crate::solana_compat::solana::{
3 invoke, AccountInfo, AccountMeta, Instruction, ProgramResult, Pubkey,
4};
5use magicblock_magic_program_api::args::{
6 ActionArgs, BaseActionArgs, CommitAndUndelegateArgs, CommitTypeArgs, MagicBaseIntentArgs,
7 ShortAccountMeta, UndelegateTypeArgs,
8};
9use magicblock_magic_program_api::instruction::MagicBlockInstruction;
10use std::collections::HashMap;
11
12const EXPECTED_KEY_MSG: &str = "Key expected to exist!";
13
14pub struct MagicInstructionBuilder<'info> {
16 pub payer: AccountInfo<'info>,
17 pub magic_context: AccountInfo<'info>,
18 pub magic_program: AccountInfo<'info>,
19 pub magic_action: MagicAction<'info>,
20}
21
22impl<'info> MagicInstructionBuilder<'info> {
23 pub fn build(self) -> (Vec<AccountInfo<'info>>, Instruction) {
25 let mut all_accounts = vec![self.payer, self.magic_context];
27 self.magic_action.collect_accounts(&mut all_accounts);
29 let indices_map = utils::filter_duplicates_with_map(&mut all_accounts);
31
32 let args = self.magic_action.build_args(&indices_map);
34 let accounts_meta = all_accounts
36 .iter()
37 .map(|account| AccountMeta {
38 pubkey: *account.key,
39 is_signer: account.is_signer,
40 is_writable: account.is_writable,
41 })
42 .collect();
43
44 (
45 all_accounts,
46 Instruction::new_with_bincode(
47 *self.magic_program.key,
48 &MagicBlockInstruction::ScheduleBaseIntent(args),
49 accounts_meta,
50 ),
51 )
52 }
53
54 pub fn build_and_invoke(self) -> ProgramResult {
56 let (accounts, ix) = self.build();
57 invoke(&ix, &accounts)
58 }
59}
60
61pub enum MagicAction<'info> {
63 BaseActions(Vec<CallHandler<'info>>),
64 Commit(CommitType<'info>),
65 CommitAndUndelegate(CommitAndUndelegate<'info>),
66}
67
68impl<'info> MagicAction<'info> {
69 fn collect_accounts(&self, accounts_container: &mut Vec<AccountInfo<'info>>) {
72 match self {
73 MagicAction::BaseActions(call_handlers) => call_handlers
74 .iter()
75 .for_each(|call_handler| call_handler.collect_accounts(accounts_container)),
76 MagicAction::Commit(commit_type) => commit_type.collect_accounts(accounts_container),
77 MagicAction::CommitAndUndelegate(commit_and_undelegate) => {
78 commit_and_undelegate.collect_accounts(accounts_container)
79 }
80 }
81 }
82
83 fn build_args(self, indices_map: &HashMap<Pubkey, u8>) -> MagicBaseIntentArgs {
85 match self {
86 MagicAction::BaseActions(call_handlers) => {
87 let call_handlers_args = call_handlers
88 .into_iter()
89 .map(|call_handler| call_handler.into_args(indices_map))
90 .collect();
91 MagicBaseIntentArgs::BaseActions(call_handlers_args)
92 }
93 MagicAction::Commit(value) => MagicBaseIntentArgs::Commit(value.into_args(indices_map)),
94 MagicAction::CommitAndUndelegate(value) => {
95 MagicBaseIntentArgs::CommitAndUndelegate(value.into_args(indices_map))
96 }
97 }
98 }
99}
100
101pub enum CommitType<'info> {
103 Standalone(Vec<AccountInfo<'info>>), WithHandler {
107 commited_accounts: Vec<AccountInfo<'info>>,
108 call_handlers: Vec<CallHandler<'info>>,
109 },
110}
111
112impl<'info> CommitType<'info> {
113 pub fn commited_accounts(&self) -> &[AccountInfo<'info>] {
114 match self {
115 Self::Standalone(commited_accounts) => commited_accounts,
116 Self::WithHandler {
117 commited_accounts, ..
118 } => commited_accounts,
119 }
120 }
121
122 fn collect_accounts(&self, accounts_container: &mut Vec<AccountInfo<'info>>) {
123 match self {
124 Self::Standalone(accounts) => accounts_container.extend(accounts.clone()),
125 Self::WithHandler {
126 commited_accounts,
127 call_handlers,
128 } => {
129 accounts_container.extend(commited_accounts.clone());
130 call_handlers
131 .iter()
132 .for_each(|call_handler| call_handler.collect_accounts(accounts_container));
133 }
134 }
135 }
136
137 fn into_args(self, indices_map: &HashMap<Pubkey, u8>) -> CommitTypeArgs {
138 match self {
139 Self::Standalone(accounts) => {
140 let accounts_indices = accounts_to_indices(accounts.as_slice(), indices_map);
141 CommitTypeArgs::Standalone(accounts_indices)
142 }
143 Self::WithHandler {
144 commited_accounts,
145 call_handlers,
146 } => {
147 let commited_accounts_indices =
148 accounts_to_indices(commited_accounts.as_slice(), indices_map);
149 let call_handlers_args = call_handlers
150 .into_iter()
151 .map(|call_handler| call_handler.into_args(indices_map))
152 .collect();
153 CommitTypeArgs::WithBaseActions {
154 committed_accounts: commited_accounts_indices,
155 base_actions: call_handlers_args,
156 }
157 }
158 }
159 }
160}
161
162pub enum UndelegateType<'info> {
165 Standalone,
166 WithHandler(Vec<CallHandler<'info>>),
167}
168
169impl<'info> UndelegateType<'info> {
170 fn collect_accounts(&self, accounts_container: &mut Vec<AccountInfo<'info>>) {
171 match self {
172 Self::Standalone => {}
173 Self::WithHandler(call_handlers) => call_handlers
174 .iter()
175 .for_each(|call_handler| call_handler.collect_accounts(accounts_container)),
176 }
177 }
178
179 fn into_args(self, indices_map: &HashMap<Pubkey, u8>) -> UndelegateTypeArgs {
180 match self {
181 Self::Standalone => UndelegateTypeArgs::Standalone,
182 Self::WithHandler(call_handlers) => {
183 let call_handlers_args = call_handlers
184 .into_iter()
185 .map(|call_handler| call_handler.into_args(indices_map))
186 .collect();
187 UndelegateTypeArgs::WithBaseActions {
188 base_actions: call_handlers_args,
189 }
190 }
191 }
192 }
193}
194
195pub struct CommitAndUndelegate<'info> {
196 pub commit_type: CommitType<'info>,
197 pub undelegate_type: UndelegateType<'info>,
198}
199
200impl<'info> CommitAndUndelegate<'info> {
201 fn collect_accounts(&self, accounts_container: &mut Vec<AccountInfo<'info>>) {
202 self.commit_type.collect_accounts(accounts_container);
203 self.undelegate_type.collect_accounts(accounts_container);
204 }
205
206 fn into_args(self, indices_map: &HashMap<Pubkey, u8>) -> CommitAndUndelegateArgs {
207 let commit_type_args = self.commit_type.into_args(indices_map);
208 let undelegate_type_args = self.undelegate_type.into_args(indices_map);
209 CommitAndUndelegateArgs {
210 commit_type: commit_type_args,
211 undelegate_type: undelegate_type_args,
212 }
213 }
214}
215
216pub struct CallHandler<'info> {
217 pub args: ActionArgs,
218 pub compute_units: u32,
219 pub escrow_authority: AccountInfo<'info>,
220 pub destination_program: Pubkey,
221 pub accounts: Vec<ShortAccountMeta>,
222}
223
224impl<'info> CallHandler<'info> {
225 fn collect_accounts(&self, container: &mut Vec<AccountInfo<'info>>) {
226 container.push(self.escrow_authority.clone());
227 }
228
229 fn into_args(self, indices_map: &HashMap<Pubkey, u8>) -> BaseActionArgs {
230 let escrow_authority_index = indices_map
231 .get(self.escrow_authority.key)
232 .expect(EXPECTED_KEY_MSG);
233
234 BaseActionArgs {
235 args: self.args,
236 compute_units: self.compute_units,
237 destination_program: self.destination_program.to_bytes().into(),
238 escrow_authority: *escrow_authority_index,
239 accounts: self.accounts,
240 }
241 }
242}
243
244#[inline(always)]
246pub fn commit_accounts<'a, 'info>(
247 payer: &'a AccountInfo<'info>,
248 account_infos: Vec<&'a AccountInfo<'info>>,
249 magic_context: &'a AccountInfo<'info>,
250 magic_program: &'a AccountInfo<'info>,
251) -> ProgramResult {
252 let ix = create_schedule_commit_ix(payer, &account_infos, magic_context, magic_program, false);
253 let mut all_accounts = vec![payer.clone(), magic_context.clone()];
254 all_accounts.extend(account_infos.into_iter().cloned());
255 invoke(&ix, &all_accounts)
256}
257
258#[inline(always)]
260pub fn commit_and_undelegate_accounts<'a, 'info>(
261 payer: &'a AccountInfo<'info>,
262 account_infos: Vec<&'a AccountInfo<'info>>,
263 magic_context: &'a AccountInfo<'info>,
264 magic_program: &'a AccountInfo<'info>,
265) -> ProgramResult {
266 let ix = create_schedule_commit_ix(payer, &account_infos, magic_context, magic_program, true);
267 let mut all_accounts = vec![payer.clone(), magic_context.clone()];
268 all_accounts.extend(account_infos.into_iter().cloned());
269 invoke(&ix, &all_accounts)
270}
271
272pub fn create_schedule_commit_ix<'a, 'info>(
273 payer: &'a AccountInfo<'info>,
274 account_infos: &[&'a AccountInfo<'info>],
275 magic_context: &'a AccountInfo<'info>,
276 magic_program: &'a AccountInfo<'info>,
277 allow_undelegation: bool,
278) -> Instruction {
279 let instruction = if allow_undelegation {
280 MagicBlockInstruction::ScheduleCommitAndUndelegate
281 } else {
282 MagicBlockInstruction::ScheduleCommit
283 };
284 let mut account_metas = vec![
285 AccountMeta {
286 pubkey: *payer.key,
287 is_signer: true,
288 is_writable: true,
289 },
290 AccountMeta {
291 pubkey: *magic_context.key,
292 is_signer: false,
293 is_writable: true,
294 },
295 ];
296 account_metas.extend(account_infos.iter().map(|x| AccountMeta {
297 pubkey: *x.key,
298 is_signer: x.is_signer,
299 is_writable: x.is_writable,
300 }));
301 Instruction::new_with_bincode(*magic_program.key, &instruction, account_metas)
302}
303
304mod utils {
305 use crate::ephem::EXPECTED_KEY_MSG;
306 use crate::solana_compat::solana::{AccountInfo, Pubkey};
307 use std::collections::hash_map::Entry;
308 use std::collections::HashMap;
309
310 #[inline(always)]
311 pub fn accounts_to_indices(
312 accounts: &[AccountInfo],
313 indices_map: &HashMap<Pubkey, u8>,
314 ) -> Vec<u8> {
315 accounts
316 .iter()
317 .map(|account| *indices_map.get(account.key).expect(EXPECTED_KEY_MSG))
318 .collect()
319 }
320
321 pub fn filter_duplicates_with_map(container: &mut Vec<AccountInfo>) -> HashMap<Pubkey, u8> {
324 let mut map = HashMap::new();
325 container.retain(|el| match map.entry(*el.key) {
326 Entry::Occupied(_) => false,
327 Entry::Vacant(entry) => {
328 entry.insert(1);
330 true
331 }
332 });
333 container.iter().enumerate().for_each(|(i, account)| {
335 *map.get_mut(account.key).unwrap() = i as u8;
336 });
337
338 map
339 }
340}
341
342#[test]
343fn test_instruction_equality() {
344 let serialized = bincode::serialize(&MagicBlockInstruction::ScheduleCommit).unwrap();
345 assert_eq!(vec![1, 0, 0, 0], serialized);
346
347 let serialized =
348 bincode::serialize(&MagicBlockInstruction::ScheduleCommitAndUndelegate).unwrap();
349 assert_eq!(vec![2, 0, 0, 0], serialized);
350}