usdc-plus-exchange 0.1.8

USDC <-> USDC+ exchange library for the Reflect protocol.
Documentation
use anchor_lang::prelude::*;
use crate::drift::components::*;

pub fn get_usdc_amount_drift_data(
    user_data: &[u8],
    spot_market_data: &[u8]
) -> Result<u64> {
    let user_positions: UserSpots = get_user_position_data(user_data)?;    
    
    // Skip the 8-byte discriminator
    let mut buf = &spot_market_data[8..];
    let spot_market = SpotMarket::deserialize(&mut buf)?;
    
    let usdc_amount: u128 = get_token_amount(
        user_positions.spot_positions[0].scaled_balance.cast()?,
        &spot_market,
        &SpotBalanceType::Deposit,
    )?;
    
    Ok(usdc_amount as u64)
}

#[inline(never)]
pub fn get_user_position_data(user_data: &[u8]) -> Result<UserSpots> {
    // Skip the 8-byte discriminator and extract spot positions
    let spot_positions_data = &user_data[ANCHOR_DISCRIMINATOR_SIZE + SPOT_POSITIONS_OFFSET..
                                          ANCHOR_DISCRIMINATOR_SIZE + PERP_POSITIONS_OFFSET];
    let spot_positions: [SpotPosition; SPOT_POSITION_COUNT] =
        AnchorDeserialize::deserialize(&mut &spot_positions_data[..])
            .map_err(|_| ProgramError::InvalidAccountData)?;

    Ok(UserSpots { spot_positions })
}


// cargo test --lib drift_comp::tests -- --nocapture

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use std::path::PathBuf;
    
    fn get_test_assets_dir() -> PathBuf {
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("test_assets");
        path
    }

    #[test]
    fn get_usdc_amount() {
        let assets_dir = get_test_assets_dir();
        let test_files = vec![
            ("spot_market_raw.bin", "user_account_raw.bin", "Local"),
            ("spot_market_usdc_mainnet.bin", "user_account_usdc_mainnet.bin", "Mainnet"),
        ];

        for (spot_filename, user_filename, label) in test_files {
            let market_path = assets_dir.join(spot_filename);
            let user_path = assets_dir.join(user_filename);
            
            if !market_path.exists() {
                println!("Skipping {} - file doesn't exist", spot_filename);
                continue;
            }
            
            if !user_path.exists() {
                println!("Skipping {} - file doesn't exist", user_filename);
                continue;
            }


            let spot_market = fs::read(&market_path).expect(&format!("Failed to read {}", spot_filename));
            let user_account = fs::read(&user_path).expect(&format!("Failed to read {}", user_filename));

            get_usdc_amount_drift_data(&user_account, &spot_market).map(|amount| {
                println!("{} USDC amount: {}", label, amount);
            }).unwrap_or_else(|err| {
                println!("Error getting USDC amount for {}: {:?}", label, err);
            });           
        }    
    } 

    #[test]
    fn test_spot_market_comprehensive_validation() {
        let assets_dir = get_test_assets_dir();
        let test_files = vec![
            ("spot_market_raw.bin", "Local"),
            ("spot_market_usdc_mainnet.bin", "Mainnet"),
        ];
        
        for (filename, label) in test_files {
            let market_path = assets_dir.join(filename);
            if !market_path.exists() {
                println!("Skipping {} - file doesn't exist", filename);
                continue;
            }
            
            let data = fs::read(&market_path).expect(&format!("Failed to read {}", filename));            
            let mut buf = &data[8..];
            let spot_market = SpotMarket::deserialize(&mut buf).expect(&format!("Failed to deserialize {}", filename));
            
            println!("\n=== {} SpotMarket Field Validation ({}) ===\n", label, filename);
            
            // Basic fields.
            println!("Basic fields:");
            println!("  Decimals: {} (expect 6 for USDC)", spot_market.decimals);
            println!("  Market index: {} (expect 0 for USDC)", spot_market.market_index);
            println!("  Orders enabled: {}", spot_market.orders_enabled);
            println!("  Status: {:?}", spot_market.status);
            
            // Key financial fields.
            println!("\nFinancial fields:");
            println!("  Deposit balance: {}", spot_market.deposit_balance);
            println!("  Borrow balance: {}", spot_market.borrow_balance);
            println!("  Cumulative deposit interest: {}", spot_market.cumulative_deposit_interest);
            println!("  Cumulative borrow interest: {}", spot_market.cumulative_borrow_interest);
            
            // Weights (should be non-zero for active market).
            println!("\nAsset weights:");
            println!("  Initial asset weight: {}", spot_market.initial_asset_weight);
            println!("  Maintenance asset weight: {}", spot_market.maintenance_asset_weight);
            println!("  Initial liability weight: {}", spot_market.initial_liability_weight);
            println!("  Maintenance liability weight: {}", spot_market.maintenance_liability_weight);
            
            // Order parameters.
            println!("\nOrder parameters:");
            println!("  Order step size: {}", spot_market.order_step_size);
            println!("  Min order size: {}", spot_market.min_order_size);
            println!("  Max position size: {}", spot_market.max_position_size);
            
            // Pubkeys - verify they're valid.
            println!("\nPubkeys:");
            println!("  Mint: {}", spot_market.mint);
            println!("  Oracle: {}", spot_market.oracle);
            println!("  Vault: {}", spot_market.vault);
            
            // Name field.
            let name_str = String::from_utf8_lossy(&spot_market.name)
                .trim_end_matches('\0')
                .to_string();
            
            // Validation checks.
            assert_eq!(spot_market.decimals, 6, "{}: USDC should have 6 decimals", label);
            assert_eq!(spot_market.market_index, 0, "{}: USDC should be market 0", label);
            
            // Weights should be non-zero.
            assert!(spot_market.initial_asset_weight > 0, "{}: Initial asset weight should be non-zero", label);
            assert!(spot_market.maintenance_asset_weight > 0, "{}: Maintenance asset weight should be non-zero", label);
            
            // Check mint is USDC (mainnet USDC mint).
            let usdc_mint_bytes = bs58::decode("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
                .into_vec()
                .unwrap();
            
            let usdc_mint = Pubkey::new_from_array(usdc_mint_bytes.try_into().unwrap());
            if spot_market.mint == usdc_mint {
                println!("\n{} Mint matches mainnet USDC", label);
            } else {
                println!("\n⚠️ {} Mint doesn't match mainnet USDC, might be devnet or different", label);
            }
            
            // Cumulative interest should be within 15% of expected values.
            let expected_deposit_interest = 11_722_589_531u128;
            let expected_borrow_interest = 13_485_551_200u128;
            
            assert!(
                spot_market.cumulative_deposit_interest > (expected_deposit_interest * 85 / 100) &&
                spot_market.cumulative_deposit_interest < (expected_deposit_interest * 115 / 100),
                "{}: Cumulative deposit interest {} outside 15% range of expected {}",
                label,
                spot_market.cumulative_deposit_interest,
                expected_deposit_interest
            );
            
            assert!(
                spot_market.cumulative_borrow_interest > (expected_borrow_interest * 85 / 100) &&
                spot_market.cumulative_borrow_interest < (expected_borrow_interest * 115 / 100),
                "{}: Cumulative borrow interest {} outside 15% range of expected {}",
                label,
                spot_market.cumulative_borrow_interest,
                expected_borrow_interest
            );
            
            println!("\n{} validations passed!", label);
        }
    }
}