shank-parse 2.0.0

A proc-macro crate that generates Rust client code from Shank/Anchor IDL JSON files for Solana programs.
Documentation
shank_parse::shank_parse!("../idl/vault.json");

#[cfg(test)]
mod tests {
    use super::vault::accounts::*;
    use super::vault::instructions::*;
    use super::vault::types::*;
    use super::vault::ID;
    use shank_parse::__private::Pubkey;

    // ── Program ID ────────────────────────────────────────────────────────────

    #[test]
    fn test_program_id() {
        assert_eq!(ID.to_string(), "9pq4Z25ahsUMFZ9TrKH1yhmibLbGkxxAdbTn7FUJ7reV");
    }

    // ── accounts ─────────────────────────────────────────────────────────────

    #[test]
    fn test_vault_from_account_data() {
        let owner = [1u8; 32];
        let vault_id = [2u8; 32];
        let token_mint_a = [3u8; 32];
        let token_mint_b = [4u8; 32];
        let token_account_a = [5u8; 32];
        let token_account_b = [6u8; 32];
        let token_program_a = [7u8; 32];
        let token_program_b = [8u8; 32];
        let reserve_a: u64 = 1000;
        let reserve_b: u64 = 2000;
        let k: u128 = 2_000_000;
        let bump: u8 = 255;

        let mut data = Vec::new();
        data.extend_from_slice(&owner);
        data.extend_from_slice(&vault_id);
        data.extend_from_slice(&token_mint_a);
        data.extend_from_slice(&token_mint_b);
        data.extend_from_slice(&token_account_a);
        data.extend_from_slice(&token_account_b);
        data.extend_from_slice(&token_program_a);
        data.extend_from_slice(&token_program_b);
        data.extend_from_slice(&reserve_a.to_le_bytes());
        data.extend_from_slice(&reserve_b.to_le_bytes());
        data.extend_from_slice(&k.to_le_bytes());
        data.push(bump);

        let vault = Vault::from_account_data(&data).unwrap();
        assert_eq!(vault.owner, owner);
        assert_eq!(vault.vault_id, vault_id);
        assert_eq!(vault.token_mint_a, token_mint_a);
        assert_eq!(vault.reserve_a, reserve_a);
        assert_eq!(vault.reserve_b, reserve_b);
        assert_eq!(vault.k, k);
        assert_eq!(vault.bump, bump);
    }

    // ── instructions ─────────────────────────────────────────────────────────

    #[test]
    fn test_create_vault_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = CreateVaultAccounts {
            owner: Pubkey::new_from_array([2u8; 32]),
            vault: Pubkey::new_from_array([3u8; 32]),
            user_token_account_a: Pubkey::new_from_array([4u8; 32]),
            user_token_account_b: Pubkey::new_from_array([5u8; 32]),
            vault_token_account_a: Pubkey::new_from_array([6u8; 32]),
            vault_token_account_b: Pubkey::new_from_array([7u8; 32]),
            token_program_a: Pubkey::new_from_array([8u8; 32]),
            token_program_b: Pubkey::new_from_array([9u8; 32]),
            system_program: Pubkey::new_from_array([10u8; 32]),
        };
        let args = CreateVaultArgs {
            vault_id: [11u8; 32],
            token_mint_a: [12u8; 32],
            token_mint_b: [13u8; 32],
            amount_a: 500,
            amount_b: 1000,
        };
        let ix = create_vault(&program_id, &accounts, &args).unwrap();

        assert_eq!(ix.program_id, program_id);
        assert_eq!(ix.accounts.len(), 9);
        assert_eq!(ix.data[0], 0); // discriminant
        assert!(ix.accounts[0].is_signer); // owner
        assert!(ix.accounts[1].is_writable); // vault
    }

    #[test]
    fn test_swap_exact_in_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = SwapExactInAccounts {
            user: Pubkey::new_from_array([2u8; 32]),
            vault: Pubkey::new_from_array([3u8; 32]),
            user_token_in: Pubkey::new_from_array([4u8; 32]),
            vault_token_in: Pubkey::new_from_array([5u8; 32]),
            user_token_out: Pubkey::new_from_array([6u8; 32]),
            vault_token_out: Pubkey::new_from_array([7u8; 32]),
            token_program_in: Pubkey::new_from_array([8u8; 32]),
            token_program_out: Pubkey::new_from_array([9u8; 32]),
        };
        let args = SwapExactInArgs {
            amount_in: 100,
            min_amount_out: 90,
        };
        let ix = swap_exact_in(&program_id, &accounts, &args).unwrap();

        assert_eq!(ix.data[0], 1); // discriminant
        assert_eq!(ix.accounts.len(), 8);
        assert_eq!(
            u64::from_le_bytes(ix.data[1..9].try_into().expect("slice")),
            100
        );
        assert_eq!(
            u64::from_le_bytes(ix.data[9..17].try_into().expect("slice")),
            90
        );
    }

    #[test]
    fn test_swap_exact_out_instruction() {
        let program_id = Pubkey::new_from_array([1u8; 32]);
        let accounts = SwapExactOutAccounts {
            user: Pubkey::new_from_array([2u8; 32]),
            vault: Pubkey::new_from_array([3u8; 32]),
            user_token_in: Pubkey::new_from_array([4u8; 32]),
            vault_token_in: Pubkey::new_from_array([5u8; 32]),
            user_token_out: Pubkey::new_from_array([6u8; 32]),
            vault_token_out: Pubkey::new_from_array([7u8; 32]),
            token_program_in: Pubkey::new_from_array([8u8; 32]),
            token_program_out: Pubkey::new_from_array([9u8; 32]),
        };
        let args = SwapExactOutArgs {
            amount_out: 200,
            max_amount_in: 250,
        };
        let ix = swap_exact_out(&program_id, &accounts, &args).unwrap();

        assert_eq!(ix.data[0], 2); // discriminant
    }

    // ── events ───────────────────────────────────────────────────────────────

    #[test]
    fn test_vault_created_try_from_slice() {
        let vault_addr = [42u8; 32];
        let owner_addr = [43u8; 32];

        // event payload: no leading discriminant byte
        let mut payload = Vec::new();
        payload.extend_from_slice(&vault_addr);
        payload.extend_from_slice(&owner_addr);
        payload.extend_from_slice(&1000u64.to_le_bytes());
        payload.extend_from_slice(&2000u64.to_le_bytes());
        payload.extend_from_slice(&2_000_000u128.to_le_bytes());

        let ev = VaultCreated::try_from_slice(&payload).unwrap();
        assert_eq!(ev.vault, vault_addr);
        assert_eq!(ev.owner, owner_addr);
        assert_eq!(ev.reserve_a, 1000);
        assert_eq!(ev.k, 2_000_000);
    }

    #[test]
    fn test_vault_event_enum_try_from_slice() {
        // VaultCreated (tag 0)
        let mut data0 = vec![0u8];
        data0.extend_from_slice(&[1u8; 32]); // vault
        data0.extend_from_slice(&[2u8; 32]); // owner
        data0.extend_from_slice(&100u64.to_le_bytes());
        data0.extend_from_slice(&200u64.to_le_bytes());
        data0.extend_from_slice(&20000u128.to_le_bytes());
        match VaultEvent::try_from_slice(&data0).unwrap() {
            VaultEvent::VaultCreated(e) => assert_eq!(e.reserve_a, 100),
            other => panic!("expected VaultCreated, got {other:?}"),
        }

        // SwapExactInExecuted (tag 1)
        let mut data1 = vec![1u8];
        data1.extend_from_slice(&[3u8; 32]); // vault
        data1.extend_from_slice(&50u64.to_le_bytes());
        data1.extend_from_slice(&45u64.to_le_bytes());
        data1.extend_from_slice(&150u64.to_le_bytes());
        data1.extend_from_slice(&155u64.to_le_bytes());
        match VaultEvent::try_from_slice(&data1).unwrap() {
            VaultEvent::SwapExactInExecuted(e) => {
                assert_eq!(e.amount_in, 50);
                assert_eq!(e.amount_out, 45);
            }
            other => panic!("expected SwapExactInExecuted, got {other:?}"),
        }
    }
}