triggr-program 0.1.1

Created with Anchor
Documentation
use anchor_lang::{
    prelude::*, solana_program::clock::Clock, solana_program::instruction::Instruction,
};

use super::Statistics;

/// Represents a task in the system.
#[account]
pub struct Task {
    /// The list of instruction bundles associated with the task.
    pub bundles: Vec<InstructionBundle>,

    /// The status of the task. (Incomplete, Complete, Partial, Executed, Failed)
    pub status: TaskStatus,

    /// The authority (creator) of the task.
    pub authority: Pubkey,

    /// The timestamp when the task was created (in milliseconds).
    pub created_at: i64,

    /// The timestamp when the task was last updated (in milliseconds).
    pub updated_at: i64,

    /// The usage statistics of the task.
    pub usage_stats: Statistics,

    /// The index of the task from the trigger's task count.
    pub own_index: u8,

    /// The public key of the parent trigger.
    pub parent_trigger: Pubkey,

    /// The lookup table associated with the task, if any.
    pub lut: Option<Pubkey>,
}

impl Task {
    pub const MIN_SIZE: usize = 1
        + 1
        + 8
        + 8
        + 32
        + 32
        + 1
        + 1
        + 32
        + 4
        + SerializableInstruction::MIN_SIZE
        + Statistics::MIN_SIZE;

    pub fn get_size(instructions: Vec<SerializableInstruction>) -> usize {
        let mut size = Self::MIN_SIZE;
        size += instructions.len() * SerializableInstruction::MIN_SIZE;
        size
    }

    pub fn new(&self, own_index: u8, trigger: &Pubkey) -> Result<Self> {
        Ok(Self {
            authority: self.authority, // maybe authority should be signer
            status: self.status,
            bundles: self.bundles.to_vec(),
            created_at: Clock::get()?.unix_timestamp,
            updated_at: Clock::get()?.unix_timestamp,
            usage_stats: Statistics::new(),
            own_index,
            lut: None,
            parent_trigger: *trigger,
        })
    }

    pub fn collect_all_accounts(task: &Task) -> Vec<Pubkey> {
        let mut all_accounts = Vec::new();

        // Iterate over bundle
        for bundle in &task.bundles {
            for instruction in &bundle.instructions {
                // Iterate over accounts within each instruction
                for account in &instruction.accounts {
                    all_accounts.push(account.pubkey.clone());
                }
            }
        }

        all_accounts
    }
}

/// Represents the status of a task.
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, Debug, Copy)]
pub enum TaskStatus {
    /// The task is incomplete, meaning it is missing instructions.
    Incomplete,

    /// The task is complete and ready to be executed.
    Complete,

    /// The task is partially executed.
    Partial,

    /// The task has been executed.
    Executed,

    /// The task has failed to execute.
    Failed,
}

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug, Hash, PartialEq, Default)]
pub struct InstructionBundle {
    /// Represents a bundle of instructions associated with a task. A bundle is a collection of instructions that are executed together.
    /// for example, a bundle can be a collection of instructions that are usually executed in a single transaction.
    /// (e.g. a swap which may require more than one instruction)
    pub instructions: Vec<SerializableInstruction>,

    /// Indicates if an instruction needs to be created at a later time. It is marked as ready once all instructions are present.
    pub ready: bool,

    /// The expiration slot of the bundle. It should always be checked before checking if the bundle is ready.
    pub expiration_slot: Option<u64>,

    /// Indicates if the bundle has been executed. For future partial execution.
    pub executed: bool,

    /// A short title for the instruction, followed by 40 characters.
    pub instruction_type: String,

    /// Serialized arguments for populating the instruction to be consumed in populating program.
    pub args: Option<Args>,
}

/// Represents the arguments for populating an instruction to be consumed in a program.
#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug, Hash, PartialEq, Default)]
pub struct Args {
    /// The program ID associated with the instruction.
    pub program_id: Pubkey,

    /// The data to be passed to the program for its own interpretation.
    pub data: Vec<u8>,
}

impl InstructionBundle {
    pub const MIN_SIZE: usize = 8 + 1 + 1 + 1 + 8 + SerializableInstruction::MIN_SIZE + 40;
}

// From Clockwork [SerializableInstruction](https://github.com/clockwork-xyz/clockwork/blob/06258e7d60954d809324fa4fc2d609cae898f318/utils/src/thread.rs#L104)
#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug, Hash, PartialEq, Default)]
pub struct SerializableInstruction {
    /// Pubkey of the program that executes this instruction.
    pub program_id: Pubkey, // 32
    /// Metadata describing accounts that should be passed to the program.
    pub accounts: Vec<SerializableAccount>, // 4 + (SerializableAccount::MIN_SIZE * 2)
    /// Opaque data passed to the program for its own interpretation.
    pub data: Vec<u8>, // 4 + 8
}

impl SerializableInstruction {
    pub const MIN_SIZE: usize = 32 + 4 + (SerializableAccount::MIN_SIZE * 2);
}

impl From<Instruction> for SerializableInstruction {
    fn from(instruction: Instruction) -> Self {
        SerializableInstruction {
            program_id: instruction.program_id,
            accounts: instruction
                .accounts
                .iter()
                .map(|a| SerializableAccount {
                    pubkey: a.pubkey,
                    is_signer: a.is_signer,
                    is_writable: a.is_writable,
                })
                .collect(),
            data: instruction.data,
        }
    }
}

impl From<&SerializableInstruction> for Instruction {
    fn from(instruction: &SerializableInstruction) -> Self {
        Instruction {
            program_id: instruction.program_id,
            accounts: instruction
                .accounts
                .iter()
                .map(|a| AccountMeta {
                    pubkey: a.pubkey,
                    is_signer: a.is_signer,
                    is_writable: a.is_writable,
                })
                .collect(),
            data: instruction.data.clone(),
        }
    }
}

#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug, Hash, PartialEq)]
pub struct SerializableAccount {
    /// An account's public key
    pub pubkey: Pubkey,
    /// True if an Instruction requires a Transtask signature matching `pubkey`.
    pub is_signer: bool,
    /// True if the `pubkey` can be loaded as a read-write account.
    pub is_writable: bool,
}

impl SerializableAccount {
    pub const MIN_SIZE: usize = 32 + 1 + 1;
    /// Construct metadata for a writable account.
    pub fn mutable(pubkey: Pubkey, signer: bool) -> Self {
        Self {
            pubkey,
            is_signer: signer,
            is_writable: true,
        }
    }

    /// Construct metadata for a read-only account.
    pub fn readonly(pubkey: Pubkey, signer: bool) -> Self {
        Self {
            pubkey,
            is_signer: signer,
            is_writable: false,
        }
    }

    // pub fn from(account: SerializableAccount) -> AccountInfo<'static> {
    //     AccountInfo::try_from(*account.as_ref())
    // }
}