Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions quicklendx-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ impl QuickLendXContract {
init::ProtocolInitializer::is_initialized(&env)
}

/// Get the protocol/contract version
///
/// Returns the version written during initialization, or the current
/// PROTOCOL_VERSION constant if the contract has not been initialized yet.
///
/// # Returns
/// * `u32` - The protocol version number
///
/// # Version Format
/// Version is a simple integer increment (e.g., 1, 2, 3...)
/// Major versions indicate breaking changes that require migration.
pub fn get_version(env: Env) -> u32 {
init::ProtocolInitializer::get_protocol_version(&env)
}

/// Initialize the admin address (deprecated: use initialize)
pub fn initialize_admin(env: Env, admin: Address) -> Result<(), QuickLendXError> {
admin.require_auth();
Expand Down
2 changes: 2 additions & 0 deletions quicklendx-contracts/src/settlement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::invoice::{
Invoice, InvoiceStatus, InvoiceStorage, PaymentRecord as InvoicePaymentRecord,
};
use crate::notifications::NotificationSystem;
use crate::defaults::DEFAULT_GRACE_PERIOD;
use crate::events::TOPIC_INVOICE_SETTLED_FINAL;
use crate::payments::transfer_funds;
use soroban_sdk::{contracttype, symbol_short, Address, BytesN, Env, String, Vec};

Expand Down
24 changes: 24 additions & 0 deletions quicklendx-contracts/src/test_audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,30 @@ fn test_audit_invoice_cancelled_produces_entry() {
&1000i128,
&currency,
&due_date,
&String::from_str(&env, "Test invoice"),
&invoice::InvoiceCategory::Services,
&Vec::from_array(&env, [String::from_str(&env, "test")]),
).unwrap();

client.cancel_invoice(&invoice_id);

let logs = client.query_audit_logs(
&None,
&None,
&None,
&None,
&None,
&10
);

assert!(logs.len() >= 1);

// Find the cancellation entry
let cancel_entry = logs.iter().find(|log| {
log.operation == audit::AuditOperation::InvoiceCancelled
}).unwrap();

assert_eq!(cancel_entry.actor, business);
&String::from_str(&env, "Cancel Test"),
&InvoiceCategory::Services,
&Vec::new(&env),
Expand Down
188 changes: 188 additions & 0 deletions quicklendx-contracts/src/test_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,191 @@ fn test_validation_invalid_grace_period() {
let result = client.try_initialize(&params);
assert_eq!(result, Err(Ok(QuickLendXError::InvalidTimestamp)));
}

#[test]
fn test_get_version_before_initialization() {
let (env, client) = setup();

// Before initialization, should return the current PROTOCOL_VERSION constant
let version = client.get_version();
assert_eq!(version, 1);

// Contract should not be initialized yet
assert!(!client.is_initialized());
}

#[test]
fn test_get_version_after_initialization() {
let (env, client) = setup();

let admin = Address::generate(&env);
let treasury = Address::generate(&env);
let initial_currencies = Vec::from_array(&env, [Address::generate(&env)]);

let params = InitializationParams {
admin: admin.clone(),
treasury: treasury.clone(),
fee_bps: 200,
min_invoice_amount: 1_000_000,
max_due_date_days: 365,
grace_period_seconds: 604800,
initial_currencies: initial_currencies.clone(),
};

// Before initialization
let version_before = client.get_version();
assert_eq!(version_before, 1);

// Initialize the contract
client.initialize(&params);

// After initialization, version should still be the same
let version_after = client.get_version();
assert_eq!(version_after, 1);

// Contract should now be initialized
assert!(client.is_initialized());

// Version should be consistent before and after initialization
assert_eq!(version_before, version_after);
}

#[test]
fn test_version_immutability() {
let (env, client) = setup();

let admin = Address::generate(&env);
let treasury = Address::generate(&env);
let initial_currencies = Vec::from_array(&env, [Address::generate(&env)]);

let params = InitializationParams {
admin: admin.clone(),
treasury: treasury.clone(),
fee_bps: 200,
min_invoice_amount: 1_000_000,
max_due_date_days: 365,
grace_period_seconds: 604800,
initial_currencies: initial_currencies.clone(),
};

// Initialize the contract
client.initialize(&params);

// Get version multiple times - should always return the same value
let version1 = client.get_version();
let version2 = client.get_version();
let version3 = client.get_version();

assert_eq!(version1, 1);
assert_eq!(version2, 1);
assert_eq!(version3, 1);

// Version should remain constant across multiple calls
assert_eq!(version1, version2);
assert_eq!(version2, version3);
}

#[test]
fn test_version_format_documentation() {
let (env, client) = setup();

// Test that version follows the documented format (simple integer)
let version = client.get_version();

// Version should be a positive integer
assert!(version > 0);
assert!(version <= u32::MAX);

// Current version should be 1 based on PROTOCOL_VERSION constant
assert_eq!(version, 1);

// Verify it's a simple integer format (not semver or complex format)
let version_str = version.to_string();
assert!(version_str.parse::<u32>().is_ok());
}

#[test]
fn test_version_consistency_across_operations() {
let (env, client) = setup();

let admin = Address::generate(&env);
let treasury = Address::generate(&env);
let initial_currencies = Vec::from_array(&env, [Address::generate(&env)]);

let params = InitializationParams {
admin: admin.clone(),
treasury: treasury.clone(),
fee_bps: 200,
min_invoice_amount: 1_000_000,
max_due_date_days: 365,
grace_period_seconds: 604800,
initial_currencies: initial_currencies.clone(),
};

// Get initial version
let initial_version = client.get_version();

// Initialize
client.initialize(&params);

// Perform various operations
let current_admin = client.get_current_admin().unwrap();
client.transfer_admin(&current_admin, &Address::generate(&env));

// Add currency
let new_currency = Address::generate(&env);
client.add_currency(&current_admin, &new_currency);

// Version should remain unchanged throughout all operations
let final_version = client.get_version();
assert_eq!(initial_version, final_version);
assert_eq!(final_version, 1);
}

#[test]
fn test_version_edge_cases() {
let (env, client) = setup();

// Test version behavior in edge cases

// 1. Fresh contract instance
let version1 = client.get_version();
assert_eq!(version1, 1);

// 2. After failed initialization attempt
let admin = Address::generate(&env);
let invalid_params = InitializationParams {
admin: admin.clone(),
treasury: Address::generate(&env),
fee_bps: 1001, // Invalid - should fail
min_invoice_amount: 1_000_000,
max_due_date_days: 365,
grace_period_seconds: 604800,
initial_currencies: Vec::new(&env),
};

// This should fail
let result = client.try_initialize(&invalid_params);
assert!(result.is_err());

// Version should still be accessible and unchanged
let version2 = client.get_version();
assert_eq!(version2, 1);
assert_eq!(version1, version2);

// 3. After successful initialization
let valid_params = InitializationParams {
admin: admin.clone(),
treasury: Address::generate(&env),
fee_bps: 200,
min_invoice_amount: 1_000_000,
max_due_date_days: 365,
grace_period_seconds: 604800,
initial_currencies: Vec::new(&env),
};

client.initialize(&valid_params);
let version3 = client.get_version();
assert_eq!(version3, 1);
assert_eq!(version1, version3);
}
Loading