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