shank-parse 0.1.1

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::events::*;
    use super::vault::instructions::*;
    use super::vault::ID;
    use shank_parse::__private::{base64_encode, 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);

        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);

        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);

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

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

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

        let mut event_data = vec![0u8]; // VaultCreated discriminant
        event_data.extend_from_slice(&vault_addr);
        event_data.extend_from_slice(&owner_addr);
        event_data.extend_from_slice(&1000u64.to_le_bytes());
        event_data.extend_from_slice(&2000u64.to_le_bytes());
        event_data.extend_from_slice(&2_000_000u128.to_le_bytes());
        let log = format!("Program data: {}", base64_encode(&event_data));

        let events = VaultCreated::from_logs(&[log.as_str()]);
        assert_eq!(events.len(), 1);
        assert_eq!(events[0].vault, vault_addr);
        assert_eq!(events[0].owner, owner_addr);
        assert_eq!(events[0].reserve_a, 1000);
        assert_eq!(events[0].k, 2_000_000);
    }

    #[test]
    fn test_vault_event_enum_from_logs() {
        // VaultCreated (disc 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());
        let log0 = format!("Program data: {}", base64_encode(&data0));

        // SwapExactInExecuted (disc 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());
        let log1 = format!("Program data: {}", base64_encode(&data1));

        let events = VaultEvent::from_logs(&[log0.as_str(), log1.as_str()]);
        assert_eq!(events.len(), 2);

        match &events[0] {
            VaultEvent::VaultCreated(e) => assert_eq!(e.reserve_a, 100),
            _ => panic!("expected VaultCreated"),
        }
        match &events[1] {
            VaultEvent::SwapExactInExecuted(e) => {
                assert_eq!(e.amount_in, 50);
                assert_eq!(e.amount_out, 45);
            }
            _ => panic!("expected SwapExactInExecuted"),
        }
    }
}