sablier_utils/
thread.rs

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