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
96pub fn compile_transaction(
97    instructions: Vec<Instruction>,
98    signer_seeds: Vec<Vec<Vec<u8>>>,
99) -> Result<(CompiledTransactionV0, Vec<AccountMeta>)> {
100    let mut pubkeys_to_metadata: HashMap<Pubkey, AccountMeta> = HashMap::new();
101
102    // Process all instructions to build metadata
103    for ix in &instructions {
104        pubkeys_to_metadata
105            .entry(ix.program_id)
106            .or_insert(AccountMeta {
107                pubkey: ix.program_id,
108                is_signer: false,
109                is_writable: false,
110            });
111
112        for key in &ix.accounts {
113            let entry = pubkeys_to_metadata
114                .entry(key.pubkey)
115                .or_insert(AccountMeta {
116                    is_signer: false,
117                    is_writable: false,
118                    pubkey: key.pubkey,
119                });
120            entry.is_writable |= key.is_writable;
121            entry.is_signer |= key.is_signer;
122        }
123    }
124
125    // Sort accounts: writable signers first, then ro signers, then rw non-signers, then ro
126    let mut sorted_accounts: Vec<Pubkey> = pubkeys_to_metadata.keys().cloned().collect();
127    sorted_accounts.sort_by(|a, b| {
128        let a_meta = &pubkeys_to_metadata[a];
129        let b_meta = &pubkeys_to_metadata[b];
130
131        // Compare accounts based on priority: writable signers > readonly signers > writable > readonly
132        fn get_priority(meta: &AccountMeta) -> u8 {
133            match (meta.is_signer, meta.is_writable) {
134                (true, true) => 0,   // Writable signer: highest priority
135                (true, false) => 1,  // Readonly signer
136                (false, true) => 2,  // Writable non-signer
137                (false, false) => 3, // Readonly non-signer: lowest priority
138            }
139        }
140
141        get_priority(a_meta).cmp(&get_priority(b_meta))
142    });
143
144    // Count different types of accounts
145    let mut num_rw_signers = 0u8;
146    let mut num_ro_signers = 0u8;
147    let mut num_rw = 0u8;
148
149    for k in &sorted_accounts {
150        let metadata = &pubkeys_to_metadata[k];
151        if metadata.is_signer && metadata.is_writable {
152            num_rw_signers += 1;
153        } else if metadata.is_signer && !metadata.is_writable {
154            num_ro_signers += 1;
155        } else if metadata.is_writable {
156            num_rw += 1;
157        }
158    }
159
160    // Create accounts to index mapping
161    let accounts_to_index: HashMap<Pubkey, u8> = sorted_accounts
162        .iter()
163        .enumerate()
164        .map(|(i, k)| (*k, i as u8))
165        .collect();
166
167    // Compile instructions
168    let compiled_instructions: Vec<CompiledInstructionV0> = instructions
169        .iter()
170        .map(|ix| CompiledInstructionV0 {
171            program_id_index: *accounts_to_index.get(&ix.program_id).unwrap(),
172            accounts: ix
173                .accounts
174                .iter()
175                .map(|k| *accounts_to_index.get(&k.pubkey).unwrap())
176                .collect(),
177            data: ix.data.clone(),
178        })
179        .collect();
180
181    let remaining_accounts = sorted_accounts
182        .iter()
183        .enumerate()
184        .map(|(index, k)| AccountMeta {
185            pubkey: *k,
186            is_signer: false,
187            is_writable: index < num_rw_signers as usize
188                || (index >= num_rw_signers as usize + num_ro_signers as usize
189                    && index < num_rw_signers as usize + num_ro_signers as usize + num_rw as usize),
190        })
191        .collect();
192
193    Ok((
194        CompiledTransactionV0 {
195            num_ro_signers,
196            num_rw_signers,
197            num_rw,
198            instructions: compiled_instructions,
199            signer_seeds,
200            accounts: sorted_accounts,
201        },
202        remaining_accounts,
203    ))
204}