Skip to content

Latest commit

 

History

History
374 lines (275 loc) · 11.1 KB

File metadata and controls

374 lines (275 loc) · 11.1 KB

Borrow Function Test Suite Documentation

Overview

This document describes the comprehensive test suite for the borrow_asset function in the StellarLend protocol. The test suite ensures the borrow functionality is secure, well-tested, and documented, meeting the requirement for 95%+ test coverage.

Location

  • Test File: stellar-lend/contracts/hello-world/src/tests/borrow_test.rs
  • Test Module: tests::borrow_test

Test Statistics

  • Total Tests: 40+
  • Test Categories: 9
  • Coverage Target: 95%+

Test Organization

The test suite is organized into logical sections:

1. Test Setup & Helpers

Reusable utility functions for test setup and data retrieval.

2. Successful Borrow Tests

Happy path scenarios testing normal borrow operations.

3. Validation Error Tests

All error conditions and edge cases for input validation.

4. Interest Accrual Tests

Time-based interest calculation scenarios.

5. Pause Functionality Tests

Pause/unpause mechanism validation.

6. Event Emission Tests

Verification of all emitted events.

7. Edge Cases & Boundary Tests

Boundary conditions and limit testing.

8. Security Tests

Security-focused scenarios and vulnerability checks.

9. Multi-Asset Tests

Native XLM vs token asset scenarios.

10. Analytics & State Tests

State management and analytics verification.

Running Tests

Run All Borrow Tests

cd stellar-lend/contracts/hello-world
cargo test borrow_test

Run Specific Test

cargo test test_borrow_asset_success_basic

Run with Output

cargo test borrow_test -- --nocapture

Run Single Test with Output

cargo test test_borrow_asset_success_basic -- --nocapture --exact

Test Categories Details

Successful Borrow Scenarios

Tests verify that borrows succeed under normal conditions:

  • Basic Borrow: User deposits collateral and borrows successfully
  • Maximum Limit: Borrow exactly at the maximum allowed amount
  • Sequential Borrows: Multiple borrows within limits
  • Existing Debt: Borrow with existing debt (interest accrual)
  • After Repayment: Borrow after partial repayment
  • Different Factors: Borrow with various collateral factors

Key Assertions:

  • Position debt is updated correctly
  • Total debt includes principal and interest
  • Analytics are updated
  • Events are emitted

Validation Error Tests

Tests verify all error conditions:

Error Code Error Name Test Scenarios
1 InvalidAmount Zero amount, negative amount
2 InvalidAsset Contract address as asset
3 InsufficientCollateral No collateral, zero balance
4 BorrowPaused Borrow paused via pause switch
5 InsufficientCollateralRatio Violates 150% minimum ratio
6 Overflow Calculation overflow scenarios
8 MaxBorrowExceeded Exceeds maximum borrowable
9 AssetNotEnabled Asset not enabled for borrowing

Test Pattern:

#[test]
#[should_panic(expected = "ErrorName")]
fn test_borrow_asset_error_scenario() {
    // Setup
    // Attempt operation that should fail
    // Verify panic with expected error
}

Interest Accrual Tests

Tests verify interest calculation and accrual:

  • Accrual on Existing Debt: Interest accrues before new borrow
  • Time-Based Calculation: Interest increases with time
  • Interest Reset: Interest resets when debt becomes zero

Important Note: These tests use manual timestamp manipulation to avoid overflow:

env.as_contract(&contract_id, || {
    let position_key = DepositDataKey::Position(user.clone());
    let mut position = env.storage().persistent()
        .get::<DepositDataKey, Position>(&position_key).unwrap();
    position.last_accrual_time = env.ledger().timestamp().saturating_sub(86400);
    env.storage().persistent().set(&position_key, &position);
});

Pause Functionality Tests

Tests verify the pause mechanism:

  • Paused: Borrow fails when pause_borrow is true
  • Not Paused: Borrow succeeds when pause_borrow is false
  • No Pause Map: Borrow succeeds when pause map doesn't exist
  • Pause Removed: Borrow succeeds after pause is removed

Event Emission Tests

Tests verify all events are emitted:

  • BorrowEvent: Contains user, asset, amount, timestamp
  • PositionUpdatedEvent: Position changes are tracked
  • AnalyticsUpdatedEvent: Analytics changes are tracked

Note: Event verification is implicit through successful execution, as Soroban test environment doesn't provide direct event log access in unit tests.

Edge Cases & Boundary Tests

Tests verify boundary conditions:

  • Exact Maximum: Borrow exactly at max borrowable amount
  • One Below Max: Borrow 1 unit below maximum (should succeed)
  • One Above Max: Borrow 1 unit above maximum (should fail)
  • Very Small Amount: Borrow minimum amount (1 unit)
  • Multiple Users: Multiple users borrowing simultaneously

Security Tests

Tests verify security assumptions:

  • Zero Collateral Factor: Max borrow should be zero
  • High Collateral Factor: Max borrow increases proportionally
  • State Consistency: Position state is consistent after operations

Multi-Asset Tests

Tests verify multi-asset support:

  • Native XLM: Borrow native XLM (None asset)
  • Token Asset: Borrow token asset (Address)
  • Default Factor: Default collateral factor (10000) when asset params not found

Analytics & State Tests

Tests verify state management:

  • User Analytics: total_borrows, debt_value, collateralization_ratio updated
  • Protocol Analytics: total_borrows incremented
  • Position State: debt, last_accrual_time updated
  • Activity Log: Activity entries added
  • Transaction Count: Count incremented
  • Last Activity: Timestamp updated

Key Formulas

Maximum Borrowable Amount

max_borrow = (collateral * collateral_factor * 10000) / MIN_COLLATERAL_RATIO_BPS

Where:

  • collateral: User's collateral balance
  • collateral_factor: Asset's collateral factor (in basis points, e.g., 10000 = 100%)
  • MIN_COLLATERAL_RATIO_BPS: Minimum collateral ratio (15000 = 150%)

Example:

  • Collateral: 2000
  • Collateral Factor: 10000 (100%)
  • Min Ratio: 15000 (150%)
  • Max Borrow: (2000 * 10000 * 10000) / 15000 = 1333

Collateral Ratio

collateral_value = (collateral * collateral_factor) / 10000
ratio = (collateral_value * 10000) / total_debt

Where:

  • total_debt = debt + borrow_interest

Example:

  • Collateral: 3000
  • Collateral Factor: 10000 (100%)
  • Debt: 1500
  • Interest: 0
  • Collateral Value: (3000 * 10000) / 10000 = 3000
  • Ratio: (3000 * 10000) / 1500 = 20000 (200%)

Interest Accrual

Interest is calculated using dynamic rates based on protocol utilization:

rate = calculate_borrow_rate(env)  // Dynamic rate based on utilization
interest = principal * rate_bps * time_elapsed / (10000 * seconds_per_year)

The rate comes from interest_rate::calculate_borrow_rate() which uses a kink model:

  • Below kink: Linear rate increase
  • Above kink: Steeper rate increase

Test Helpers

Environment Setup

fn create_test_env() -> Env

Creates a test environment with mocked authentications.

Data Retrieval

fn get_user_position(env: &Env, contract_id: &Address, user: &Address) -> Option<Position>
fn get_user_analytics(env: &Env, contract_id: &Address, user: &Address) -> Option<UserAnalytics>
fn get_protocol_analytics(env: &Env, contract_id: &Address) -> Option<ProtocolAnalytics>

Retrieve user position, user analytics, and protocol analytics from storage.

Configuration

fn set_asset_params(env: &Env, contract_id: &Address, asset: &Address, 
                   deposit_enabled: bool, collateral_factor: i128, max_deposit: i128)
fn set_pause_borrow(env: &Env, contract_id: &Address, paused: bool)

Configure asset parameters and pause switches.

Utilities

fn advance_ledger_time(env: &Env, seconds: u64)
fn calculate_expected_max_borrow(collateral: i128, collateral_factor: i128) -> i128

Advance ledger timestamp and calculate expected maximum borrowable amount.

Security Considerations

The test suite validates:

  1. Input Validation

    • Amount must be > 0
    • Asset address must be valid
    • Asset must not be the contract itself
  2. Collateral Requirements

    • User must have collateral
    • Collateral ratio must be >= 150%
    • Maximum borrow limits enforced
  3. Pause Mechanism

    • Borrow can be paused by admin
    • Pause state is checked before operations
  4. Overflow Protection

    • All calculations use checked arithmetic
    • Overflow errors are properly handled
  5. State Consistency

    • Position state is consistent
    • Analytics match actual state
    • Events match operations

Test Patterns

Success Test Pattern

#[test]
fn test_borrow_asset_success() {
    // 1. Setup environment and contract
    let env = create_test_env();
    let contract_id = env.register(HelloContract, ());
    let client = HelloContractClient::new(&env, &contract_id);
    
    // 2. Setup user and collateral
    let user = Address::generate(&env);
    client.deposit_collateral(&user, &None, &collateral_amount);
    
    // 3. Perform borrow
    let borrow_amount = 1000;
    let total_debt = client.borrow_asset(&user, &None, &borrow_amount);
    
    // 4. Verify results
    let position = get_user_position(&env, &contract_id, &user).unwrap();
    assert_eq!(position.debt, borrow_amount);
    assert!(total_debt >= borrow_amount);
}

Error Test Pattern

#[test]
#[should_panic(expected = "ErrorName")]
fn test_borrow_asset_error() {
    // 1. Setup
    let env = create_test_env();
    let contract_id = env.register(HelloContract, ());
    let client = HelloContractClient::new(&env, &contract_id);
    
    // 2. Setup conditions that will cause error
    // ...
    
    // 3. Attempt operation (should panic)
    client.borrow_asset(&user, &None, &invalid_amount);
}

Notes

  1. Native vs Token Assets: Most tests use native XLM (None asset) for simplicity. Token asset tests require proper token contract setup.

  2. Time Manipulation: Interest accrual tests use manual timestamp manipulation to avoid overflow issues with large time advances.

  3. Event Verification: Event verification is implicit through successful execution, as direct event log access isn't available in unit tests.

  4. Isolation: All tests are isolated and can run independently.

  5. Coverage: The test suite aims for 95%+ coverage of the borrow_asset function.

Maintenance

When adding new tests:

  1. Follow existing test patterns
  2. Add appropriate documentation comments
  3. Organize tests into appropriate sections
  4. Ensure tests are isolated and independent
  5. Update this documentation if adding new test categories

Related Documentation