antegen_thread_program/instructions/
thread_exec.rs1use crate::{
2 errors::*,
3 state::{decompile_instruction, CompiledInstructionV0, Signal},
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: Box<Account<'info, antegen_fiber_program::state::FiberState>>,
37
38 #[account(
40 seeds = [SEED_CONFIG],
41 bump = config.bump,
42 )]
43 pub config: Account<'info, ThreadConfig>,
44
45 #[account(
48 mut,
49 constraint = admin.key().eq(&config.admin) @ AntegenThreadError::InvalidConfigAdmin,
50 )]
51 pub admin: UncheckedAccount<'info>,
52
53 #[account(mut)]
56 pub nonce_account: Option<UncheckedAccount<'info>>,
57
58 pub recent_blockhashes: Option<UncheckedAccount<'info>>,
60
61 #[account(address = anchor_lang::system_program::ID)]
62 pub system_program: Program<'info, System>,
63}
64
65pub fn thread_exec(
66 ctx: Context<ThreadExec>,
67 forgo_commission: bool,
68 fiber_cursor: u8,
69) -> Result<()> {
70 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 let executor: &mut Signer = &mut ctx.accounts.executor;
75 let executor_lamports_start: u64 = executor.lamports();
76 let thread_pubkey = thread.key();
77
78 require!(
79 !ctx.accounts.config.paused,
80 AntegenThreadError::GlobalPauseActive
81 );
82
83 if thread.fiber_signal == Signal::Close {
85 let compiled = CompiledInstructionV0::try_from_slice(&thread.close_fiber)?;
86 let mut instruction = decompile_instruction(&compiled)?;
87
88 for &fiber_idx in &thread.fiber_ids {
90 instruction.accounts.push(AccountMeta {
91 pubkey: FiberState::pubkey(thread_pubkey, fiber_idx),
92 is_signer: false,
93 is_writable: true,
94 });
95 }
96
97 msg!("Executing close_fiber to delete thread ({} fibers)", thread.fiber_ids.len());
98
99 thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
100
101 return Ok(());
102 }
103
104 let is_chained = thread.fiber_signal.eq(&Signal::Chain);
106
107 if is_chained {
109 thread.fiber_cursor = fiber_cursor;
110 }
111
112 thread.validate_for_execution()?;
114
115 thread.advance_nonce_if_required(
116 &thread.to_account_info(),
117 &ctx.accounts.nonce_account,
118 &ctx.accounts.recent_blockhashes,
119 )?;
120
121 let time_since_ready = if is_chained {
122 msg!("Chained execution");
123 0
124 } else {
125 thread.validate_trigger(&clock, ctx.remaining_accounts, &thread_pubkey)?
126 };
127
128 let fiber = &ctx.accounts.fiber;
130
131 let expected_fiber = thread.fiber_at_index(&thread_pubkey, fiber_cursor);
132 require!(
133 fiber.key().eq(&expected_fiber),
134 AntegenThreadError::WrongFiberIndex
135 );
136
137 let instruction = fiber.get_instruction(&executor.key())?;
138
139 thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
140
141 require!(
143 executor.data_is_empty(),
144 AntegenThreadError::UnauthorizedWrite
145 );
146
147 let signal: Signal = match get_return_data() {
149 None => Signal::None,
150 Some((program_id, return_data)) => {
151 if program_id.eq(&instruction.program_id) {
152 Signal::try_from_slice(return_data.as_slice()).unwrap_or(Signal::None)
153 } else {
154 Signal::None
155 }
156 }
157 };
158
159 let last_fiber = thread.fiber_ids.last().copied().unwrap_or(fiber_cursor);
161 let signal = if signal.eq(&Signal::Chain) && last_fiber.eq(&fiber_cursor) {
162 Signal::None
163 } else {
164 signal
165 };
166
167 if signal.ne(&Signal::Chain) {
169 let balance_change = executor.lamports() as i64 - executor_lamports_start as i64;
170 let payments =
171 config.calculate_payments(time_since_ready, balance_change, forgo_commission);
172
173 if forgo_commission && payments.executor_commission.eq(&0) {
174 let effective_commission = config.calculate_effective_commission(time_since_ready);
175 let forgone = config.calculate_executor_fee(effective_commission);
176 msg!(
177 "Executed {}s after trigger, forgoing {} commission",
178 time_since_ready,
179 forgone
180 );
181 } else {
182 msg!("Executed {}s after trigger", time_since_ready);
183 }
184
185 thread.distribute_payments(
186 &thread.to_account_info(),
187 &executor.to_account_info(),
188 &ctx.accounts.admin.to_account_info(),
189 &payments,
190 )?;
191 }
192
193 thread.fiber_signal = Signal::None;
197 match &signal {
198 Signal::Chain | Signal::Close => {
199 thread.fiber_signal = signal.clone();
200 }
201 Signal::Next { index } => {
202 thread.fiber_cursor = *index;
203 }
204 Signal::Update { paused, trigger, index } => {
205 if let Some(paused) = paused {
206 thread.paused = *paused;
207 }
208 if let Some(trigger) = trigger {
209 thread.trigger = trigger.clone();
210 }
211 if let Some(index) = index {
212 thread.fiber_cursor = *index;
213 } else {
214 thread.advance_to_next_fiber();
215 }
216 }
217 Signal::Repeat => {
218 }
220 Signal::None => {
221 thread.advance_to_next_fiber();
222 }
223 }
224
225 if matches!(thread.trigger, Trigger::Immediate { .. }) && signal != Signal::Chain {
227 thread.fiber_signal = Signal::Close;
228 }
229
230 if signal != Signal::Chain {
232 thread.update_schedule(&clock, ctx.remaining_accounts, &thread_pubkey)?;
233 }
234
235 thread.exec_count += 1;
237 thread.last_executor = executor.key();
238
239 Ok(())
240}