gmsol/
timelock.rs

1use std::{future::Future, ops::Deref, sync::Arc};
2
3use anchor_client::{
4    anchor_lang::{prelude::AccountMeta, system_program},
5    solana_sdk::{instruction::Instruction, pubkey::Pubkey, signer::Signer},
6};
7use gmsol_solana_utils::transaction_builder::TransactionBuilder;
8use gmsol_store::states::{PriceProviderKind, RoleKey};
9use gmsol_timelock::{
10    accounts, instruction, roles,
11    states::{Executor, InstructionAccess, InstructionHeader},
12};
13
14use crate::utils::ZeroCopy;
15
16/// Timelock instructions.
17pub trait TimelockOps<C> {
18    /// Initialize [`TimelockConfig`](crate::types::timelock::TimelockConfig) account.
19    fn initialize_timelock_config(
20        &self,
21        store: &Pubkey,
22        initial_delay: u32,
23    ) -> TransactionBuilder<C, Pubkey>;
24
25    /// Increase timelock delay.
26    fn increase_timelock_delay(&self, store: &Pubkey, delta: u32) -> TransactionBuilder<C>;
27
28    /// Initialize [`Executor`] account.
29    fn initialize_executor(
30        &self,
31        store: &Pubkey,
32        role: &str,
33    ) -> crate::Result<TransactionBuilder<C, Pubkey>>;
34
35    /// Create a timelocked instruction buffer for the given instruction.
36    fn create_timelocked_instruction(
37        &self,
38        store: &Pubkey,
39        role: &str,
40        buffer: impl Signer + 'static,
41        instruction: Instruction,
42    ) -> crate::Result<TransactionBuilder<C, Pubkey>>;
43
44    /// Approve timelocked instruction.
45    fn approve_timelocked_instruction(
46        &self,
47        store: &Pubkey,
48        buffer: &Pubkey,
49        role_hint: Option<&str>,
50    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
51
52    /// Approve timelocked instruction.
53    fn approve_timelocked_instructions(
54        &self,
55        store: &Pubkey,
56        buffers: impl IntoIterator<Item = Pubkey>,
57        role_hint: Option<&str>,
58    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
59
60    /// Cancel timelocked instruction.
61    fn cancel_timelocked_instruction(
62        &self,
63        store: &Pubkey,
64        buffer: &Pubkey,
65        executor_hint: Option<&Pubkey>,
66        rent_receiver_hint: Option<&Pubkey>,
67    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
68
69    /// Cancel timelocked instruction.
70    fn cancel_timelocked_instructions(
71        &self,
72        store: &Pubkey,
73        buffers: impl IntoIterator<Item = Pubkey>,
74        executor_hint: Option<&Pubkey>,
75        rent_receiver_hint: Option<&Pubkey>,
76    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
77
78    /// Execute timelocked instruction.
79    fn execute_timelocked_instruction(
80        &self,
81        store: &Pubkey,
82        buffer: &Pubkey,
83        hint: Option<ExecuteTimelockedInstructionHint<'_>>,
84    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
85
86    /// Timelock-bypassed revoke role.
87    fn timelock_bypassed_revoke_role(
88        &self,
89        store: &Pubkey,
90        role: &str,
91        address: &Pubkey,
92    ) -> TransactionBuilder<C>;
93
94    /// Timelock-bypassed set expected price provider.
95    fn timelock_bypassed_set_epxected_price_provider(
96        &self,
97        store: &Pubkey,
98        token_map: &Pubkey,
99        token: &Pubkey,
100        new_expected_price_provider: PriceProviderKind,
101    ) -> TransactionBuilder<C>;
102}
103
104impl<C: Deref<Target = impl Signer> + Clone> TimelockOps<C> for crate::Client<C> {
105    fn initialize_timelock_config(
106        &self,
107        store: &Pubkey,
108        initial_delay: u32,
109    ) -> TransactionBuilder<C, Pubkey> {
110        let config = self.find_timelock_config_address(store);
111        let executor = self
112            .find_executor_address(store, roles::ADMIN)
113            .expect("must success");
114        self.timelock_transaction()
115            .anchor_args(instruction::InitializeConfig {
116                delay: initial_delay,
117            })
118            .anchor_accounts(accounts::InitializeConfig {
119                authority: self.payer(),
120                store: *store,
121                timelock_config: config,
122                executor,
123                wallet: self.find_executor_wallet_address(&executor),
124                store_program: *self.store_program_id(),
125                system_program: system_program::ID,
126            })
127            .output(config)
128    }
129
130    fn increase_timelock_delay(&self, store: &Pubkey, delta: u32) -> TransactionBuilder<C> {
131        self.timelock_transaction()
132            .anchor_args(instruction::IncreaseDelay { delta })
133            .anchor_accounts(accounts::IncreaseDelay {
134                authority: self.payer(),
135                store: *store,
136                timelock_config: self.find_timelock_config_address(store),
137                store_program: *self.store_program_id(),
138            })
139    }
140
141    fn initialize_executor(
142        &self,
143        store: &Pubkey,
144        role: &str,
145    ) -> crate::Result<TransactionBuilder<C, Pubkey>> {
146        let executor = self.find_executor_address(store, role)?;
147        let wallet = self.find_executor_wallet_address(&executor);
148        Ok(self
149            .timelock_transaction()
150            .anchor_args(instruction::InitializeExecutor {
151                role: role.to_string(),
152            })
153            .anchor_accounts(accounts::InitializeExecutor {
154                payer: self.payer(),
155                store: *store,
156                executor,
157                wallet,
158                system_program: system_program::ID,
159            })
160            .output(executor))
161    }
162
163    fn create_timelocked_instruction(
164        &self,
165        store: &Pubkey,
166        role: &str,
167        buffer: impl Signer + 'static,
168        mut instruction: Instruction,
169    ) -> crate::Result<TransactionBuilder<C, Pubkey>> {
170        let executor = self.find_executor_address(store, role)?;
171        let instruction_buffer = buffer.pubkey();
172
173        let mut signers = vec![];
174
175        instruction
176            .accounts
177            .iter_mut()
178            .enumerate()
179            .for_each(|(idx, account)| {
180                if account.is_signer {
181                    signers.push(idx as u16);
182                }
183                account.is_signer = false;
184            });
185
186        let num_accounts = instruction
187            .accounts
188            .len()
189            .try_into()
190            .map_err(|_| crate::Error::invalid_argument("too many accounts"))?;
191
192        let data_len = instruction
193            .data
194            .len()
195            .try_into()
196            .map_err(|_| crate::Error::invalid_argument("data too long"))?;
197
198        let rpc = self
199            .timelock_transaction()
200            .anchor_args(instruction::CreateInstructionBuffer {
201                num_accounts,
202                data_len,
203                data: instruction.data,
204                signers,
205            })
206            .anchor_accounts(accounts::CreateInstructionBuffer {
207                authority: self.payer(),
208                store: *store,
209                executor,
210                instruction_buffer,
211                instruction_program: instruction.program_id,
212                store_program: *self.store_program_id(),
213                system_program: system_program::ID,
214            })
215            .accounts(instruction.accounts)
216            .owned_signer(Arc::new(buffer))
217            .output(instruction_buffer);
218        Ok(rpc)
219    }
220
221    async fn approve_timelocked_instruction(
222        &self,
223        store: &Pubkey,
224        buffer: &Pubkey,
225        role_hint: Option<&str>,
226    ) -> crate::Result<TransactionBuilder<C>> {
227        let role = match role_hint {
228            Some(role) => role.to_string(),
229            None => {
230                let instruction_header = self
231                    .account::<ZeroCopy<InstructionHeader>>(buffer)
232                    .await?
233                    .ok_or(crate::Error::NotFound)?
234                    .0;
235                let executor = instruction_header.executor();
236                let executor = self
237                    .account::<ZeroCopy<Executor>>(executor)
238                    .await?
239                    .ok_or(crate::Error::NotFound)?
240                    .0;
241                executor.role_name()?.to_string()
242            }
243        };
244        let executor = self.find_executor_address(store, &role)?;
245        Ok(self
246            .timelock_transaction()
247            .anchor_args(instruction::ApproveInstruction { role })
248            .anchor_accounts(accounts::ApproveInstruction {
249                authority: self.payer(),
250                store: *store,
251                executor,
252                instruction: *buffer,
253                store_program: *self.store_program_id(),
254            }))
255    }
256
257    async fn approve_timelocked_instructions(
258        &self,
259        store: &Pubkey,
260        buffers: impl IntoIterator<Item = Pubkey>,
261        role_hint: Option<&str>,
262    ) -> crate::Result<TransactionBuilder<C>> {
263        let mut buffers = buffers.into_iter().peekable();
264        let buffer = buffers
265            .peek()
266            .ok_or_else(|| crate::Error::invalid_argument("no instructions to appove"))?;
267        let role = match role_hint {
268            Some(role) => role.to_string(),
269            None => {
270                let instruction_header = self
271                    .account::<ZeroCopy<InstructionHeader>>(buffer)
272                    .await?
273                    .ok_or(crate::Error::NotFound)?
274                    .0;
275                let executor = instruction_header.executor();
276                let executor = self
277                    .account::<ZeroCopy<Executor>>(executor)
278                    .await?
279                    .ok_or(crate::Error::NotFound)?
280                    .0;
281                executor.role_name()?.to_string()
282            }
283        };
284        let executor = self.find_executor_address(store, &role)?;
285        Ok(self
286            .timelock_transaction()
287            .anchor_args(instruction::ApproveInstructions { role })
288            .anchor_accounts(accounts::ApproveInstructions {
289                authority: self.payer(),
290                store: *store,
291                executor,
292                store_program: *self.store_program_id(),
293            })
294            .accounts(
295                buffers
296                    .map(|pubkey| AccountMeta {
297                        pubkey,
298                        is_signer: false,
299                        is_writable: true,
300                    })
301                    .collect::<Vec<_>>(),
302            ))
303    }
304
305    async fn cancel_timelocked_instruction(
306        &self,
307        store: &Pubkey,
308        buffer: &Pubkey,
309        executor_hint: Option<&Pubkey>,
310        rent_receiver_hint: Option<&Pubkey>,
311    ) -> crate::Result<TransactionBuilder<C>> {
312        let (executor, rent_receiver) = match (executor_hint, rent_receiver_hint) {
313            (Some(executor), Some(rent_receiver)) => (*executor, *rent_receiver),
314            _ => {
315                let instruction_header = self
316                    .account::<ZeroCopy<InstructionHeader>>(buffer)
317                    .await?
318                    .ok_or(crate::Error::NotFound)?
319                    .0;
320                (
321                    *instruction_header.executor(),
322                    *instruction_header.rent_receiver(),
323                )
324            }
325        };
326        Ok(self
327            .timelock_transaction()
328            .anchor_args(instruction::CancelInstruction {})
329            .anchor_accounts(accounts::CancelInstruction {
330                authority: self.payer(),
331                store: *store,
332                executor,
333                rent_receiver,
334                instruction: *buffer,
335                store_program: *self.store_program_id(),
336            }))
337    }
338
339    async fn cancel_timelocked_instructions(
340        &self,
341        store: &Pubkey,
342        buffers: impl IntoIterator<Item = Pubkey>,
343        executor_hint: Option<&Pubkey>,
344        rent_receiver_hint: Option<&Pubkey>,
345    ) -> crate::Result<TransactionBuilder<C>> {
346        let mut buffers = buffers.into_iter().peekable();
347        let buffer = buffers
348            .peek()
349            .ok_or_else(|| crate::Error::invalid_argument("no instructions to appove"))?;
350        let (executor, rent_receiver) = match (executor_hint, rent_receiver_hint) {
351            (Some(executor), Some(rent_receiver)) => (*executor, *rent_receiver),
352            _ => {
353                let instruction_header = self
354                    .account::<ZeroCopy<InstructionHeader>>(buffer)
355                    .await?
356                    .ok_or(crate::Error::NotFound)?
357                    .0;
358                (
359                    *instruction_header.executor(),
360                    *instruction_header.rent_receiver(),
361                )
362            }
363        };
364        Ok(self
365            .timelock_transaction()
366            .anchor_args(instruction::CancelInstructions {})
367            .anchor_accounts(accounts::CancelInstructions {
368                authority: self.payer(),
369                store: *store,
370                executor,
371                rent_receiver,
372                store_program: *self.store_program_id(),
373            })
374            .accounts(
375                buffers
376                    .map(|pubkey| AccountMeta {
377                        pubkey,
378                        is_signer: false,
379                        is_writable: true,
380                    })
381                    .collect::<Vec<_>>(),
382            ))
383    }
384
385    async fn execute_timelocked_instruction(
386        &self,
387        store: &Pubkey,
388        buffer: &Pubkey,
389        hint: Option<ExecuteTimelockedInstructionHint<'_>>,
390    ) -> crate::Result<TransactionBuilder<C>> {
391        let (executor, rent_receiver, mut accounts) = match hint {
392            Some(hint) => (
393                *hint.executor,
394                *hint.rent_receiver,
395                hint.accounts.to_owned(),
396            ),
397            None => {
398                let buffer = self
399                    .instruction_buffer(buffer)
400                    .await?
401                    .ok_or(crate::Error::NotFound)?;
402                let executor = buffer.header.executor();
403                let rent_receiver = buffer.header.rent_receiver();
404                (
405                    *executor,
406                    *rent_receiver,
407                    buffer.accounts().map(AccountMeta::from).collect(),
408                )
409            }
410        };
411
412        let wallet = self.find_executor_wallet_address(&executor);
413
414        accounts
415            .iter_mut()
416            .filter(|a| a.pubkey == wallet)
417            .for_each(|a| a.is_signer = false);
418
419        Ok(self
420            .timelock_transaction()
421            .anchor_args(instruction::ExecuteInstruction {})
422            .anchor_accounts(accounts::ExecuteInstruction {
423                authority: self.payer(),
424                store: *store,
425                timelock_config: self.find_timelock_config_address(store),
426                executor,
427                wallet,
428                rent_receiver,
429                instruction: *buffer,
430                store_program: *self.store_program_id(),
431            })
432            .accounts(accounts))
433    }
434
435    fn timelock_bypassed_revoke_role(
436        &self,
437        store: &Pubkey,
438        role: &str,
439        address: &Pubkey,
440    ) -> TransactionBuilder<C> {
441        let executor = self
442            .find_executor_address(store, roles::ADMIN)
443            .expect("must success");
444        let wallet = self.find_executor_wallet_address(&executor);
445        self.timelock_transaction()
446            .anchor_args(instruction::RevokeRole {
447                role: role.to_string(),
448            })
449            .anchor_accounts(accounts::RevokeRole {
450                authority: self.payer(),
451                store: *store,
452                executor,
453                wallet,
454                user: *address,
455                store_program: *self.store_program_id(),
456            })
457    }
458
459    fn timelock_bypassed_set_epxected_price_provider(
460        &self,
461        store: &Pubkey,
462        token_map: &Pubkey,
463        token: &Pubkey,
464        new_expected_price_provider: PriceProviderKind,
465    ) -> TransactionBuilder<C> {
466        let executor = self
467            .find_executor_address(store, RoleKey::MARKET_KEEPER)
468            .expect("must success");
469        let wallet = self.find_executor_wallet_address(&executor);
470        self.timelock_transaction()
471            .anchor_args(instruction::SetExpectedPriceProvider {
472                new_expected_price_provider: new_expected_price_provider.into(),
473            })
474            .anchor_accounts(accounts::SetExpectedPriceProvider {
475                authority: self.payer(),
476                store: *store,
477                executor,
478                wallet,
479                token_map: *token_map,
480                token: *token,
481                store_program: *self.store_program_id(),
482                system_program: system_program::ID,
483            })
484    }
485}
486
487/// Execute timelocked instruction hint.
488#[derive(Debug)]
489pub struct ExecuteTimelockedInstructionHint<'a> {
490    /// Executor.
491    pub executor: &'a Pubkey,
492    /// Rent receiver.
493    pub rent_receiver: &'a Pubkey,
494    /// Accounts.
495    pub accounts: &'a [AccountMeta],
496}