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
14 changes: 10 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl QuickLendXContract {
mod test {
use super::*;
use soroban_sdk::Env;
use soroban_sdk::testutils::Address as _; // Brings the mock address generator trait into scope
use soroban_sdk::testutils::Address as _;

#[test]
fn test_initialization() {
Expand All @@ -80,8 +80,7 @@ mod test {
// Initialize the contract cleanly
client.init(&admin, &fee, &min_holding);

// Directly query the contract state using storage lookups to satisfy
// test assertions and code coverage without causing an OS abort loop
// Fetch stored state
let stored_admin: Address = env.as_contract(&contract_id, || {
env.storage().instance().get(&DataKey::Admin).unwrap()
});
Expand All @@ -90,5 +89,12 @@ mod test {
env.storage().instance().get(&DataKey::Config).unwrap()
});

// Assertions using correct ProtocolConfig fields
assert_eq!(stored_admin, admin);
assert_eq!(stored_config.fee_percentage, fee);
assert_eq!(stored_config.min_holding_period, min_holding);
}
}

#[cfg(test)]
mod test_solvency_invariant;
mod test_solvency_invariant;
57 changes: 57 additions & 0 deletions src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub enum GuardError {
VerificationPending,
/// KYC application was rejected.
VerificationRejected,
/// KYC data has expired based on current ledger time.
KycExpired,
/// Bid / investment amount exceeds the investor's computed limit.
InvestmentLimitExceeded {
requested: u128,
Expand Down Expand Up @@ -471,6 +473,35 @@ pub fn compute_tier(total_invested: u128, successful_investments: u32) -> Invest
}
}

/// Guard: Checks whether a business actor's KYC is missing, expired, or current.
///
/// # Parameters
/// - `status` — The current validation status if a record exists (`Some`), or `None`.
/// - `expiration_timestamp` — The timestamp when the KYC coverage officially lapses.
/// - `current_timestamp` — The current ledger block time supplied by the caller.
pub fn verify_business_kyc(
status: Option<VerificationStatus>,
expiration_timestamp: u64,
current_timestamp: u64,
) -> Result<(), GuardError> {
// 1. Missing case
let current_status = status.ok_or(GuardError::NotSubmitted)?;

match current_status {
VerificationStatus::Pending => return Err(GuardError::VerificationPending),
VerificationStatus::Rejected => return Err(GuardError::VerificationRejected),
VerificationStatus::Verified => {}
}

// 2. Expired case (Boundary check)
if current_timestamp >= expiration_timestamp {
return Err(GuardError::KycExpired);
}

// 3. Current case (Happy path)
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -757,4 +788,30 @@ mod tests {
// Low invested but high count → stays at lower tier
assert_eq!(compute_tier(1, 1_000), InvestorTier::Basic);
}

// ── verify_business_kyc Boundary Tests ─────────────────────────────────

#[test]
fn returns_error_when_kyc_is_missing() {
let result = verify_business_kyc(None, 2000, 1000);
assert_eq!(result, Err(GuardError::NotSubmitted));
}

#[test]
fn returns_error_when_kyc_is_expired() {
// Boundary check: current time matches expiration time exactly
let exact_boundary = verify_business_kyc(Some(VerificationStatus::Verified), 2000, 2000);
assert_eq!(exact_boundary, Err(GuardError::KycExpired));

// Sad path: current time exceeds expiration time
let past_boundary = verify_business_kyc(Some(VerificationStatus::Verified), 2000, 2001);
assert_eq!(past_boundary, Err(GuardError::KycExpired));
}

#[test]
fn succeeds_when_kyc_is_current() {
// Happy path: current time is strictly before expiration time
let result = verify_business_kyc(Some(VerificationStatus::Verified), 2000, 1999);
assert_eq!(result, Ok(()));
}
}
Loading