clockwork_crank/state/
queue.rs1use {
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#[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
65pub 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 let worker_lamports_pre = worker.lamports();
105
106 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 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_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 require!(worker.data_is_empty(), ClockworkError::UnauthorizedWrite);
144
145 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 self.realloc()?;
163
164 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 let data_len = 8 + self.try_to_vec()?.len();
186 self.to_account_info().realloc(data_len, false)?;
187 Ok(())
188 }
189}
190
191#[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#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone)]
213pub enum Trigger {
214 Cron { schedule: String },
215 Immediate,
216}
217
218#[derive(AnchorDeserialize, AnchorSerialize, Clone, Copy, Debug, Hash, PartialEq, Eq)]
223pub enum ExecContext {
224 Cron { started_at: i64 },
225 Immediate,
226}