antegen_thread_program/instructions/
thread_exec.rs1use crate::{
2 errors::*,
3 state::{decompile_instruction, CompiledInstructionV0, Signal, PAYER_PUBKEY},
4 *,
5};
6use anchor_lang::{
7 prelude::*,
8 solana_program::program::{get_return_data, invoke_signed},
9};
10
11#[derive(Accounts)]
13#[instruction(forgo_commission: bool, fiber_cursor: u8)]
14pub struct ThreadExec<'info> {
15 #[account(mut)]
17 pub executor: Signer<'info>,
18
19 #[account(
22 mut,
23 dup,
24 seeds = [
25 SEED_THREAD,
26 thread.authority.as_ref(),
27 thread.id.as_slice(),
28 ],
29 bump = thread.bump,
30 constraint = !thread.paused @ AntegenThreadError::ThreadPaused,
31 constraint = !thread.fiber_ids.is_empty() @ AntegenThreadError::InvalidThreadState,
32 )]
33 pub thread: Box<Account<'info, Thread>>,
34
35 pub fiber: Option<Box<Account<'info, FiberState>>>,
38
39 #[account(
41 seeds = [SEED_CONFIG],
42 bump = config.bump,
43 )]
44 pub config: Account<'info, ThreadConfig>,
45
46 #[account(
49 mut,
50 constraint = admin.key().eq(&config.admin) @ AntegenThreadError::InvalidConfigAdmin,
51 )]
52 pub admin: UncheckedAccount<'info>,
53
54 #[account(mut)]
57 pub nonce_account: Option<UncheckedAccount<'info>>,
58
59 pub recent_blockhashes: Option<UncheckedAccount<'info>>,
61
62 #[account(address = anchor_lang::system_program::ID)]
63 pub system_program: Program<'info, System>,
64}
65
66pub fn thread_exec(
67 ctx: Context<ThreadExec>,
68 forgo_commission: bool,
69 fiber_cursor: u8,
70) -> Result<()> {
71 let clock: Clock = Clock::get()?;
72 let thread: &mut Box<Account<Thread>> = &mut ctx.accounts.thread;
73 let config: &Account<ThreadConfig> = &ctx.accounts.config;
74
75 let executor: &mut Signer = &mut ctx.accounts.executor;
76 let executor_lamports_start: u64 = executor.lamports();
77
78 require!(
80 !ctx.accounts.config.paused,
81 AntegenThreadError::GlobalPauseActive
82 );
83
84 let thread_pubkey = thread.key();
85
86 if thread.fiber_signal == Signal::Close {
88 let compiled = CompiledInstructionV0::try_from_slice(&thread.close_fiber)?;
90 let instruction = decompile_instruction(&compiled)?;
91
92 msg!("Executing close_fiber to delete thread");
93
94 thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
96
97 return Ok(());
99 }
100
101 let is_chained = thread.fiber_signal == Signal::Chain;
103
104 if is_chained {
106 thread.fiber_cursor = fiber_cursor;
107 }
108
109 thread.validate_for_execution()?;
112
113 thread.advance_nonce_if_required(
115 &thread.to_account_info(),
116 &ctx.accounts.nonce_account,
117 &ctx.accounts.recent_blockhashes,
118 )?;
119
120 let time_since_ready = if is_chained {
122 msg!(
123 "Chained execution from fiber_signal={:?}",
124 thread.fiber_signal
125 );
126 0 } else {
128 thread.validate_trigger(&clock, ctx.remaining_accounts, &thread_pubkey)?
129 };
130
131 let (instruction, _priority_fee, is_inline) =
133 if fiber_cursor == 0 && thread.default_fiber.is_some() {
134 let compiled =
136 CompiledInstructionV0::try_from_slice(thread.default_fiber.as_ref().unwrap())?;
137 let mut ix = decompile_instruction(&compiled)?;
138
139 for acc in ix.accounts.iter_mut() {
141 if acc.pubkey.eq(&PAYER_PUBKEY) {
142 acc.pubkey = executor.key();
143 }
144 }
145
146 (ix, thread.default_fiber_priority_fee, true)
147 } else {
148 let fiber = ctx
150 .accounts
151 .fiber
152 .as_ref()
153 .ok_or(AntegenThreadError::FiberAccountRequired)?;
154
155 let expected_fiber = thread.fiber_at_index(&thread_pubkey, fiber_cursor);
157 require!(
158 fiber.key() == expected_fiber,
159 AntegenThreadError::WrongFiberIndex
160 );
161
162 (
163 fiber.get_instruction(&executor.key())?,
164 fiber.priority_fee,
165 false,
166 )
167 };
168
169 thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
171
172 require!(
174 executor.data_is_empty(),
175 AntegenThreadError::UnauthorizedWrite
176 );
177
178 let signal: Signal = match get_return_data() {
180 None => Signal::None,
181 Some((program_id, return_data)) => {
182 if program_id.eq(&instruction.program_id) {
183 Signal::try_from_slice(return_data.as_slice()).unwrap_or(Signal::None)
184 } else {
185 Signal::None
186 }
187 }
188 };
189
190 if signal != Signal::Chain {
193 let balance_change = executor.lamports() as i64 - executor_lamports_start as i64;
194 let payments =
195 config.calculate_payments(time_since_ready, balance_change, forgo_commission);
196
197 if forgo_commission && payments.executor_commission.eq(&0) {
199 let effective_commission = config.calculate_effective_commission(time_since_ready);
200 let forgone = config.calculate_executor_fee(effective_commission);
201 msg!(
202 "Executed {}s after trigger, forgoing {} commission",
203 time_since_ready,
204 forgone
205 );
206 } else {
207 msg!("Executed {}s after trigger", time_since_ready);
208 }
209
210 thread.distribute_payments(
212 &thread.to_account_info(),
213 &executor.to_account_info(),
214 &ctx.accounts.admin.to_account_info(),
215 &payments,
216 )?;
217 }
218
219 thread.fiber_signal = signal.clone();
221
222 if matches!(thread.trigger, Trigger::Immediate { .. }) && signal != Signal::Chain {
226 thread.fiber_signal = Signal::Close;
227 }
228
229 match &signal {
230 Signal::Next { index } => {
231 thread.fiber_cursor = *index;
232 }
233 Signal::UpdateTrigger { trigger } => {
234 thread.trigger = trigger.clone();
235 thread.advance_to_next_fiber();
236 }
237 Signal::None => {
238 thread.advance_to_next_fiber();
239 }
240 _ => {}
241 }
242
243 if !is_chained {
245 thread.update_schedule(&clock, ctx.remaining_accounts, &thread_pubkey)?;
246 }
247
248 if !is_inline {
250 let fiber = ctx
251 .accounts
252 .fiber
253 .as_mut()
254 .ok_or(AntegenThreadError::FiberAccountRequired)?;
255 fiber.last_executed = clock.unix_timestamp;
256 fiber.exec_count += 1;
257 }
258
259 thread.exec_count += 1;
260 thread.last_executor = executor.key();
261 thread.last_error_time = None;
262
263 Ok(())
264}