miclockwork_utils/
thread.rs

1use std::{convert::TryFrom, fmt::Debug, hash::Hash};
2
3use anchor_lang::{
4    prelude::borsh::BorshSchema,
5    prelude::Pubkey,
6    prelude::*,
7    solana_program::{self, instruction::Instruction},
8    AnchorDeserialize,
9};
10use serde::{Deserialize, Serialize};
11use static_pubkey::static_pubkey;
12
13/// The stand-in pubkey for delegating a payer address to a worker. All workers are re-imbursed by the user for lamports spent during this delegation.
14pub static PAYER_PUBKEY: Pubkey = static_pubkey!("C1ockworkPayer11111111111111111111111111111");
15
16/// The clock object, representing a specific moment in time recorded by a Solana cluster.
17#[derive(AnchorDeserialize, AnchorSerialize, BorshSchema, Clone, Debug, PartialEq)]
18pub struct ClockData {
19    /// The current slot.
20    pub slot: u64,
21    /// The bank epoch.
22    pub epoch: u64,
23    /// The current unix timestamp.
24    pub unix_timestamp: i64,
25}
26
27impl From<Clock> for ClockData {
28    fn from(clock: Clock) -> Self {
29        ClockData {
30            slot: clock.slot,
31            epoch: clock.epoch,
32            unix_timestamp: clock.unix_timestamp,
33        }
34    }
35}
36
37impl TryFrom<Vec<u8>> for ClockData {
38    type Error = Error;
39    fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
40        Ok(
41            borsh::try_from_slice_with_schema::<ClockData>(data.as_slice())
42                .map_err(|_err| ErrorCode::AccountDidNotDeserialize)?,
43        )
44    }
45}
46
47/// The triggering conditions of a thread.
48#[derive(AnchorDeserialize, AnchorSerialize, Debug, Clone, PartialEq)]
49pub enum Trigger {
50    /// Allows a thread to be kicked off whenever the data of an account changes.
51    Account {
52        /// The address of the account to monitor.
53        address: Pubkey,
54        /// The byte offset of the account data to monitor.
55        offset: u64,
56        /// The size of the byte slice to monitor (must be less than 1kb)
57        size: u64,
58    },
59
60    /// Allows a thread to be kicked off according to a one-time or recurring schedule.
61    Cron {
62        /// The schedule in cron syntax. Value must be parsable by the `clockwork_cron` package.
63        schedule: String,
64
65        /// Boolean value indicating whether triggering moments may be skipped if they are missed (e.g. due to network downtime).
66        /// If false, any "missed" triggering moments will simply be executed as soon as the network comes back online.
67        skippable: bool,
68    },
69
70    /// Allows a thread to be kicked off as soon as it's created.
71    Now,
72
73    /// Allows a thread to be kicked off according to a slot.
74    Slot { slot: u64 },
75
76    /// Allows a thread to be kicked off according to an epoch number.
77    Epoch { epoch: u64 },
78
79    /// Allows a thread to be kicked off according to a unix timestamp.
80    Timestamp { unix_ts: i64 },
81
82    /// Allows a thread to be kicked off according to a Pyth price feed movement.
83    Pyth {
84        /// The address of the price feed to monitor.
85        price_feed: Pubkey,
86        /// The equality operator (gte or lte) used to compare prices. 
87        equality: Equality,
88        /// The limit price to compare the Pyth feed to. 
89        limit: i64,
90    },
91}
92
93/// Operators for describing how to compare two values to one another.  
94#[repr(u8)]
95#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug, Eq, PartialEq, Hash)]
96pub enum Equality {
97    GreaterThanOrEqual,
98    LessThanOrEqual,
99}
100
101/// A response value target programs can return to update the thread.
102#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
103pub struct ThreadResponse {
104    /// If set, the thread will automatically close and return lamports to the provided address.
105    /// If dynamic_instruction is also set, close_to will take precedence and the dynamic instruction will not be executed.
106    pub close_to: Option<Pubkey>,
107    /// A dynamic instruction to execute next.
108    /// If close_to is also set, it will take precedence and the dynamic instruction will not be executed.
109    pub dynamic_instruction: Option<SerializableInstruction>,
110    /// Value to update the thread trigger to.
111    pub trigger: Option<Trigger>,
112}
113
114impl Default for ThreadResponse {
115    fn default() -> Self {
116        return Self {
117            close_to: None,
118            dynamic_instruction: None,
119            trigger: None,
120        };
121    }
122}
123
124/// The data needed execute an instruction on Solana.
125#[derive(
126    AnchorDeserialize,
127    AnchorSerialize,
128    Serialize,
129    Deserialize,
130    BorshSchema,
131    Clone,
132    Debug,
133    Hash,
134    PartialEq,
135)]
136pub struct SerializableInstruction {
137    /// Pubkey of the instruction processor that executes this instruction
138    pub program_id: Pubkey,
139    /// Metadata for what accounts should be passed to the instruction processor
140    pub accounts: Vec<SerializableAccount>,
141    /// Opaque data passed to the instruction processor
142    pub data: Vec<u8>,
143}
144
145impl From<Instruction> for SerializableInstruction {
146    fn from(instruction: Instruction) -> Self {
147        SerializableInstruction {
148            program_id: instruction.program_id,
149            accounts: instruction
150                .accounts
151                .iter()
152                .map(|a| SerializableAccount {
153                    pubkey: a.pubkey,
154                    is_signer: a.is_signer,
155                    is_writable: a.is_writable,
156                })
157                .collect(),
158            data: instruction.data,
159        }
160    }
161}
162
163impl From<&SerializableInstruction> for Instruction {
164    fn from(instruction: &SerializableInstruction) -> Self {
165        Instruction {
166            program_id: instruction.program_id,
167            accounts: instruction
168                .accounts
169                .iter()
170                .map(|a| AccountMeta {
171                    pubkey: a.pubkey,
172                    is_signer: a.is_signer,
173                    is_writable: a.is_writable,
174                })
175                .collect(),
176            data: instruction.data.clone(),
177        }
178    }
179}
180
181impl TryFrom<Vec<u8>> for SerializableInstruction {
182    type Error = Error;
183    fn try_from(data: Vec<u8>) -> std::result::Result<Self, Self::Error> {
184        Ok(
185            borsh::try_from_slice_with_schema::<SerializableInstruction>(data.as_slice())
186                .map_err(|_err| ErrorCode::AccountDidNotDeserialize)?,
187        )
188    }
189}
190
191/// Account metadata needed to execute an instruction on Solana.
192#[derive(
193    AnchorDeserialize,
194    AnchorSerialize,
195    Serialize,
196    Deserialize,
197    BorshSchema,
198    Clone,
199    Debug,
200    Hash,
201    PartialEq,
202)]
203pub struct SerializableAccount {
204    /// An account's public key
205    pub pubkey: Pubkey,
206    /// True if an Instruction requires a Transaction signature matching `pubkey`.
207    pub is_signer: bool,
208    /// True if the `pubkey` can be loaded as a read-write account.
209    pub is_writable: bool,
210}
211
212impl SerializableAccount {
213    /// Construct metadata for a writable account.
214    pub fn mutable(pubkey: Pubkey, signer: bool) -> Self {
215        Self {
216            pubkey,
217            is_signer: signer,
218            is_writable: true,
219        }
220    }
221
222    /// Construct metadata for a read-only account.
223    pub fn readonly(pubkey: Pubkey, signer: bool) -> Self {
224        Self {
225            pubkey,
226            is_signer: signer,
227            is_writable: false,
228        }
229    }
230}