Skip to main content

roshi_interface/instructions/
mod.rs

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// Codama parses the enum source directly and currently requires literal
37// discriminator values here; keep `tags::*` in sync via the IDL test below.
38#[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}