miclockwork_thread_program/instructions/
thread_exec.rs1use anchor_lang::{
2 prelude::*,
3 solana_program::{
4 instruction::Instruction,
5 program::{get_return_data, invoke_signed},
6 },
7 AnchorDeserialize, InstructionData,
8};
9use miclockwork_network_program::state::{Fee, Pool, Worker, WorkerAccount};
10use miclockwork_utils::thread::{SerializableInstruction, ThreadResponse, PAYER_PUBKEY};
11
12use crate::{errors::ClockworkError, state::*};
13
14const POOL_ID: u64 = 0;
16
17pub const TRANSACTION_BASE_FEE_REIMBURSEMENT: u64 = 5_000;
19
20#[derive(Accounts)]
22pub struct ThreadExec<'info> {
23 #[account(
25 mut,
26 seeds = [
27 miclockwork_network_program::state::SEED_FEE,
28 worker.key().as_ref(),
29 ],
30 bump,
31 seeds::program = miclockwork_network_program::ID,
32 has_one = worker,
33 )]
34 pub fee: Account<'info, Fee>,
35
36 #[account(address = Pool::pubkey(POOL_ID))]
38 pub pool: Box<Account<'info, Pool>>,
39
40 #[account(mut)]
42 pub signatory: Signer<'info>,
43
44 #[account(
46 mut,
47 seeds = [
48 SEED_THREAD,
49 thread.authority.as_ref(),
50 thread.id.as_slice(),
51 ],
52 bump = thread.bump,
53 constraint = !thread.paused @ ClockworkError::ThreadPaused,
54 constraint = thread.next_instruction.is_some(),
55 constraint = thread.exec_context.is_some()
56 )]
57 pub thread: Box<Account<'info, Thread>>,
58
59 #[account(address = worker.pubkey())]
61 pub worker: Account<'info, Worker>,
62}
63
64pub fn handler(ctx: Context<ThreadExec>) -> Result<()> {
65 let clock = Clock::get().unwrap();
67 let fee = &mut ctx.accounts.fee;
68 let pool = &ctx.accounts.pool;
69 let signatory = &mut ctx.accounts.signatory;
70 let thread = &mut ctx.accounts.thread;
71 let worker = &ctx.accounts.worker;
72
73 if thread.exec_context.unwrap().last_exec_at == clock.slot
75 && thread.exec_context.unwrap().execs_since_slot >= thread.rate_limit
76 {
77 return Err(ClockworkError::RateLimitExeceeded.into());
78 }
79
80 let signatory_lamports_pre = signatory.lamports();
82
83 let instruction: &mut SerializableInstruction = &mut thread.next_instruction.clone().unwrap();
86
87 for acc in instruction.accounts.iter_mut() {
89 if acc.pubkey.eq(&PAYER_PUBKEY) {
90 acc.pubkey = signatory.key();
91 }
92 }
93
94 invoke_signed(
96 &Instruction::from(&*instruction),
97 ctx.remaining_accounts,
98 &[&[
99 SEED_THREAD,
100 thread.authority.as_ref(),
101 thread.id.as_slice(),
102 &[thread.bump],
103 ]],
104 )?;
105
106 require!(signatory.data_is_empty(), ClockworkError::UnauthorizedWrite);
108
109 let thread_response: Option<ThreadResponse> = match get_return_data() {
111 None => None,
112 Some((program_id, return_data)) => {
113 require!(
114 program_id.eq(&instruction.program_id),
115 ClockworkError::InvalidThreadResponse
116 );
117 ThreadResponse::try_from_slice(return_data.as_slice()).ok()
118 }
119 };
120
121 let mut close_to = None;
123 let mut next_instruction = None;
124 if let Some(thread_response) = thread_response {
125 close_to = thread_response.close_to;
126 next_instruction = thread_response.dynamic_instruction;
127
128 if let Some(trigger) = thread_response.trigger {
130 require!(
131 std::mem::discriminant(&thread.trigger) == std::mem::discriminant(&trigger),
132 ClockworkError::InvalidTriggerVariant
133 );
134 thread.trigger = trigger.clone();
135
136 thread.exec_context = Some(ExecContext {
139 trigger_context: match trigger {
140 Trigger::Account {
141 address: _,
142 offset: _,
143 size: _,
144 } => TriggerContext::Account { data_hash: 0 },
145 _ => thread.exec_context.unwrap().trigger_context,
146 },
147 ..thread.exec_context.unwrap()
148 })
149 }
150 }
151
152 let mut exec_index = thread.exec_context.unwrap().exec_index;
154 if next_instruction.is_none() {
155 if let Some(ix) = thread.instructions.get((exec_index + 1) as usize) {
156 next_instruction = Some(ix.clone());
157 exec_index = exec_index + 1;
158 }
159 }
160
161 if let Some(close_to) = close_to {
163 thread.next_instruction = Some(
164 Instruction {
165 program_id: crate::ID,
166 accounts: crate::accounts::ThreadDelete {
167 authority: thread.key(),
168 close_to,
169 thread: thread.key(),
170 }
171 .to_account_metas(Some(true)),
172 data: crate::instruction::ThreadDelete {}.data(),
173 }
174 .into(),
175 );
176 } else {
177 thread.next_instruction = next_instruction;
178 }
179
180 let should_reimburse_transaction = clock.slot > thread.exec_context.unwrap().last_exec_at;
182 thread.exec_context = Some(ExecContext {
183 exec_index,
184 execs_since_slot: if clock.slot == thread.exec_context.unwrap().last_exec_at {
185 thread
186 .exec_context
187 .unwrap()
188 .execs_since_slot
189 .checked_add(1)
190 .unwrap()
191 } else {
192 1
193 },
194 last_exec_at: clock.slot,
195 ..thread.exec_context.unwrap()
196 });
197
198 let signatory_lamports_post = signatory.lamports();
200 let mut signatory_reimbursement =
201 signatory_lamports_pre.saturating_sub(signatory_lamports_post);
202 if should_reimburse_transaction {
203 signatory_reimbursement = signatory_reimbursement
204 .checked_add(TRANSACTION_BASE_FEE_REIMBURSEMENT)
205 .unwrap();
206 }
207 if signatory_reimbursement.gt(&0) {
208 **thread.to_account_info().try_borrow_mut_lamports()? = thread
209 .to_account_info()
210 .lamports()
211 .checked_sub(signatory_reimbursement)
212 .unwrap();
213 **signatory.to_account_info().try_borrow_mut_lamports()? = signatory
214 .to_account_info()
215 .lamports()
216 .checked_add(signatory_reimbursement)
217 .unwrap();
218 }
219
220 if pool.clone().into_inner().workers.contains(&worker.key()) {
222 **thread.to_account_info().try_borrow_mut_lamports()? = thread
223 .to_account_info()
224 .lamports()
225 .checked_sub(thread.fee)
226 .unwrap();
227 **fee.to_account_info().try_borrow_mut_lamports()? = fee
228 .to_account_info()
229 .lamports()
230 .checked_add(thread.fee)
231 .unwrap();
232 }
233
234 Ok(())
235}