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", 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}