1pub mod args;
2
3pub use args::*;
4
5use wincode::{config::DefaultConfig, SchemaRead, SchemaWrite};
6
7pub trait InstructionArgs: SchemaWrite<DefaultConfig, Src = Self> {
8 const TAG: u8;
9}
10
11pub mod tags {
12 pub const INITIALIZE_PROGRAM: u8 = 0;
13 pub const INITIALIZE_VAULT: u8 = 1;
14 pub const AUTHORIZE_ACTION: u8 = 2;
15 pub const REVOKE_ACTION: u8 = 3;
16 pub const MANAGE: u8 = 4;
17 pub const MANAGE_BATCH: u8 = 5;
18 pub const REPORT_NAV: u8 = 6;
19 pub const DEPOSIT: u8 = 7;
20 pub const REDEEM: u8 = 8;
21 pub const CANCEL_REDEEM: u8 = 9;
22 pub const PROCESS_WITHDRAWALS: u8 = 10;
23 pub const UPDATE_VAULT_CONFIG: u8 = 11;
24 pub const INITIALIZE_ASSET: u8 = 12;
25 pub const UPDATE_ASSET: u8 = 13;
26 pub const SET_PAUSE_FLAGS: u8 = 15;
27 pub const SET_VAULT_ACCESS: u8 = 16;
28 pub const TRANSFER_PROGRAM_AUTHORITY: u8 = 17;
29 pub const TRANSFER_VAULT_AUTHORITY: u8 = 18;
30 pub const SET_STRATEGIST: u8 = 19;
31 pub const SET_NAV_AUTHORITY: u8 = 20;
32 pub const SET_WITHDRAWAL_AUTHORITY: u8 = 21;
33 pub const COLLECT_FEES: u8 = 22;
34}
35
36#[repr(u8)]
39#[derive(codama_macros::CodamaInstructions)]
40#[codama(program(
41 name = "roshi",
42 address = "Roshi11111111111111111111111111111111111111"
43))]
44pub enum RoshiInstruction {
45 #[codama(account(name = "payer", signer, writable))]
46 #[codama(account(name = "program_config", writable))]
47 #[codama(account(name = "system_program", default_value = program("system")))]
48 InitializeProgram(#[codama(name = "args")] InitializeProgramArgs) = 0,
49
50 #[codama(account(name = "program_authority", signer))]
51 #[codama(account(name = "program_config"))]
52 #[codama(account(name = "payer", signer, writable))]
53 #[codama(account(name = "vault", writable))]
54 #[codama(account(name = "base_mint"))]
55 #[codama(account(name = "share_mint"))]
56 #[codama(account(name = "fee_collector"))]
57 #[codama(account(name = "system_program", default_value = program("system")))]
58 InitializeVault(#[codama(name = "args")] InitializeVaultArgs) = 1,
59
60 #[codama(account(name = "admin", signer, writable))]
61 #[codama(account(name = "vault"))]
62 #[codama(account(name = "action", writable))]
63 #[codama(account(name = "system_program", default_value = program("system")))]
64 AuthorizeAction(#[codama(name = "args")] AuthorizeActionArgs) = 2,
65
66 #[codama(account(name = "admin", signer))]
67 #[codama(account(name = "vault"))]
68 #[codama(account(name = "action", writable))]
69 RevokeAction(#[codama(name = "args")] RevokeActionArgs) = 3,
70
71 #[codama(account(name = "strategist", signer))]
72 #[codama(account(name = "vault"))]
73 #[codama(account(name = "sub_account", writable))]
74 #[codama(account(name = "action"))]
75 Manage(#[codama(name = "args")] ManageArgs) = 4,
76
77 #[codama(account(name = "strategist", signer))]
78 #[codama(account(name = "vault"))]
79 ManageBatch(#[codama(name = "args")] ManageBatchArgs) = 5,
80
81 #[codama(account(name = "nav_authority", signer))]
82 #[codama(account(name = "vault", writable))]
83 #[codama(account(name = "share_mint"))]
84 ReportNav(#[codama(name = "args")] ReportNavArgs) = 6,
85
86 #[codama(account(name = "depositor", signer))]
87 #[codama(account(name = "vault", writable))]
88 #[codama(account(name = "user_source_token_account", writable))]
89 #[codama(account(name = "vault_custody_token_account", writable))]
90 #[codama(account(name = "user_share_account", writable))]
91 #[codama(account(name = "share_mint", writable))]
92 #[codama(account(name = "token_program", default_value = program("token")))]
93 Deposit(#[codama(name = "args")] DepositArgs) = 7,
94
95 #[codama(account(name = "owner", signer, writable))]
96 #[codama(account(name = "vault", writable))]
97 #[codama(account(name = "user_share_account", writable))]
98 #[codama(account(name = "share_mint", writable))]
99 #[codama(account(name = "recipient_token_account"))]
100 #[codama(account(name = "withdrawal_ticket", writable))]
101 #[codama(account(name = "system_program", default_value = program("system")))]
102 #[codama(account(name = "token_program", default_value = program("token")))]
103 Redeem(#[codama(name = "args")] RedeemArgs) = 8,
104
105 #[codama(account(name = "owner", signer, writable))]
106 #[codama(account(name = "vault", writable))]
107 #[codama(account(name = "withdrawal_ticket", writable))]
108 #[codama(account(name = "share_mint", writable))]
109 #[codama(account(name = "owner_share_account", writable))]
110 #[codama(account(name = "token_program", default_value = program("token")))]
111 CancelRedeem(#[codama(name = "args")] CancelRedeemArgs) = 9,
112
113 #[codama(account(name = "withdrawal_authority", signer))]
114 #[codama(account(name = "vault", writable))]
115 #[codama(account(name = "withdraw_sub_account"))]
116 #[codama(account(name = "custody", writable))]
117 #[codama(account(name = "share_mint"))]
118 #[codama(account(name = "token_program", default_value = program("token")))]
119 ProcessWithdrawals = 10,
120
121 #[codama(account(name = "admin", signer))]
122 #[codama(account(name = "vault", writable))]
123 #[codama(account(name = "fee_collector"))]
124 UpdateVaultConfig(#[codama(name = "args")] UpdateVaultConfigArgs) = 11,
125
126 #[codama(account(name = "admin", signer, writable))]
127 #[codama(account(name = "vault"))]
128 #[codama(account(name = "asset", writable))]
129 #[codama(account(name = "system_program", default_value = program("system")))]
130 InitializeAsset(#[codama(name = "args")] InitializeAssetArgs) = 12,
131
132 #[codama(account(name = "admin", signer))]
133 #[codama(account(name = "vault"))]
134 #[codama(account(name = "asset", writable))]
135 UpdateAsset(#[codama(name = "args")] UpdateAssetArgs) = 13,
136
137 #[codama(account(name = "admin", signer))]
138 #[codama(account(name = "vault", writable))]
139 SetPauseFlags(#[codama(name = "args")] SetPauseFlagsArgs) = 15,
140
141 #[codama(account(name = "admin", signer))]
142 #[codama(account(name = "vault", writable))]
143 SetVaultAccess(#[codama(name = "args")] SetVaultAccessArgs) = 16,
144
145 #[codama(account(name = "authority", signer))]
146 #[codama(account(name = "program_config", writable))]
147 TransferProgramAuthority(#[codama(name = "args")] TransferProgramAuthorityArgs) = 17,
148
149 #[codama(account(name = "admin", signer))]
150 #[codama(account(name = "vault", writable))]
151 TransferVaultAuthority(#[codama(name = "args")] TransferVaultAuthorityArgs) = 18,
152
153 #[codama(account(name = "admin", signer))]
154 #[codama(account(name = "vault", writable))]
155 SetStrategist(#[codama(name = "args")] SetStrategistArgs) = 19,
156
157 #[codama(account(name = "admin", signer))]
158 #[codama(account(name = "vault", writable))]
159 SetNavAuthority(#[codama(name = "args")] SetNavAuthorityArgs) = 20,
160
161 #[codama(account(name = "admin", signer))]
162 #[codama(account(name = "vault", writable))]
163 SetWithdrawalAuthority(#[codama(name = "args")] SetWithdrawalAuthorityArgs) = 21,
164
165 #[codama(account(name = "admin", signer))]
166 #[codama(account(name = "vault", writable))]
167 #[codama(account(name = "fee_sub_account"))]
168 #[codama(account(name = "custody", writable))]
169 #[codama(account(name = "fee_collector", writable))]
170 #[codama(account(name = "token_program", default_value = program("token")))]
171 CollectFees(#[codama(name = "args")] CollectFeesArgs) = 22,
172}
173
174impl RoshiInstruction {
175 pub const fn tag(&self) -> u8 {
176 match self {
177 Self::InitializeProgram(_) => tags::INITIALIZE_PROGRAM,
178 Self::InitializeVault(_) => tags::INITIALIZE_VAULT,
179 Self::AuthorizeAction(_) => tags::AUTHORIZE_ACTION,
180 Self::RevokeAction(_) => tags::REVOKE_ACTION,
181 Self::Manage(_) => tags::MANAGE,
182 Self::ManageBatch(_) => tags::MANAGE_BATCH,
183 Self::ReportNav(_) => tags::REPORT_NAV,
184 Self::Deposit(_) => tags::DEPOSIT,
185 Self::Redeem(_) => tags::REDEEM,
186 Self::CancelRedeem(_) => tags::CANCEL_REDEEM,
187 Self::ProcessWithdrawals => tags::PROCESS_WITHDRAWALS,
188 Self::UpdateVaultConfig(_) => tags::UPDATE_VAULT_CONFIG,
189 Self::InitializeAsset(_) => tags::INITIALIZE_ASSET,
190 Self::UpdateAsset(_) => tags::UPDATE_ASSET,
191 Self::SetPauseFlags(_) => tags::SET_PAUSE_FLAGS,
192 Self::SetVaultAccess(_) => tags::SET_VAULT_ACCESS,
193 Self::TransferProgramAuthority(_) => tags::TRANSFER_PROGRAM_AUTHORITY,
194 Self::TransferVaultAuthority(_) => tags::TRANSFER_VAULT_AUTHORITY,
195 Self::SetStrategist(_) => tags::SET_STRATEGIST,
196 Self::SetNavAuthority(_) => tags::SET_NAV_AUTHORITY,
197 Self::SetWithdrawalAuthority(_) => tags::SET_WITHDRAWAL_AUTHORITY,
198 Self::CollectFees(_) => tags::COLLECT_FEES,
199 }
200 }
201
202 pub fn decode(data: &[u8]) -> Result<Self, ()> {
203 let (tag, payload) = data.split_first().ok_or(())?;
204
205 match *tag {
206 tags::INITIALIZE_PROGRAM => Ok(Self::InitializeProgram(decode_payload(payload)?)),
207 tags::INITIALIZE_VAULT => Ok(Self::InitializeVault(decode_payload(payload)?)),
208 tags::AUTHORIZE_ACTION => Ok(Self::AuthorizeAction(decode_payload(payload)?)),
209 tags::REVOKE_ACTION => Ok(Self::RevokeAction(decode_payload(payload)?)),
210 tags::MANAGE => Ok(Self::Manage(decode_payload(payload)?)),
211 tags::MANAGE_BATCH => Ok(Self::ManageBatch(decode_payload(payload)?)),
212 tags::REPORT_NAV => Ok(Self::ReportNav(decode_payload(payload)?)),
213 tags::DEPOSIT => Ok(Self::Deposit(decode_payload(payload)?)),
214 tags::REDEEM => Ok(Self::Redeem(decode_payload(payload)?)),
215 tags::CANCEL_REDEEM => Ok(Self::CancelRedeem(decode_payload(payload)?)),
216 tags::PROCESS_WITHDRAWALS => {
217 let ProcessWithdrawalsArgs = decode_payload(payload)?;
218 Ok(Self::ProcessWithdrawals)
219 }
220 tags::UPDATE_VAULT_CONFIG => Ok(Self::UpdateVaultConfig(decode_payload(payload)?)),
221 tags::INITIALIZE_ASSET => Ok(Self::InitializeAsset(decode_payload(payload)?)),
222 tags::UPDATE_ASSET => Ok(Self::UpdateAsset(decode_payload(payload)?)),
223 tags::SET_PAUSE_FLAGS => Ok(Self::SetPauseFlags(decode_payload(payload)?)),
224 tags::SET_VAULT_ACCESS => Ok(Self::SetVaultAccess(decode_payload(payload)?)),
225 tags::TRANSFER_PROGRAM_AUTHORITY => {
226 Ok(Self::TransferProgramAuthority(decode_payload(payload)?))
227 }
228 tags::TRANSFER_VAULT_AUTHORITY => {
229 Ok(Self::TransferVaultAuthority(decode_payload(payload)?))
230 }
231 tags::SET_STRATEGIST => Ok(Self::SetStrategist(decode_payload(payload)?)),
232 tags::SET_NAV_AUTHORITY => Ok(Self::SetNavAuthority(decode_payload(payload)?)),
233 tags::SET_WITHDRAWAL_AUTHORITY => {
234 Ok(Self::SetWithdrawalAuthority(decode_payload(payload)?))
235 }
236 tags::COLLECT_FEES => Ok(Self::CollectFees(decode_payload(payload)?)),
237 _ => Err(()),
238 }
239 }
240
241 pub fn serialize(&self) -> Result<Vec<u8>, wincode::WriteError> {
242 let mut data = vec![self.tag()];
243
244 match self {
245 Self::InitializeProgram(args) => wincode::serialize_into(&mut data, args)?,
246 Self::InitializeVault(args) => wincode::serialize_into(&mut data, args)?,
247 Self::AuthorizeAction(args) => wincode::serialize_into(&mut data, args)?,
248 Self::RevokeAction(args) => wincode::serialize_into(&mut data, args)?,
249 Self::Manage(args) => wincode::serialize_into(&mut data, args)?,
250 Self::ManageBatch(args) => wincode::serialize_into(&mut data, args)?,
251 Self::ReportNav(args) => wincode::serialize_into(&mut data, args)?,
252 Self::Deposit(args) => wincode::serialize_into(&mut data, args)?,
253 Self::Redeem(args) => wincode::serialize_into(&mut data, args)?,
254 Self::CancelRedeem(args) => wincode::serialize_into(&mut data, args)?,
255 Self::ProcessWithdrawals => {
256 wincode::serialize_into(&mut data, &ProcessWithdrawalsArgs)?
257 }
258 Self::UpdateVaultConfig(args) => wincode::serialize_into(&mut data, args)?,
259 Self::InitializeAsset(args) => wincode::serialize_into(&mut data, args)?,
260 Self::UpdateAsset(args) => wincode::serialize_into(&mut data, args)?,
261 Self::SetPauseFlags(args) => wincode::serialize_into(&mut data, args)?,
262 Self::SetVaultAccess(args) => wincode::serialize_into(&mut data, args)?,
263 Self::TransferProgramAuthority(args) => wincode::serialize_into(&mut data, args)?,
264 Self::TransferVaultAuthority(args) => wincode::serialize_into(&mut data, args)?,
265 Self::SetStrategist(args) => wincode::serialize_into(&mut data, args)?,
266 Self::SetNavAuthority(args) => wincode::serialize_into(&mut data, args)?,
267 Self::SetWithdrawalAuthority(args) => wincode::serialize_into(&mut data, args)?,
268 Self::CollectFees(args) => wincode::serialize_into(&mut data, args)?,
269 }
270
271 Ok(data)
272 }
273}
274
275fn decode_payload<'a, T>(payload: &'a [u8]) -> Result<T, ()>
276where
277 T: SchemaRead<'a, DefaultConfig, Dst = T>,
278{
279 wincode::deserialize_exact(payload).map_err(|_| ())
280}
281
282macro_rules! impl_instruction_args {
283 ($( $args:ty = $tag:expr ),+ $(,)?) => {
284 $(
285 impl InstructionArgs for $args {
286 const TAG: u8 = $tag;
287 }
288 )+
289
290 #[cfg(test)]
291 const TAG_CASES: &[u8] = &[
292 $(
293 $tag,
294 )+
295 ];
296 };
297}
298
299impl_instruction_args! {
300 InitializeProgramArgs = tags::INITIALIZE_PROGRAM,
301 InitializeVaultArgs = tags::INITIALIZE_VAULT,
302 AuthorizeActionArgs = tags::AUTHORIZE_ACTION,
303 RevokeActionArgs = tags::REVOKE_ACTION,
304 ManageArgs = tags::MANAGE,
305 ManageBatchArgs = tags::MANAGE_BATCH,
306 ReportNavArgs = tags::REPORT_NAV,
307 DepositArgs = tags::DEPOSIT,
308 RedeemArgs = tags::REDEEM,
309 CancelRedeemArgs = tags::CANCEL_REDEEM,
310 ProcessWithdrawalsArgs = tags::PROCESS_WITHDRAWALS,
311 UpdateVaultConfigArgs = tags::UPDATE_VAULT_CONFIG,
312 InitializeAssetArgs = tags::INITIALIZE_ASSET,
313 UpdateAssetArgs = tags::UPDATE_ASSET,
314 SetPauseFlagsArgs = tags::SET_PAUSE_FLAGS,
315 SetVaultAccessArgs = tags::SET_VAULT_ACCESS,
316 TransferProgramAuthorityArgs = tags::TRANSFER_PROGRAM_AUTHORITY,
317 TransferVaultAuthorityArgs = tags::TRANSFER_VAULT_AUTHORITY,
318 SetStrategistArgs = tags::SET_STRATEGIST,
319 SetNavAuthorityArgs = tags::SET_NAV_AUTHORITY,
320 SetWithdrawalAuthorityArgs = tags::SET_WITHDRAWAL_AUTHORITY,
321 CollectFeesArgs = tags::COLLECT_FEES,
322}
323
324pub fn serialize_instruction<T>(args: &T) -> Result<Vec<u8>, wincode::WriteError>
325where
326 T: InstructionArgs,
327{
328 let mut data = vec![T::TAG];
329 wincode::serialize_into(&mut data, args)?;
330 Ok(data)
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use codama::{Codama, NodeTrait};
337 use serde_json::Value;
338 use std::path::Path;
339 use wincode::deserialize_exact;
340
341 #[test]
342 fn instruction_args_tags_match_canonical_tags() {
343 assert_eq!(
344 TAG_CASES,
345 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22]
346 );
347 assert_eq!(
348 RoshiInstruction::ProcessWithdrawals.tag(),
349 <ProcessWithdrawalsArgs as InstructionArgs>::TAG
350 );
351 }
352
353 #[test]
354 fn codama_idl_uses_canonical_instruction_discriminators() {
355 let mut idl = Codama::load(Path::new(env!("CARGO_MANIFEST_DIR")))
356 .unwrap()
357 .get_idl()
358 .unwrap();
359 idl.program.name = "roshi".into();
360 let idl: Value = serde_json::from_str(&idl.to_json().unwrap()).unwrap();
361 let instructions = idl["program"]["instructions"].as_array().unwrap();
362
363 assert_eq!(idl["program"]["name"], "roshi");
364 assert_eq!(
365 idl["program"]["publicKey"],
366 "Roshi11111111111111111111111111111111111111"
367 );
368 assert_eq!(instructions.len(), TAG_CASES.len());
369
370 for (name, tag) in IDL_TAG_CASES {
371 assert_instruction_discriminator(instructions, name, *tag);
372 }
373
374 let deposit = instruction(instructions, "deposit");
375 assert_eq!(deposit["arguments"][1]["name"], "args");
376 assert_eq!(deposit["arguments"][1]["type"]["name"], "depositArgs");
377
378 let process_withdrawals = instruction(instructions, "processWithdrawals");
379 assert_eq!(
380 process_withdrawals["arguments"].as_array().unwrap().len(),
381 1
382 );
383 }
384
385 #[test]
386 fn instruction_decode_rejects_unknown_values() {
387 assert!(RoshiInstruction::decode(&[255]).is_err());
388 }
389
390 #[test]
391 fn serialize_instruction_writes_tag_then_args_payload() {
392 let args = DepositArgs {
393 asset_mint: [4; 32],
394 amount: 123,
395 min_shares_out: 456,
396 access_proof: vec![[1; 32], [2; 32], [3; 32]],
397 };
398
399 let encoded = serialize_instruction(&args).unwrap();
400 let decoded: DepositArgs = deserialize_exact(&encoded[1..]).unwrap();
401
402 assert_eq!(encoded[0], <DepositArgs as InstructionArgs>::TAG);
403 assert_eq!(decoded.asset_mint, [4; 32]);
404 assert_eq!(decoded.amount, 123);
405 assert_eq!(decoded.min_shares_out, 456);
406 assert_eq!(decoded.access_proof, vec![[1; 32], [2; 32], [3; 32]]);
407 }
408
409 #[test]
410 fn canonical_instruction_serializes_like_args_helper() {
411 let args = DepositArgs {
412 asset_mint: [4; 32],
413 amount: 123,
414 min_shares_out: 456,
415 access_proof: vec![[1; 32]],
416 };
417
418 assert_eq!(
419 RoshiInstruction::Deposit(args).serialize().unwrap(),
420 serialize_instruction(&DepositArgs {
421 asset_mint: [4; 32],
422 amount: 123,
423 min_shares_out: 456,
424 access_proof: vec![[1; 32]],
425 })
426 .unwrap()
427 );
428 }
429
430 #[test]
431 fn serialize_zero_sized_args_writes_only_tag() {
432 assert_eq!(
433 serialize_instruction(&ProcessWithdrawalsArgs).unwrap(),
434 vec![<ProcessWithdrawalsArgs as InstructionArgs>::TAG]
435 );
436 assert_eq!(
437 RoshiInstruction::ProcessWithdrawals.serialize().unwrap(),
438 vec![<ProcessWithdrawalsArgs as InstructionArgs>::TAG]
439 );
440 }
441
442 fn instruction<'a>(instructions: &'a [Value], name: &str) -> &'a Value {
443 instructions
444 .iter()
445 .find(|instruction| instruction["name"] == name)
446 .unwrap()
447 }
448
449 fn assert_instruction_discriminator(instructions: &[Value], name: &str, tag: u8) {
450 assert_eq!(
451 instruction(instructions, name)["arguments"][0]["defaultValue"]["number"],
452 u64::from(tag)
453 );
454 }
455
456 const IDL_TAG_CASES: &[(&str, u8)] = &[
457 ("initializeProgram", tags::INITIALIZE_PROGRAM),
458 ("initializeVault", tags::INITIALIZE_VAULT),
459 ("authorizeAction", tags::AUTHORIZE_ACTION),
460 ("revokeAction", tags::REVOKE_ACTION),
461 ("manage", tags::MANAGE),
462 ("manageBatch", tags::MANAGE_BATCH),
463 ("reportNav", tags::REPORT_NAV),
464 ("deposit", tags::DEPOSIT),
465 ("redeem", tags::REDEEM),
466 ("cancelRedeem", tags::CANCEL_REDEEM),
467 ("processWithdrawals", tags::PROCESS_WITHDRAWALS),
468 ("updateVaultConfig", tags::UPDATE_VAULT_CONFIG),
469 ("initializeAsset", tags::INITIALIZE_ASSET),
470 ("updateAsset", tags::UPDATE_ASSET),
471 ("setPauseFlags", tags::SET_PAUSE_FLAGS),
472 ("setVaultAccess", tags::SET_VAULT_ACCESS),
473 ("transferProgramAuthority", tags::TRANSFER_PROGRAM_AUTHORITY),
474 ("transferVaultAuthority", tags::TRANSFER_VAULT_AUTHORITY),
475 ("setStrategist", tags::SET_STRATEGIST),
476 ("setNavAuthority", tags::SET_NAV_AUTHORITY),
477 ("setWithdrawalAuthority", tags::SET_WITHDRAWAL_AUTHORITY),
478 ("collectFees", tags::COLLECT_FEES),
479 ];
480}