use crate::{
errors::*,
state::{decompile_instruction, CompiledInstructionV0, Signal, PAYER_PUBKEY},
*,
};
use anchor_lang::{
prelude::*,
solana_program::program::{get_return_data, invoke_signed},
};
#[derive(Accounts)]
#[instruction(forgo_commission: bool, fiber_cursor: u8)]
pub struct ThreadExec<'info> {
#[account(mut)]
pub executor: Signer<'info>,
#[account(
mut,
dup,
seeds = [
SEED_THREAD,
thread.authority.as_ref(),
thread.id.as_slice(),
],
bump = thread.bump,
constraint = !thread.paused @ AntegenThreadError::ThreadPaused,
constraint = !thread.fiber_ids.is_empty() @ AntegenThreadError::InvalidThreadState,
)]
pub thread: Box<Account<'info, Thread>>,
pub fiber: Option<Box<Account<'info, FiberState>>>,
#[account(
seeds = [SEED_CONFIG],
bump = config.bump,
)]
pub config: Account<'info, ThreadConfig>,
#[account(
mut,
constraint = admin.key().eq(&config.admin) @ AntegenThreadError::InvalidConfigAdmin,
)]
pub admin: UncheckedAccount<'info>,
#[account(mut)]
pub nonce_account: Option<UncheckedAccount<'info>>,
pub recent_blockhashes: Option<UncheckedAccount<'info>>,
#[account(address = anchor_lang::system_program::ID)]
pub system_program: Program<'info, System>,
}
pub fn thread_exec(
ctx: Context<ThreadExec>,
forgo_commission: bool,
fiber_cursor: u8,
) -> Result<()> {
let clock: Clock = Clock::get()?;
let thread: &mut Box<Account<Thread>> = &mut ctx.accounts.thread;
let config: &Account<ThreadConfig> = &ctx.accounts.config;
let executor: &mut Signer = &mut ctx.accounts.executor;
let executor_lamports_start: u64 = executor.lamports();
require!(
!ctx.accounts.config.paused,
AntegenThreadError::GlobalPauseActive
);
let thread_pubkey = thread.key();
if thread.fiber_signal == Signal::Close {
let compiled = CompiledInstructionV0::try_from_slice(&thread.close_fiber)?;
let instruction = decompile_instruction(&compiled)?;
msg!("Executing close_fiber to delete thread");
thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
return Ok(());
}
let is_chained = thread.fiber_signal == Signal::Chain;
if is_chained {
thread.fiber_cursor = fiber_cursor;
}
thread.validate_for_execution()?;
thread.advance_nonce_if_required(
&thread.to_account_info(),
&ctx.accounts.nonce_account,
&ctx.accounts.recent_blockhashes,
)?;
let time_since_ready = if is_chained {
msg!(
"Chained execution from fiber_signal={:?}",
thread.fiber_signal
);
0 } else {
thread.validate_trigger(&clock, ctx.remaining_accounts, &thread_pubkey)?
};
let (instruction, _priority_fee, is_inline) =
if fiber_cursor == 0 && thread.default_fiber.is_some() {
let compiled =
CompiledInstructionV0::try_from_slice(thread.default_fiber.as_ref().unwrap())?;
let mut ix = decompile_instruction(&compiled)?;
for acc in ix.accounts.iter_mut() {
if acc.pubkey.eq(&PAYER_PUBKEY) {
acc.pubkey = executor.key();
}
}
(ix, thread.default_fiber_priority_fee, true)
} else {
let fiber = ctx
.accounts
.fiber
.as_ref()
.ok_or(AntegenThreadError::FiberAccountRequired)?;
let expected_fiber = thread.fiber_at_index(&thread_pubkey, fiber_cursor);
require!(
fiber.key() == expected_fiber,
AntegenThreadError::WrongFiberIndex
);
(
fiber.get_instruction(&executor.key())?,
fiber.priority_fee,
false,
)
};
thread.sign(|seeds| invoke_signed(&instruction, ctx.remaining_accounts, &[seeds]))?;
require!(
executor.data_is_empty(),
AntegenThreadError::UnauthorizedWrite
);
let signal: Signal = match get_return_data() {
None => Signal::None,
Some((program_id, return_data)) => {
if program_id.eq(&instruction.program_id) {
Signal::try_from_slice(return_data.as_slice()).unwrap_or(Signal::None)
} else {
Signal::None
}
}
};
if signal != Signal::Chain {
let balance_change = executor.lamports() as i64 - executor_lamports_start as i64;
let payments =
config.calculate_payments(time_since_ready, balance_change, forgo_commission);
if forgo_commission && payments.executor_commission.eq(&0) {
let effective_commission = config.calculate_effective_commission(time_since_ready);
let forgone = config.calculate_executor_fee(effective_commission);
msg!(
"Executed {}s after trigger, forgoing {} commission",
time_since_ready,
forgone
);
} else {
msg!("Executed {}s after trigger", time_since_ready);
}
thread.distribute_payments(
&thread.to_account_info(),
&executor.to_account_info(),
&ctx.accounts.admin.to_account_info(),
&payments,
)?;
}
thread.fiber_signal = signal.clone();
if matches!(thread.trigger, Trigger::Immediate { .. }) && signal != Signal::Chain {
thread.fiber_signal = Signal::Close;
}
match &signal {
Signal::Next { index } => {
thread.fiber_cursor = *index;
}
Signal::UpdateTrigger { trigger } => {
thread.trigger = trigger.clone();
thread.advance_to_next_fiber();
}
Signal::None => {
thread.advance_to_next_fiber();
}
_ => {}
}
if !is_chained {
thread.update_schedule(&clock, ctx.remaining_accounts, &thread_pubkey)?;
}
if !is_inline {
let fiber = ctx
.accounts
.fiber
.as_mut()
.ok_or(AntegenThreadError::FiberAccountRequired)?;
fiber.last_executed = clock.unix_timestamp;
fiber.exec_count += 1;
}
thread.exec_count += 1;
thread.last_executor = executor.key();
thread.last_error_time = None;
Ok(())
}