tuktuk_program/
lib.rs

1use std::collections::HashMap;
2
3use anchor_lang::{prelude::*, solana_program::instruction::Instruction};
4
5pub mod write_return_tasks;
6
7pub use write_return_tasks::write_return_tasks;
8
9declare_program!(tuktuk);
10declare_program!(cron);
11
12#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)]
13pub struct RunTaskReturnV0 {
14    pub tasks: Vec<TaskReturnV0>,
15    pub accounts: Vec<Pubkey>,
16}
17
18#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
19pub struct TaskReturnV0 {
20    pub trigger: TriggerV0,
21    // Note that you can pass accounts from the remaining accounts to reduce
22    // the size of the transaction
23    pub transaction: TransactionSourceV0,
24    pub crank_reward: Option<u64>,
25    // Number of free tasks to append to the end of the accounts. This allows
26    // you to easily add new tasks
27    pub free_tasks: u8,
28    // Description of the task. Useful for debugging and logging
29    pub description: String,
30}
31
32impl Default for TaskReturnV0 {
33    fn default() -> Self {
34        TaskReturnV0 {
35            trigger: TriggerV0::Now,
36            transaction: TransactionSourceV0::CompiledV0(CompiledTransactionV0::default()),
37            crank_reward: None,
38            free_tasks: 0,
39            description: "".to_string(),
40        }
41    }
42}
43
44#[allow(clippy::derivable_impls)]
45impl Default for TriggerV0 {
46    fn default() -> Self {
47        TriggerV0::Now
48    }
49}
50
51pub use self::{
52    tuktuk::{
53        accounts::{
54            TaskQueueAuthorityV0, TaskQueueNameMappingV0, TaskQueueV0, TaskV0, TuktukConfigV0,
55        },
56        client, types,
57    },
58    types::{
59        CompiledInstructionV0, CompiledTransactionV0, InitializeTuktukConfigArgsV0,
60        TransactionSourceV0, TriggerV0,
61    },
62};
63
64impl TriggerV0 {
65    pub fn is_active(&self, now: i64) -> bool {
66        match self {
67            TriggerV0::Now => true,
68            TriggerV0::Timestamp(ts) => now >= *ts,
69        }
70    }
71}
72
73impl TaskQueueV0 {
74    pub fn task_exists(&self, task_idx: u16) -> bool {
75        if task_idx >= self.capacity {
76            return false;
77        }
78        self.task_bitmap[task_idx as usize / 8] & (1 << (task_idx % 8)) != 0
79    }
80
81    pub fn next_available_task_id(&self) -> Option<u16> {
82        for (byte_idx, byte) in self.task_bitmap.iter().enumerate() {
83            if *byte != 0xff {
84                // If byte is not all 1s
85                for bit_idx in 0..8 {
86                    if byte & (1 << bit_idx) == 0 {
87                        return Some((byte_idx * 8 + bit_idx) as u16);
88                    }
89                }
90            }
91        }
92        None
93    }
94}
95
96impl From<CompiledTransactionV0> for cron::types::CompiledTransactionV0 {
97    fn from(value: CompiledTransactionV0) -> Self {
98        cron::types::CompiledTransactionV0 {
99            num_ro_signers: value.num_ro_signers,
100            num_rw_signers: value.num_rw_signers,
101            num_rw: value.num_rw,
102            instructions: value.instructions.into_iter().map(|ix| ix.into()).collect(),
103            signer_seeds: value.signer_seeds,
104            accounts: value.accounts,
105        }
106    }
107}
108
109impl From<CompiledInstructionV0> for cron::types::CompiledInstructionV0 {
110    fn from(value: CompiledInstructionV0) -> Self {
111        cron::types::CompiledInstructionV0 {
112            program_id_index: value.program_id_index,
113            accounts: value.accounts,
114            data: value.data,
115        }
116    }
117}
118
119pub fn compile_transaction(
120    instructions: Vec<Instruction>,
121    signer_seeds: Vec<Vec<Vec<u8>>>,
122) -> Result<(CompiledTransactionV0, Vec<AccountMeta>)> {
123    let mut pubkeys_to_metadata: HashMap<Pubkey, AccountMeta> = HashMap::new();
124
125    // Process all instructions to build metadata
126    for ix in &instructions {
127        pubkeys_to_metadata
128            .entry(ix.program_id)
129            .or_insert(AccountMeta {
130                pubkey: ix.program_id,
131                is_signer: false,
132                is_writable: false,
133            });
134
135        for key in &ix.accounts {
136            let entry = pubkeys_to_metadata
137                .entry(key.pubkey)
138                .or_insert(AccountMeta {
139                    is_signer: false,
140                    is_writable: false,
141                    pubkey: key.pubkey,
142                });
143            entry.is_writable |= key.is_writable;
144            entry.is_signer |= key.is_signer;
145        }
146    }
147
148    // Sort accounts: writable signers first, then ro signers, then rw non-signers, then ro
149    let mut sorted_accounts: Vec<Pubkey> = pubkeys_to_metadata.keys().cloned().collect();
150    sorted_accounts.sort_by(|a, b| {
151        let a_meta = &pubkeys_to_metadata[a];
152        let b_meta = &pubkeys_to_metadata[b];
153
154        // Compare accounts based on priority: writable signers > readonly signers > writable > readonly
155        fn get_priority(meta: &AccountMeta) -> u8 {
156            match (meta.is_signer, meta.is_writable) {
157                (true, true) => 0,   // Writable signer: highest priority
158                (true, false) => 1,  // Readonly signer
159                (false, true) => 2,  // Writable non-signer
160                (false, false) => 3, // Readonly non-signer: lowest priority
161            }
162        }
163
164        get_priority(a_meta).cmp(&get_priority(b_meta))
165    });
166
167    // Count different types of accounts
168    let mut num_rw_signers = 0u8;
169    let mut num_ro_signers = 0u8;
170    let mut num_rw = 0u8;
171
172    for k in &sorted_accounts {
173        let metadata = &pubkeys_to_metadata[k];
174        if metadata.is_signer && metadata.is_writable {
175            num_rw_signers += 1;
176        } else if metadata.is_signer && !metadata.is_writable {
177            num_ro_signers += 1;
178        } else if metadata.is_writable {
179            num_rw += 1;
180        }
181    }
182
183    // Create accounts to index mapping
184    let accounts_to_index: HashMap<Pubkey, u8> = sorted_accounts
185        .iter()
186        .enumerate()
187        .map(|(i, k)| (*k, i as u8))
188        .collect();
189
190    // Compile instructions
191    let compiled_instructions: Vec<CompiledInstructionV0> = instructions
192        .iter()
193        .map(|ix| CompiledInstructionV0 {
194            program_id_index: *accounts_to_index.get(&ix.program_id).unwrap(),
195            accounts: ix
196                .accounts
197                .iter()
198                .map(|k| *accounts_to_index.get(&k.pubkey).unwrap())
199                .collect(),
200            data: ix.data.clone(),
201        })
202        .collect();
203
204    let remaining_accounts = sorted_accounts
205        .iter()
206        .enumerate()
207        .map(|(index, k)| AccountMeta {
208            pubkey: *k,
209            is_signer: false,
210            is_writable: index < num_rw_signers as usize
211                || (index >= num_rw_signers as usize + num_ro_signers as usize
212                    && index < num_rw_signers as usize + num_ro_signers as usize + num_rw as usize),
213        })
214        .collect();
215
216    Ok((
217        CompiledTransactionV0 {
218            num_ro_signers,
219            num_rw_signers,
220            num_rw,
221            instructions: compiled_instructions,
222            signer_seeds,
223            accounts: sorted_accounts,
224        },
225        remaining_accounts,
226    ))
227}