clockwork_crank/state/
queue.rs

1use {
2    super::{ClockData, InstructionData},
3    crate::errors::ClockworkError,
4    anchor_lang::{
5        prelude::*,
6        solana_program::{
7            instruction::Instruction,
8            program::{get_return_data, invoke_signed},
9        },
10        AnchorDeserialize, AnchorSerialize,
11    },
12    std::{
13        convert::TryFrom,
14        hash::{Hash, Hasher},
15    },
16};
17
18pub const SEED_QUEUE: &[u8] = b"queue";
19
20/**
21 * Queue
22 */
23
24#[account]
25#[derive(Debug)]
26pub struct Queue {
27    pub authority: Pubkey,
28    pub created_at: ClockData,
29    pub exec_context: Option<ExecContext>,
30    pub id: String,
31    pub is_paused: bool,
32    pub kickoff_instruction: InstructionData,
33    pub next_instruction: Option<InstructionData>,
34    pub trigger: Trigger,
35}
36
37impl Queue {
38    pub fn pubkey(authority: Pubkey, id: String) -> Pubkey {
39        Pubkey::find_program_address(&[SEED_QUEUE, authority.as_ref(), id.as_bytes()], &crate::ID).0
40    }
41}
42
43impl TryFrom<Vec<u8>> for Queue {
44    type Error = Error;
45    fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
46        Queue::try_deserialize(&mut data.as_slice())
47    }
48}
49
50impl Hash for Queue {
51    fn hash<H: Hasher>(&self, state: &mut H) {
52        self.authority.hash(state);
53        self.id.hash(state);
54    }
55}
56
57impl PartialEq for Queue {
58    fn eq(&self, other: &Self) -> bool {
59        self.authority.eq(&other.authority) && self.id.eq(&other.id)
60    }
61}
62
63impl Eq for Queue {}
64
65/**
66 * QueueAccount
67 */
68
69pub trait QueueAccount {
70    fn init(
71        &mut self,
72        authority: Pubkey,
73        id: String,
74        kickoff_instruction: InstructionData,
75        trigger: Trigger,
76    ) -> Result<()>;
77
78    fn crank(&mut self, account_infos: &[AccountInfo], bump: u8, worker: &Signer) -> Result<()>;
79
80    fn realloc(&mut self) -> Result<()>;
81}
82
83impl QueueAccount for Account<'_, Queue> {
84    fn init(
85        &mut self,
86        authority: Pubkey,
87        id: String,
88        kickoff_instruction: InstructionData,
89        trigger: Trigger,
90    ) -> Result<()> {
91        self.authority = authority.key();
92        self.created_at = Clock::get().unwrap().into();
93        self.exec_context = None;
94        self.id = id;
95        self.is_paused = false;
96        self.kickoff_instruction = kickoff_instruction;
97        self.next_instruction = None;
98        self.trigger = trigger;
99        Ok(())
100    }
101
102    fn crank(&mut self, account_infos: &[AccountInfo], bump: u8, worker: &Signer) -> Result<()> {
103        // Record the worker's lamports before invoking inner ixs
104        let worker_lamports_pre = worker.lamports();
105
106        // Get the instruction to crank
107        let kickoff_instruction: &InstructionData = &self.clone().kickoff_instruction;
108        let next_instruction: &Option<InstructionData> = &self.clone().next_instruction;
109        let instruction = next_instruction.as_ref().unwrap_or(kickoff_instruction);
110
111        // Inject the worker's pubkey for the Clockwork payer ID
112        let normalized_accounts: &mut Vec<AccountMeta> = &mut vec![];
113        instruction.accounts.iter().for_each(|acc| {
114            let acc_pubkey = if acc.pubkey == crate::payer::ID {
115                worker.key()
116            } else {
117                acc.pubkey
118            };
119            normalized_accounts.push(AccountMeta {
120                pubkey: acc_pubkey,
121                is_signer: acc.is_signer,
122                is_writable: acc.is_writable,
123            });
124        });
125
126        // Invoke the provided instruction
127        invoke_signed(
128            &Instruction {
129                program_id: instruction.program_id,
130                data: instruction.data.clone(),
131                accounts: normalized_accounts.to_vec(),
132            },
133            account_infos,
134            &[&[
135                SEED_QUEUE,
136                self.authority.as_ref(),
137                self.id.as_bytes(),
138                &[bump],
139            ]],
140        )?;
141
142        // Verify that the inner ix did not write data to the worker address
143        require!(worker.data_is_empty(), ClockworkError::UnauthorizedWrite);
144
145        // Parse the crank response
146        match get_return_data() {
147            None => {
148                self.next_instruction = None;
149            }
150            Some((program_id, return_data)) => {
151                require!(
152                    program_id.eq(&instruction.program_id),
153                    ClockworkError::InvalidCrankResponse
154                );
155                let crank_response = CrankResponse::try_from_slice(return_data.as_slice())
156                    .map_err(|_err| ClockworkError::InvalidCrankResponse)?;
157                self.next_instruction = crank_response.next_instruction;
158            }
159        };
160
161        // Realloc the queue account
162        self.realloc()?;
163
164        // Reimbursement worker for lamports paid during inner ix
165        let worker_lamports_post = worker.lamports();
166        let worker_reimbursement = worker_lamports_pre
167            .checked_sub(worker_lamports_post)
168            .unwrap();
169        **self.to_account_info().try_borrow_mut_lamports()? = self
170            .to_account_info()
171            .lamports()
172            .checked_sub(worker_reimbursement)
173            .unwrap();
174        **worker.to_account_info().try_borrow_mut_lamports()? = worker
175            .to_account_info()
176            .lamports()
177            .checked_add(worker_reimbursement)
178            .unwrap();
179
180        Ok(())
181    }
182
183    fn realloc(&mut self) -> Result<()> {
184        // Realloc memory for the queue account
185        let data_len = 8 + self.try_to_vec()?.len();
186        self.to_account_info().realloc(data_len, false)?;
187        Ok(())
188    }
189}
190
191/**
192 * CrankResponse
193 */
194
195#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
196pub struct CrankResponse {
197    pub next_instruction: Option<InstructionData>,
198}
199
200impl Default for CrankResponse {
201    fn default() -> Self {
202        return Self {
203            next_instruction: None,
204        };
205    }
206}
207
208/**
209 * Trigger
210 */
211
212#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
213pub enum Trigger {
214    Cron { schedule: String },
215    Immediate,
216}
217
218/**
219 * ExecContext
220 */
221
222#[derive(AnchorDeserialize, AnchorSerialize, Clone, Copy, Debug, Hash, PartialEq, Eq)]
223pub enum ExecContext {
224    Cron { started_at: i64 },
225    Immediate,
226}