1use std::cell::{Ref, RefMut};
2
3use anchor_lang::prelude::*;
4use gmsol_store::{
5 utils::pubkey::{optional_address, DEFAULT_PUBKEY},
6 CoreError,
7};
8use gmsol_utils::{instruction::InstructionError, InitSpace};
9
10use crate::states::create_executor_wallet_pda;
11
12pub use gmsol_utils::instruction::{InstructionAccess, InstructionAccount, InstructionAccountFlag};
13
14const MAX_FLAGS: usize = 8;
15
16#[account(zero_copy)]
18pub struct InstructionHeader {
19 version: u8,
20 flags: InstructionFlagContainer,
21 wallet_bump: u8,
22 padding_0: [u8; 5],
23 approved_at: i64,
25 pub(crate) executor: Pubkey,
27 program_id: Pubkey,
29 num_accounts: u16,
31 data_len: u16,
33 padding_1: [u8; 12],
34 pub(crate) rent_receiver: Pubkey,
35 approver: Pubkey,
36 reserved: [u8; 64],
37}
38
39impl InstructionHeader {
40 pub(crate) fn init_space(num_accounts: u16, data_len: u16) -> usize {
42 std::mem::size_of::<Self>()
43 + usize::from(data_len)
44 + usize::from(num_accounts) * InstructionAccount::INIT_SPACE
45 }
46
47 pub(crate) fn approve(&mut self, approver: Pubkey) -> Result<()> {
49 require!(!self.is_approved(), CoreError::PreconditionsAreNotMet);
50 require_keys_eq!(
51 self.approver,
52 DEFAULT_PUBKEY,
53 CoreError::PreconditionsAreNotMet
54 );
55
56 require_keys_neq!(approver, DEFAULT_PUBKEY, CoreError::InvalidArgument);
57
58 let clock = Clock::get()?;
59
60 self.flags.set_flag(InstructionFlag::Approved, true);
61 self.approved_at = clock.unix_timestamp;
62 self.approver = approver;
63
64 Ok(())
65 }
66
67 pub fn is_approved(&self) -> bool {
69 self.flags.get_flag(InstructionFlag::Approved)
70 }
71
72 pub fn approved_at(&self) -> Option<i64> {
74 self.is_approved().then_some(self.approved_at)
75 }
76
77 pub fn apporver(&self) -> Option<&Pubkey> {
79 optional_address(&self.approver)
80 }
81
82 pub fn is_executable(&self, delay: u32) -> Result<bool> {
84 let now = Clock::get()?.unix_timestamp;
85 let Some(approved_at) = self.approved_at() else {
86 return Ok(false);
87 };
88 let executable_at = approved_at.saturating_add_unsigned(delay as u64);
89 Ok(now >= executable_at)
90 }
91
92 pub fn executor(&self) -> &Pubkey {
94 &self.executor
95 }
96
97 pub fn wallet(&self) -> Result<Pubkey> {
99 match create_executor_wallet_pda(self.executor(), self.wallet_bump, &crate::ID) {
100 Ok(address) => Ok(address),
101 Err(err) => {
102 msg!("[Ix Buffer] failed to create wallet pda: {}", err);
103 err!(CoreError::Internal)
104 }
105 }
106 }
107
108 pub fn rent_receiver(&self) -> &Pubkey {
110 &self.rent_receiver
111 }
112}
113
114#[derive(num_enum::IntoPrimitive)]
116#[repr(u8)]
117pub enum InstructionFlag {
118 Approved,
120 }
122
123gmsol_utils::flags!(InstructionFlag, MAX_FLAGS, u8);
124
125pub struct InstructionRef<'a> {
127 header: Ref<'a, InstructionHeader>,
128 data: Ref<'a, [u8]>,
129 accounts: Ref<'a, [u8]>,
130}
131
132impl InstructionRef<'_> {
133 pub(crate) fn header(&self) -> &InstructionHeader {
134 &self.header
135 }
136}
137
138pub trait InstructionLoader<'info> {
140 fn load_instruction(&self) -> Result<InstructionRef>;
142
143 #[allow(clippy::too_many_arguments)]
145 fn load_and_init_instruction(
146 &self,
147 executor: Pubkey,
148 wallet_bump: u8,
149 rent_receiver: Pubkey,
150 program_id: Pubkey,
151 data: &[u8],
152 accounts: &[AccountInfo<'info>],
153 signers: &[u16],
154 ) -> Result<InstructionRef>;
155}
156
157impl<'info> InstructionLoader<'info> for AccountLoader<'info, InstructionHeader> {
158 fn load_instruction(&self) -> Result<InstructionRef> {
159 self.load()?;
161
162 let data = self.as_ref().try_borrow_data()?;
163
164 let (_disc, remaining_data) = Ref::map_split(data, |d| d.split_at(8));
165 let (header, remaining_data) = Ref::map_split(remaining_data, |d| {
166 d.split_at(std::mem::size_of::<InstructionHeader>())
167 });
168 let header = Ref::map(header, bytemuck::from_bytes::<InstructionHeader>);
169 let data_len = usize::from(header.data_len);
170 let (data, accounts) = Ref::map_split(remaining_data, |d| d.split_at(data_len));
171
172 let expected_accounts_len = usize::from(header.num_accounts);
173 require_eq!(accounts.len(), expected_accounts_len, CoreError::Internal);
174
175 Ok(InstructionRef {
176 header,
177 data,
178 accounts,
179 })
180 }
181
182 fn load_and_init_instruction(
183 &self,
184 executor: Pubkey,
185 wallet_bump: u8,
186 rent_receiver: Pubkey,
187 program_id: Pubkey,
188 instruction_data: &[u8],
189 instruction_accounts: &[AccountInfo<'info>],
190 signers: &[u16],
191 ) -> Result<InstructionRef> {
192 use gmsol_store::utils::dynamic_access::get_mut;
193
194 {
196 let data_len = instruction_data.len().try_into()?;
197 let num_accounts = instruction_accounts.len().try_into()?;
198 let mut header = self.load_init()?;
199 header.wallet_bump = wallet_bump;
200 header.executor = executor;
201 header.program_id = program_id;
202 header.num_accounts = num_accounts;
203 header.data_len = data_len;
204 header.rent_receiver = rent_receiver;
205
206 drop(header);
207
208 self.exit(&crate::ID)?;
209 }
210
211 {
213 self.load_mut()?;
215
216 let data = self.as_ref().try_borrow_mut_data()?;
217
218 msg!("[Timelock] buffer size: {}", data.len());
219
220 let (_disc, remaining_data) = RefMut::map_split(data, |d| d.split_at_mut(8));
221 let (header, remaining_data) = RefMut::map_split(remaining_data, |d| {
222 d.split_at_mut(std::mem::size_of::<InstructionHeader>())
223 });
224 let header = RefMut::map(header, bytemuck::from_bytes_mut::<InstructionHeader>);
225 let data_len = usize::from(header.data_len);
226 let (mut data, mut accounts) =
227 RefMut::map_split(remaining_data, |d| d.split_at_mut(data_len));
228
229 data.copy_from_slice(instruction_data);
230
231 let wallet = header.wallet()?;
232
233 for (idx, account) in instruction_accounts.iter().enumerate() {
234 let idx_u16: u16 = idx
235 .try_into()
236 .map_err(|_| error!(CoreError::InvalidArgument))?;
237 let dst = get_mut::<InstructionAccount>(&mut accounts, idx)
238 .ok_or_else(|| error!(CoreError::InvalidArgument))?;
239
240 let address = account.key();
241 let is_signer = signers.contains(&idx_u16);
242 if is_signer {
243 require_keys_eq!(wallet, address, CoreError::InvalidArgument);
245 }
246
247 dst.pubkey = address;
248 dst.flags
249 .set_flag(InstructionAccountFlag::Signer, is_signer);
250 dst.flags
251 .set_flag(InstructionAccountFlag::Writable, account.is_writable);
252 }
253 }
254
255 self.load_instruction()
256 }
257}
258
259impl InstructionAccess for InstructionRef<'_> {
260 fn wallet(&self) -> std::result::Result<Pubkey, InstructionError> {
261 self.header
262 .wallet()
263 .map_err(|_| InstructionError::FailedToGetWallet)
264 }
265
266 fn program_id(&self) -> &Pubkey {
267 &self.header.program_id
268 }
269
270 fn data(&self) -> &[u8] {
271 &self.data
272 }
273
274 fn num_accounts(&self) -> usize {
275 usize::from(self.header.num_accounts)
276 }
277
278 fn accounts(&self) -> impl Iterator<Item = &InstructionAccount> {
279 use gmsol_store::utils::dynamic_access::get;
280
281 let num_accounts = self.num_accounts();
282
283 (0..num_accounts).map(|idx| get(&self.accounts, idx).expect("must exist"))
284 }
285}
286
287#[cfg(feature = "utils")]
289pub mod utils {
290
291 use anchor_lang::{prelude::Pubkey, AccountDeserialize};
292 use bytes::Bytes;
293 use gmsol_store::utils::de;
294 use gmsol_utils::instruction::InstructionError;
295
296 use super::{InstructionAccess, InstructionHeader};
297
298 pub struct InstructionBuffer {
300 pub header: InstructionHeader,
302 data: Bytes,
303 accounts: Bytes,
304 }
305
306 impl InstructionAccess for InstructionBuffer {
307 fn wallet(&self) -> Result<Pubkey, InstructionError> {
308 self.header
309 .wallet()
310 .map_err(|_| InstructionError::FailedToGetWallet)
311 }
312
313 fn program_id(&self) -> &Pubkey {
314 &self.header.program_id
315 }
316
317 fn data(&self) -> &[u8] {
318 &self.data
319 }
320
321 fn num_accounts(&self) -> usize {
322 usize::from(self.header.num_accounts)
323 }
324
325 fn accounts(&self) -> impl Iterator<Item = &super::InstructionAccount> {
326 use gmsol_store::utils::dynamic_access::get;
327
328 let num_accounts = self.num_accounts();
329
330 (0..num_accounts).map(|idx| get(&self.accounts, idx).expect("must exist"))
331 }
332 }
333
334 impl AccountDeserialize for InstructionBuffer {
335 fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
336 de::check_discriminator::<InstructionHeader>(buf)?;
337 Self::try_deserialize_unchecked(buf)
338 }
339
340 fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
341 let header = de::try_deserailize_unchecked::<InstructionHeader>(buf)?;
342 let (_disc, data) = buf.split_at(8);
343 let (_header, remaining_data) = data.split_at(std::mem::size_of::<InstructionHeader>());
344 let data_len = usize::from(header.data_len);
345 let (data, accounts) = remaining_data.split_at(data_len);
346 Ok(Self {
347 header,
348 data: Bytes::copy_from_slice(data),
349 accounts: Bytes::copy_from_slice(accounts),
350 })
351 }
352 }
353}