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
16pub trait TimelockOps<C> {
18 fn initialize_timelock_config(
20 &self,
21 store: &Pubkey,
22 initial_delay: u32,
23 ) -> TransactionBuilder<C, Pubkey>;
24
25 fn increase_timelock_delay(&self, store: &Pubkey, delta: u32) -> TransactionBuilder<C>;
27
28 fn initialize_executor(
30 &self,
31 store: &Pubkey,
32 role: &str,
33 ) -> crate::Result<TransactionBuilder<C, Pubkey>>;
34
35 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 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 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 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 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 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 fn timelock_bypassed_revoke_role(
88 &self,
89 store: &Pubkey,
90 role: &str,
91 address: &Pubkey,
92 ) -> TransactionBuilder<C>;
93
94 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#[derive(Debug)]
489pub struct ExecuteTimelockedInstructionHint<'a> {
490 pub executor: &'a Pubkey,
492 pub rent_receiver: &'a Pubkey,
494 pub accounts: &'a [AccountMeta],
496}