clockwork_scheduler/state/
task.rs1use crate::state::FeeAccount;
2
3use {
4 super::{Config, Fee, Queue},
5 crate::{
6 errors::ClockworkError,
7 state::{QueueAccount, QueueStatus},
8 },
9 anchor_lang::{
10 prelude::borsh::BorshSchema, prelude::*, solana_program::instruction::Instruction,
11 AnchorDeserialize,
12 },
13 std::convert::TryFrom,
14};
15
16pub const SEED_TASK: &[u8] = b"task";
17
18#[account]
23#[derive(Debug)]
24pub struct Task {
25 pub id: u64,
26 pub ixs: Vec<InstructionData>,
27 pub queue: Pubkey,
28}
29
30impl Task {
31 pub fn pubkey(queue: Pubkey, id: u64) -> Pubkey {
32 Pubkey::find_program_address(
33 &[SEED_TASK, queue.as_ref(), id.to_be_bytes().as_ref()],
34 &crate::ID,
35 )
36 .0
37 }
38}
39
40impl TryFrom<Vec<u8>> for Task {
41 type Error = Error;
42 fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
43 Task::try_deserialize(&mut data.as_slice())
44 }
45}
46
47pub trait TaskAccount {
52 fn new(&mut self, ixs: Vec<InstructionData>, queue: &mut Account<Queue>) -> Result<()>;
53
54 fn exec(
55 &mut self,
56 account_infos: &Vec<AccountInfo>,
57 config: &Account<Config>,
58 fee: &mut Account<Fee>,
59 queue: &mut Account<Queue>,
60 queue_bump: u8,
61 worker: &mut Signer,
62 ) -> Result<()>;
63}
64
65impl TaskAccount for Account<'_, Task> {
66 fn new(&mut self, ixs: Vec<InstructionData>, queue: &mut Account<Queue>) -> Result<()> {
67 for ix in ixs.iter() {
69 for acc in ix.accounts.iter() {
70 if acc.is_signer {
71 require!(
72 acc.pubkey == queue.key() || acc.pubkey == crate::payer::ID,
73 ClockworkError::InvalidSignatory
74 );
75 }
76 }
77 }
78
79 self.id = queue.task_count;
81 self.ixs = ixs;
82 self.queue = queue.key();
83
84 queue.task_count = queue.task_count.checked_add(1).unwrap();
86
87 Ok(())
88 }
89
90 fn exec(
91 &mut self,
92 account_infos: &Vec<AccountInfo>,
93 config: &Account<Config>,
94 fee: &mut Account<Fee>,
95 queue: &mut Account<Queue>,
96 queue_bump: u8,
97 worker: &mut Signer,
98 ) -> Result<()> {
99 require!(
101 self.id
102 == match queue.status {
103 QueueStatus::Processing { task_id } => task_id,
104 _ => return Err(ClockworkError::InvalidQueueStatus.into()),
105 },
106 ClockworkError::InvalidTask
107 );
108
109 require!(worker.data_is_empty(), ClockworkError::WorkerDataNotEmpty);
111
112 let worker_lamports_pre = worker.lamports();
114
115 let dyanmic_ixs: &mut Vec<InstructionData> = &mut vec![];
117
118 for ix in &self.ixs {
120 let accs: &mut Vec<AccountMetaData> = &mut vec![];
128 ix.accounts.iter().for_each(|acc| {
129 if acc.pubkey == crate::payer::ID {
130 accs.push(AccountMetaData {
131 pubkey: worker.key(),
132 is_signer: acc.is_signer,
133 is_writable: acc.is_writable,
134 });
135 } else {
136 accs.push(acc.clone());
137 }
138 });
139
140 let exec_response = queue.sign(
144 &account_infos,
145 queue_bump,
146 &InstructionData {
147 program_id: ix.program_id,
148 accounts: accs.clone(),
149 data: ix.data.clone(),
150 },
151 )?;
152
153 match exec_response {
155 None => (),
156 Some(exec_response) => match exec_response.dynamic_accounts {
157 None => (),
158 Some(dynamic_accounts) => {
159 require!(
160 dynamic_accounts.len() == ix.accounts.len(),
161 ClockworkError::InvalidDynamicAccounts
162 );
163 dyanmic_ixs.push(InstructionData {
164 program_id: ix.program_id,
165 accounts: dynamic_accounts
166 .iter()
167 .enumerate()
168 .map(|(i, pubkey)| {
169 let acc = ix.accounts.get(i).unwrap();
170 AccountMetaData {
171 pubkey: match pubkey {
172 _ if *pubkey == worker.key() => crate::payer::ID,
173 _ => *pubkey,
174 },
175 is_signer: acc.is_signer,
176 is_writable: acc.is_writable,
177 }
178 })
179 .collect::<Vec<AccountMetaData>>(),
180 data: ix.data.clone(),
181 });
182 }
183 },
184 }
185 }
186
187 require!(worker.data_is_empty(), ClockworkError::WorkerDataNotEmpty);
189
190 if !dyanmic_ixs.is_empty() {
192 self.ixs = dyanmic_ixs.clone();
193 }
194
195 let worker_lamports_post = worker.lamports();
197 let worker_reimbursement = worker_lamports_pre
198 .checked_sub(worker_lamports_post)
199 .unwrap();
200
201 let total_worker_fee = config.worker_fee.checked_add(worker_reimbursement).unwrap();
203 fee.pay_to_worker(total_worker_fee, queue)?;
204
205 let next_task_id = self.id.checked_add(1).unwrap();
207 if next_task_id == queue.task_count {
208 queue.roll_forward()?;
209 } else {
210 queue.status = QueueStatus::Processing {
211 task_id: next_task_id,
212 };
213 }
214
215 Ok(())
216 }
217}
218
219#[derive(AnchorDeserialize, AnchorSerialize, BorshSchema, Clone, Debug, PartialEq)]
224pub struct InstructionData {
225 pub program_id: Pubkey,
227 pub accounts: Vec<AccountMetaData>,
229 pub data: Vec<u8>,
231}
232
233impl From<Instruction> for InstructionData {
234 fn from(instruction: Instruction) -> Self {
235 InstructionData {
236 program_id: instruction.program_id,
237 accounts: instruction
238 .accounts
239 .iter()
240 .map(|a| AccountMetaData {
241 pubkey: a.pubkey,
242 is_signer: a.is_signer,
243 is_writable: a.is_writable,
244 })
245 .collect(),
246 data: instruction.data,
247 }
248 }
249}
250
251impl From<&InstructionData> for Instruction {
252 fn from(instruction: &InstructionData) -> Self {
253 Instruction {
254 program_id: instruction.program_id,
255 accounts: instruction
256 .accounts
257 .iter()
258 .map(|a| AccountMeta {
259 pubkey: a.pubkey,
260 is_signer: a.is_signer,
261 is_writable: a.is_writable,
262 })
263 .collect(),
264 data: instruction.data.clone(),
265 }
266 }
267}
268
269impl TryFrom<Vec<u8>> for InstructionData {
270 type Error = Error;
271 fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
272 Ok(
273 borsh::try_from_slice_with_schema::<InstructionData>(data.as_slice())
274 .map_err(|_err| ErrorCode::AccountDidNotDeserialize)?,
275 )
276 }
277}
278
279#[derive(AnchorDeserialize, AnchorSerialize, BorshSchema, Clone, Debug, PartialEq)]
284pub struct AccountMetaData {
285 pub pubkey: Pubkey,
287 pub is_signer: bool,
289 pub is_writable: bool,
291}