pub use anchor_lang::prelude::*;
pub use anchor_lang::{
solana_program::{instruction::Instruction, program::invoke, program::invoke_signed, pubkey},
system_program::{transfer, Transfer},
solana_program::clock::Clock,
InstructionData,
};
pub use state::*;
use crate::errors::TriggrError;
use crate::state;
#[derive(Accounts)]
#[instruction(traverse: Vec<u8>, _trigger_count: u64, _task_count: u8)]
pub struct ExecTask<'info> {
#[account(mut)]
initiator: Signer<'info>,
program_state: Account<'info, ProgramState>,
#[account()]
authority: UncheckedAccount<'info>,
#[account(mut, seeds = ["user".as_bytes(), &authority.key().to_bytes()[..]], bump)]
user: Account<'info, User>,
#[account(mut, seeds = ["payer".as_bytes(), &authority.key().to_bytes()[..]], bump, constraint = payer.owner == &system_program.key())]
payer: UncheckedAccount<'info>,
#[account(
mut,
seeds = ["trigger".as_bytes(), &authority.key().to_bytes()[..], &_trigger_count.to_le_bytes()[..]],
bump,
constraint = trigger.status == Status::Active @ TriggrError::TriggerNotActive
)]
trigger: Box<Account<'info, Trigger>>,
#[account(mut, seeds = ["task".as_bytes(), &trigger.key().to_bytes()[..], &get_task_index(traverse, &trigger.conditions).to_le_bytes()[..]], bump)]
task: Account<'info, Task>,
system_program: Program<'info, System>,
}
pub fn get_task_index(traverse: Vec<u8>, conditions: &AdjacencyTree) -> u8 {
if traverse.len() == 0 {
0
} else {
conditions.nodes[*traverse.last().unwrap() as usize]
.task_index
.unwrap()
}
}
pub fn handler(
ctx: Context<ExecTask>,
traverse: Vec<u8>,
_trigger_count: u64,
_task_count: u8,
) -> Result<()> {
let payer_seeds = &[
"payer".as_bytes(),
&ctx.accounts.task.authority.as_ref()[..],
&[ctx.bumps["payer"]],
];
let payer = &[&payer_seeds[..]];
let mut remaining_accounts = ctx.remaining_accounts.to_vec();
if traverse.len() > 0 {
for (index, parent_node_index) in traverse.iter().enumerate() {
if index == 0 {
assert_eq!(*parent_node_index, 0);
}
ctx.accounts
.trigger
.conditions
.edges
.iter()
.find(|edge| {
traverse.get(index + 1).map_or(true, |next_value| {
**edge == [*parent_node_index, *next_value]
})
})
.unwrap_or_else(|| {
panic!("Edge with ({}, {}) not found", parent_node_index, index + 1)
});
}
for condition_node_index in traverse.iter() {
let node = &ctx.accounts.trigger.conditions.nodes[*condition_node_index as usize];
let program_account = remaining_accounts.remove(0);
let middleware_program_id = ctx.accounts.program_state.middleware[node.condition.condition_type as usize].program_id;
assert_eq!(middleware_program_id, program_account.key(), "Unexpected middleware program ID found.");
let middleware = &ctx.accounts.program_state.middleware[node.condition.condition_type as usize];
let accounts: Vec<AccountInfo> = remaining_accounts
.drain(0..middleware.accounts_count as usize)
.collect();
let account_metas: Vec<AccountMeta> = accounts
.into_iter()
.map(|account_info| AccountMeta {
pubkey: *account_info.key,
is_signer: account_info.is_signer,
is_writable: account_info.is_writable,
})
.collect();
let instruction = Instruction {
program_id: program_account.key(),
accounts: account_metas,
data: node.condition.condition_data.clone(),
};
invoke(&instruction, &ctx.remaining_accounts).unwrap();
}
}
Trigger::evaluate_time(&mut *ctx.accounts.trigger)?;
for bundle in &ctx.accounts.task.bundles {
if let Some(expiration_slot) = bundle.expiration_slot {
let current_slot = Clock::get()?.slot;
if current_slot > expiration_slot {
return Err(TriggrError::ExpirationSlotPassed.into());
}
}
assert_eq!(bundle.ready, true, "Bundle is not ready");
bundle.instructions.iter().for_each(|instruction| {
msg!("Executing instruction: {:?}", instruction.program_id);
invoke_signed(
&Instruction::from(instruction), ctx.remaining_accounts, payer,
)
.unwrap();
});
};
ctx.accounts.trigger.usage_stats.execution_count += 1;
ctx.accounts.trigger.usage_stats.last_executed_at = Clock::get()?.unix_timestamp;
ctx.accounts.task.usage_stats.execution_count += 1;
ctx.accounts.task.usage_stats.last_executed_at = Clock::get()?.unix_timestamp;
if ctx.accounts.trigger.status != Status::Active {
let active_triggers = &mut ctx.accounts.user.active_triggers;
active_triggers.retain(|&x| x != ctx.accounts.trigger.key());
ctx.accounts.user.active_triggers = active_triggers.clone();
}
Ok(())
}