1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
use crate::state::FeeAccount;

use {
    super::{Config, Fee, Queue},
    crate::{
        errors::ClockworkError,
        state::{QueueAccount, QueueStatus},
    },
    anchor_lang::{
        prelude::borsh::BorshSchema, prelude::*, solana_program::instruction::Instruction,
        AnchorDeserialize,
    },
    std::convert::TryFrom,
};

pub const SEED_TASK: &[u8] = b"task";

/**
 * Task
 */

#[account]
#[derive(Debug)]
pub struct Task {
    pub id: u64,
    pub ixs: Vec<InstructionData>,
    pub queue: Pubkey,
}

impl Task {
    pub fn pubkey(queue: Pubkey, id: u64) -> Pubkey {
        Pubkey::find_program_address(
            &[SEED_TASK, queue.as_ref(), id.to_be_bytes().as_ref()],
            &crate::ID,
        )
        .0
    }
}

impl TryFrom<Vec<u8>> for Task {
    type Error = Error;
    fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
        Task::try_deserialize(&mut data.as_slice())
    }
}

/**
 * TaskAccount
 */

pub trait TaskAccount {
    fn new(&mut self, ixs: Vec<InstructionData>, queue: &mut Account<Queue>) -> Result<()>;

    fn exec(
        &mut self,
        account_infos: &Vec<AccountInfo>,
        config: &Account<Config>,
        fee: &mut Account<Fee>,
        queue: &mut Account<Queue>,
        queue_bump: u8,
        worker: &mut Signer,
    ) -> Result<()>;
}

impl TaskAccount for Account<'_, Task> {
    fn new(&mut self, ixs: Vec<InstructionData>, queue: &mut Account<Queue>) -> Result<()> {
        // Reject inner instructions if they have a signer other than the queue or payer
        for ix in ixs.iter() {
            for acc in ix.accounts.iter() {
                if acc.is_signer {
                    require!(
                        acc.pubkey == queue.key() || acc.pubkey == crate::payer::ID,
                        ClockworkError::InvalidSignatory
                    );
                }
            }
        }

        // Save data
        self.id = queue.task_count;
        self.ixs = ixs;
        self.queue = queue.key();

        // Increment the queue's task count
        queue.task_count = queue.task_count.checked_add(1).unwrap();

        Ok(())
    }

    fn exec(
        &mut self,
        account_infos: &Vec<AccountInfo>,
        config: &Account<Config>,
        fee: &mut Account<Fee>,
        queue: &mut Account<Queue>,
        queue_bump: u8,
        worker: &mut Signer,
    ) -> Result<()> {
        // Validate the task id matches the queue's current execution state
        require!(
            self.id
                == match queue.status {
                    QueueStatus::Processing { task_id } => task_id,
                    _ => return Err(ClockworkError::InvalidQueueStatus.into()),
                },
            ClockworkError::InvalidTask
        );

        // Validate the worker data is empty
        require!(worker.data_is_empty(), ClockworkError::WorkerDataNotEmpty);

        // Record the worker's lamports before invoking inner ixs
        let worker_lamports_pre = worker.lamports();

        // Create an array of dynamic ixs to update the task for the next invocation
        let dyanmic_ixs: &mut Vec<InstructionData> = &mut vec![];

        // Process all of the task instructions
        for ix in &self.ixs {
            // If an inner ix account matches the Clockwork payer address (ClockworkPayer11111111111111111111111111111111),
            //  then inject the worker in its place. Dapp developers can use the worker as a payer to initialize
            //  new accounts in their tasks. Workers will be reimbursed for all SOL spent during the inner ixs.
            //
            // Because the worker can be injected as the signer on inner ixs (written by presumed malicious parties),
            //  node operators should not secure any assets or staking positions with their worker wallets other than
            //  an operational level of lamports needed to submit txns (~0.01 ⊚).
            let accs: &mut Vec<AccountMetaData> = &mut vec![];
            ix.accounts.iter().for_each(|acc| {
                if acc.pubkey == crate::payer::ID {
                    accs.push(AccountMetaData {
                        pubkey: worker.key(),
                        is_signer: acc.is_signer,
                        is_writable: acc.is_writable,
                    });
                } else {
                    accs.push(acc.clone());
                }
            });

            // Execute the inner ix and process the response. Note that even though the queue PDA is a signer
            //  on this ix, Solana will not allow downstream programs to mutate accounts owned by this program
            //  and explicitly forbids CPI reentrancy.
            let exec_response = queue.sign(
                &account_infos,
                queue_bump,
                &InstructionData {
                    program_id: ix.program_id,
                    accounts: accs.clone(),
                    data: ix.data.clone(),
                },
            )?;

            // Process the exec response
            match exec_response {
                None => (),
                Some(exec_response) => match exec_response.dynamic_accounts {
                    None => (),
                    Some(dynamic_accounts) => {
                        require!(
                            dynamic_accounts.len() == ix.accounts.len(),
                            ClockworkError::InvalidDynamicAccounts
                        );
                        dyanmic_ixs.push(InstructionData {
                            program_id: ix.program_id,
                            accounts: dynamic_accounts
                                .iter()
                                .enumerate()
                                .map(|(i, pubkey)| {
                                    let acc = ix.accounts.get(i).unwrap();
                                    AccountMetaData {
                                        pubkey: match pubkey {
                                            _ if *pubkey == worker.key() => crate::payer::ID,
                                            _ => *pubkey,
                                        },
                                        is_signer: acc.is_signer,
                                        is_writable: acc.is_writable,
                                    }
                                })
                                .collect::<Vec<AccountMetaData>>(),
                            data: ix.data.clone(),
                        });
                    }
                },
            }
        }

        // Verify that inner ixs have not initialized data at the worker address
        require!(worker.data_is_empty(), ClockworkError::WorkerDataNotEmpty);

        // Update the actions's ixs for the next invocation
        if !dyanmic_ixs.is_empty() {
            self.ixs = dyanmic_ixs.clone();
        }

        // Track how many lamports the worker spent in the inner ixs
        let worker_lamports_post = worker.lamports();
        let worker_reimbursement = worker_lamports_pre
            .checked_sub(worker_lamports_post)
            .unwrap();

        // Pay worker fees
        let total_worker_fee = config.worker_fee.checked_add(worker_reimbursement).unwrap();
        fee.pay_to_worker(total_worker_fee, queue)?;

        // Update the queue status
        let next_task_id = self.id.checked_add(1).unwrap();
        if next_task_id == queue.task_count {
            queue.roll_forward()?;
        } else {
            queue.status = QueueStatus::Processing {
                task_id: next_task_id,
            };
        }

        Ok(())
    }
}

/**
 * InstructionData
 */

#[derive(AnchorDeserialize, AnchorSerialize, BorshSchema, Clone, Debug, PartialEq)]
pub struct InstructionData {
    /// Pubkey of the instruction processor that executes this instruction
    pub program_id: Pubkey,
    /// Metadata for what accounts should be passed to the instruction processor
    pub accounts: Vec<AccountMetaData>,
    /// Opaque data passed to the instruction processor
    pub data: Vec<u8>,
}

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

impl From<&InstructionData> for Instruction {
    fn from(instruction: &InstructionData) -> 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(),
        }
    }
}

impl TryFrom<Vec<u8>> for InstructionData {
    type Error = Error;
    fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
        Ok(
            borsh::try_from_slice_with_schema::<InstructionData>(data.as_slice())
                .map_err(|_err| ErrorCode::AccountDidNotDeserialize)?,
        )
    }
}

/**
 * AccountMetaData
 */

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