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(
21 mut,
22 seeds = [
23 SEED_THREAD,
24 thread.authority.as_ref(),
25 thread.id.as_slice(),
26 ],
27 bump = thread.bump,
28 constraint = !thread.paused @ AntegenThreadError::ThreadPaused,
29 constraint = !thread.fiber_ids.is_empty() @ AntegenThreadError::InvalidThreadState,
30 )]
31 pub thread: Box<Account<'info, Thread>>,
32
33 pub fiber: Option<Box<Account<'info, FiberState>>>,
36
37 #[account(
39 seeds = [SEED_CONFIG],
40 bump = config.bump,
41 )]
42 pub config: Account<'info, ThreadConfig>,
43
44 #[account(
47 mut,
48 constraint = admin.key().eq(&config.admin) @ AntegenThreadError::InvalidConfigAdmin,
49 )]
50 pub admin: UncheckedAccount<'info>,
51
52 #[account(
55 mut,
56 constraint = thread_authority.key() == thread.authority @ AntegenThreadError::InvalidThreadAuthority,
57 )]
58 pub thread_authority: UncheckedAccount<'info>,
59
60 #[account(mut)]
63 pub nonce_account: Option<UncheckedAccount<'info>>,
64
65 pub recent_blockhashes: Option<UncheckedAccount<'info>>,
67
68 #[account(address = anchor_lang::system_program::ID)]
69 pub system_program: Program<'info, System>,
70}
71
72pub fn thread_exec(
73 ctx: Context<ThreadExec>,
74 forgo_commission: bool,
75 fiber_cursor: u8,
76) -> Result<()> {
77 let clock: Clock = Clock::get()?;
78 let thread: &mut Box<Account<Thread>> = &mut ctx.accounts.thread;
79 let config: &Account<ThreadConfig> = &ctx.accounts.config;
80
81 let executor: &mut Signer = &mut ctx.accounts.executor;
82 let executor_lamports_start: u64 = executor.lamports();
83
84 require!(
86 !ctx.accounts.config.paused,
87 AntegenThreadError::GlobalPauseActive
88 );
89
90 let thread_pubkey = thread.key();
91
92 if thread.fiber_signal == Signal::Close {
94 let compiled = CompiledInstructionV0::try_from_slice(&thread.close_fiber)?;
96 let instruction = decompile_instruction(&compiled)?;
97
98 msg!("Executing close_fiber to delete thread");
99
100 thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
102
103 return Ok(());
105 }
106
107 let is_chained = thread.fiber_signal == Signal::Chain;
109
110 if is_chained {
112 thread.fiber_cursor = fiber_cursor;
113 }
114
115 thread.validate_for_execution()?;
118
119 thread.advance_nonce_if_required(
121 &thread.to_account_info(),
122 &ctx.accounts.nonce_account,
123 &ctx.accounts.recent_blockhashes,
124 )?;
125
126 let time_since_ready = if is_chained {
128 msg!("Chained execution from fiber_signal={:?}", thread.fiber_signal);
129 0 } else {
131 thread.validate_trigger(&clock, ctx.remaining_accounts, &thread_pubkey)?
132 };
133
134 let (instruction, _priority_fee, is_inline) =
136 if fiber_cursor == 0 && thread.default_fiber.is_some() {
137 let compiled =
139 CompiledInstructionV0::try_from_slice(thread.default_fiber.as_ref().unwrap())?;
140 let mut ix = decompile_instruction(&compiled)?;
141
142 for acc in ix.accounts.iter_mut() {
144 if acc.pubkey.eq(&PAYER_PUBKEY) {
145 acc.pubkey = executor.key();
146 }
147 }
148
149 (ix, thread.default_fiber_priority_fee, true)
150 } else {
151 let fiber = ctx
153 .accounts
154 .fiber
155 .as_ref()
156 .ok_or(AntegenThreadError::FiberAccountRequired)?;
157
158 let expected_fiber = thread.fiber_at_index(&thread_pubkey, fiber_cursor);
160 require!(
161 fiber.key() == expected_fiber,
162 AntegenThreadError::InvalidFiberIndex
163 );
164
165 (
166 fiber.get_instruction(&executor.key())?,
167 fiber.priority_fee,
168 false,
169 )
170 };
171
172 thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
174
175 require!(
177 executor.data_is_empty(),
178 AntegenThreadError::UnauthorizedWrite
179 );
180
181 let signal: Signal = match get_return_data() {
183 None => Signal::None,
184 Some((program_id, return_data)) => {
185 if program_id.eq(&instruction.program_id) {
186 Signal::try_from_slice(return_data.as_slice()).unwrap_or(Signal::None)
187 } else {
188 Signal::None
189 }
190 }
191 };
192
193 if signal != Signal::Chain {
196 let balance_change = executor.lamports() as i64 - executor_lamports_start as i64;
197 let payments = config.calculate_payments(time_since_ready, balance_change, forgo_commission);
198
199 if forgo_commission && payments.executor_commission.eq(&0) {
201 let effective_commission = config.calculate_effective_commission(time_since_ready);
202 let forgone = config.calculate_executor_fee(effective_commission);
203 msg!(
204 "Executed {}s after trigger, forgoing {} commission",
205 time_since_ready,
206 forgone
207 );
208 } else {
209 msg!("Executed {}s after trigger", time_since_ready);
210 }
211
212 thread.distribute_payments(
214 &thread.to_account_info(),
215 &executor.to_account_info(),
216 &ctx.accounts.admin.to_account_info(),
217 &payments,
218 )?;
219 }
220
221 thread.fiber_signal = signal.clone();
223
224 if matches!(thread.trigger, Trigger::Immediate { .. }) && signal != Signal::Chain {
228 thread.fiber_signal = Signal::Close;
229 }
230
231 match &signal {
232 Signal::Next { index } => {
233 thread.fiber_cursor = *index;
234 }
235 Signal::UpdateTrigger { trigger } => {
236 thread.trigger = trigger.clone();
237 thread.advance_to_next_fiber();
238 }
239 Signal::None => {
240 thread.advance_to_next_fiber();
241 }
242 _ => {}
243 }
244
245 if !is_chained {
247 thread.update_schedule(&clock, ctx.remaining_accounts, &thread_pubkey)?;
248 }
249
250 if !is_inline {
252 let fiber = ctx
253 .accounts
254 .fiber
255 .as_mut()
256 .ok_or(AntegenThreadError::FiberAccountRequired)?;
257 fiber.last_executed = clock.unix_timestamp;
258 fiber.exec_count += 1;
259 }
260
261 thread.exec_count += 1;
262 thread.last_executor = executor.key();
263 thread.last_error_time = None;
264
265 Ok(())
266}