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