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.
- Test File:
stellar-lend/contracts/hello-world/src/tests/borrow_test.rs - Test Module:
tests::borrow_test
- Total Tests: 40+
- Test Categories: 9
- Coverage Target: 95%+
The test suite is organized into logical sections:
Reusable utility functions for test setup and data retrieval.
Happy path scenarios testing normal borrow operations.
All error conditions and edge cases for input validation.
Time-based interest calculation scenarios.
Pause/unpause mechanism validation.
Verification of all emitted events.
Boundary conditions and limit testing.
Security-focused scenarios and vulnerability checks.
Native XLM vs token asset scenarios.
State management and analytics verification.
cd stellar-lend/contracts/hello-world
cargo test borrow_testcargo test test_borrow_asset_success_basiccargo test borrow_test -- --nocapturecargo test test_borrow_asset_success_basic -- --nocapture --exactTests 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
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
}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);
});Tests verify the pause mechanism:
- Paused: Borrow fails when
pause_borrowis true - Not Paused: Borrow succeeds when
pause_borrowis false - No Pause Map: Borrow succeeds when pause map doesn't exist
- Pause Removed: Borrow succeeds after pause is removed
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.
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
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
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
Tests verify state management:
- User Analytics:
total_borrows,debt_value,collateralization_ratioupdated - Protocol Analytics:
total_borrowsincremented - Position State:
debt,last_accrual_timeupdated - Activity Log: Activity entries added
- Transaction Count: Count incremented
- Last Activity: Timestamp updated
max_borrow = (collateral * collateral_factor * 10000) / MIN_COLLATERAL_RATIO_BPS
Where:
collateral: User's collateral balancecollateral_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_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 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
fn create_test_env() -> EnvCreates a test environment with mocked authentications.
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.
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.
fn advance_ledger_time(env: &Env, seconds: u64)
fn calculate_expected_max_borrow(collateral: i128, collateral_factor: i128) -> i128Advance ledger timestamp and calculate expected maximum borrowable amount.
The test suite validates:
-
Input Validation
- Amount must be > 0
- Asset address must be valid
- Asset must not be the contract itself
-
Collateral Requirements
- User must have collateral
- Collateral ratio must be >= 150%
- Maximum borrow limits enforced
-
Pause Mechanism
- Borrow can be paused by admin
- Pause state is checked before operations
-
Overflow Protection
- All calculations use checked arithmetic
- Overflow errors are properly handled
-
State Consistency
- Position state is consistent
- Analytics match actual state
- Events match operations
#[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);
}#[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);
}-
Native vs Token Assets: Most tests use native XLM (None asset) for simplicity. Token asset tests require proper token contract setup.
-
Time Manipulation: Interest accrual tests use manual timestamp manipulation to avoid overflow issues with large time advances.
-
Event Verification: Event verification is implicit through successful execution, as direct event log access isn't available in unit tests.
-
Isolation: All tests are isolated and can run independently.
-
Coverage: The test suite aims for 95%+ coverage of the
borrow_assetfunction.
When adding new tests:
- Follow existing test patterns
- Add appropriate documentation comments
- Organize tests into appropriate sections
- Ensure tests are isolated and independent
- Update this documentation if adding new test categories