#![cfg(feature = "ctv")]
use blvm_node::payment::covenant::CovenantEngine;
use blvm_node::payment::processor::PaymentError;
use blvm_node::payment::vault::{VaultConfig, VaultEngine, VaultLifecycle, VaultState};
use blvm_protocol::payment::PaymentOutput;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
fn create_test_vault_engine() -> VaultEngine {
let covenant_engine = Arc::new(CovenantEngine::new());
VaultEngine::new(covenant_engine)
}
fn create_test_withdrawal_script() -> Vec<u8> {
vec![0x76, 0xa9, 0x14, 0x00, 0x87] }
#[test]
fn test_create_vault() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_1";
let deposit_amount = 100000; let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig::default();
let result = engine.create_vault(vault_id, deposit_amount, withdrawal_script.clone(), config);
assert!(result.is_ok(), "Vault creation should succeed");
let vault_state = result.unwrap();
assert_eq!(vault_state.vault_id, vault_id);
assert_eq!(vault_state.deposit_amount, deposit_amount);
assert_eq!(vault_state.state, VaultLifecycle::Deposited);
assert!(vault_state.deposit_covenant.template_hash.len() == 32);
assert!(vault_state.unvault_covenant.is_none());
assert!(vault_state.withdrawal_covenant.is_none());
}
#[test]
fn test_create_vault_custom_config() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_2";
let deposit_amount = 50000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
withdrawal_delay_blocks: 288, recovery_script: Some(vec![0x51, 0x87]), max_withdrawal: Some(100000),
require_unvault: false,
};
let result = engine.create_vault(vault_id, deposit_amount, withdrawal_script, config.clone());
assert!(result.is_ok());
let vault_state = result.unwrap();
assert_eq!(vault_state.config.withdrawal_delay_blocks, 288);
assert!(vault_state.config.recovery_script.is_some());
assert_eq!(vault_state.config.max_withdrawal, Some(100000));
}
#[test]
fn test_unvault() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_3";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
require_unvault: true,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config)
.unwrap();
let unvault_script = vec![0x52, 0x87]; let result = engine.unvault(&vault_state, unvault_script.clone());
assert!(result.is_ok(), "Unvaulting should succeed");
let new_state = result.unwrap();
assert_eq!(new_state.state, VaultLifecycle::Unvaulting);
assert!(new_state.unvault_covenant.is_some());
}
#[test]
fn test_unvault_fails_when_not_required() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_4";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
require_unvault: false,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config)
.unwrap();
let unvault_script = vec![0x52, 0x87];
let result = engine.unvault(&vault_state, unvault_script);
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
PaymentError::ProcessingError(_)
));
}
#[test]
fn test_check_withdrawal_eligibility() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_5";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
withdrawal_delay_blocks: 144, require_unvault: true,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config)
.unwrap();
let unvault_block_height = 1000;
let unvault_tx_hash = [0x01; 32];
let new_state = engine
.mark_unvaulted(&vault_state, unvault_tx_hash, unvault_block_height)
.unwrap();
let current_height = 1100; assert!(!VaultEngine::check_withdrawal_eligibility(
&new_state,
current_height
));
let current_height = 1144; assert!(VaultEngine::check_withdrawal_eligibility(
&new_state,
current_height
));
let current_height = 2000;
assert!(VaultEngine::check_withdrawal_eligibility(
&new_state,
current_height
));
}
#[test]
fn test_withdraw() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_6";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
withdrawal_delay_blocks: 144,
require_unvault: true,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config.clone())
.unwrap();
let unvault_script = vec![0x52, 0x87];
let unvaulted_state = engine.unvault(&vault_state, unvault_script).unwrap();
let unvault_tx_hash = [0x02; 32];
let unvault_block_height = 1000;
let mut unvaulted_state = engine
.mark_unvaulted(&unvaulted_state, unvault_tx_hash, unvault_block_height)
.unwrap();
let current_block_height = unvault_block_height + config.withdrawal_delay_blocks as u64 + 1;
let final_withdrawal_script = vec![0x53, 0x87]; let result = engine.withdraw(
&unvaulted_state,
final_withdrawal_script,
current_block_height,
);
assert!(result.is_ok(), "Withdrawal should succeed after delay");
let withdrawn_state = result.unwrap();
assert_eq!(withdrawn_state.state, VaultLifecycle::Withdrawing);
assert!(withdrawn_state.withdrawal_covenant.is_some());
}
#[test]
fn test_withdraw_fails_before_delay() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_7";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
withdrawal_delay_blocks: 144,
require_unvault: true,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config.clone())
.unwrap();
let unvault_script = vec![0x52, 0x87];
let unvaulted_state = engine.unvault(&vault_state, unvault_script).unwrap();
let unvault_tx_hash = [0x02; 32];
let unvault_block_height = 1000;
let unvaulted_state = engine
.mark_unvaulted(&unvaulted_state, unvault_tx_hash, unvault_block_height)
.unwrap();
let current_block_height = unvault_block_height + 100; let final_withdrawal_script = vec![0x53, 0x87];
let result = engine.withdraw(
&unvaulted_state,
final_withdrawal_script,
current_block_height,
);
assert!(result.is_err(), "Withdrawal should fail before delay");
}
#[test]
fn test_recover() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_8";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
recovery_script: Some(vec![0x51, 0x87]), ..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config)
.unwrap();
let recovery_script = vec![0x54, 0x87]; let result = engine.recover(&vault_state, recovery_script);
assert!(result.is_ok(), "Recovery should succeed");
let recovered_state = result.unwrap();
assert_eq!(recovered_state.state, VaultLifecycle::Recovered);
}
#[test]
fn test_recover_fails_when_not_configured() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_9";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
recovery_script: None,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config)
.unwrap();
let recovery_script = vec![0x54, 0x87];
let result = engine.recover(&vault_state, recovery_script);
assert!(result.is_err(), "Recovery should fail when not configured");
}
#[test]
fn test_mark_unvaulted() {
let engine = create_test_vault_engine();
let vault_id = "test_vault_10";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig {
withdrawal_delay_blocks: 144,
require_unvault: true,
..Default::default()
};
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config.clone())
.unwrap();
let unvault_script = vec![0x52, 0x87];
let unvaulted_state = engine.unvault(&vault_state, unvault_script).unwrap();
let unvault_tx_hash = [0x03; 32];
let unvault_block_height = 5000;
let result = engine.mark_unvaulted(&unvaulted_state, unvault_tx_hash, unvault_block_height);
assert!(result.is_ok());
let new_state = result.unwrap();
assert_eq!(new_state.unvault_tx_hash, Some(unvault_tx_hash));
assert_eq!(
new_state.withdrawal_available_at,
Some(unvault_block_height + config.withdrawal_delay_blocks as u64)
);
assert!(matches!(
new_state.state,
VaultLifecycle::Unvaulted {
unvaulted_at_block: 5000
}
));
}
#[cfg(feature = "ctv")]
#[test]
fn test_vault_state_storage() {
use blvm_node::storage::Storage;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let storage_path = temp_dir.path();
let storage = Storage::new(storage_path).expect("Failed to create storage");
let storage_arc = Arc::new(storage);
let covenant_engine = Arc::new(CovenantEngine::new());
let engine = VaultEngine::with_storage(covenant_engine, storage_arc);
let vault_id = "test_vault_storage";
let deposit_amount = 100000;
let withdrawal_script = create_test_withdrawal_script();
let config = VaultConfig::default();
let vault_state = engine
.create_vault(vault_id, deposit_amount, withdrawal_script, config)
.unwrap();
let retrieved = engine.get_vault(vault_id).unwrap();
assert!(retrieved.is_some());
let retrieved_state = retrieved.unwrap();
assert_eq!(retrieved_state.vault_id, vault_state.vault_id);
assert_eq!(retrieved_state.deposit_amount, vault_state.deposit_amount);
}