- Total Prize Pool: $135,000 in USDC
- HM awards: up to $120,000 in USDC
- If no valid Highs or Mediums are found, the HM pool is $0
- QA awards: $5,000 in USDC
- Judge awards: $9,500 in USDC
- Scout awards: $500 USDC
- HM awards: up to $120,000 in USDC
- Read our guidelines for more details
- Starts April 17, 2026 20:00 UTC
- Ends May 27, 2026 20:00 UTC
- Since this audit includes live/deployed code, all submissions will be treated as sensitive:
- Wardens are encouraged to submit High-risk submissions affecting live code promptly, to ensure timely disclosure of such vulnerabilities to the sponsor and guarantee payout in the case where a sponsor patches a live critical during the audit.
- Submissions will be hidden from all wardens (SR and non-SR alike) by default, to ensure that no sensitive issues are erroneously shared.
- If the submissions include findings affecting live code, there will be no post-judging QA phase. This ensures that awards can be distributed in a timely fashion, without compromising the security of the project. (Senior members of C4 staff will review the judges’ decisions per usual.)
- By default, submissions will not be made public until the report is published.
- Exception: if the sponsor indicates that no submissions affect live code, then we’ll make submissions visible to all authenticated wardens, and open PJQA to SR wardens per the usual C4 process.
- The "live criticals" exception therefore applies.
- A coded, runnable PoC is required for all High/Medium submissions to this audit.
- This repo includes a basic template to run the test suite, located at
tests/c4/src/lib.rs. - PoCs must use the
test_submission_validitytest intests/c4as their starting point. Extend that function (or add sibling#[test]s in the same file) to demonstrate your finding. - Your submission will be marked as Insufficient if the PoC is not runnable and working with the provided test suite.
- Exception: PoC is optional (though recommended) for wardens with signal ≥ 0.4.
- This repo includes a basic template to run the test suite, located at
- Judging phase risk adjustments (upgrades/downgrades):
- High- or Medium-risk submissions downgraded by the judge to Low-risk (QA) will be ineligible for awards.
- Upgrading a Low-risk finding from a QA report to a Medium- or High-risk finding is not supported.
- As such, wardens are encouraged to select the appropriate risk level carefully during the submission phase.
V12 is Zellic's in-house AI auditing tool. It is the only autonomous auditor that reliably finds Highs and Criticals. All issues found by V12 will be judged as out of scope and ineligible for awards.
V12 findings can be viewed in the files below, or here:
Anything included in this section is considered a publicly known issue and is therefore ineligible for awards.
- Self-liquidation: Users can liquidate their own positions. This follows Aave V3 precedent and provides a mechanism for users to efficiently unwind positions.
- Flash liquidation memory budget: The two-step flash liquidation (
prepare_liquidation+execute_liquidation) with a swap handler exceeds Soroban's 42 MB memory limit at 2+ reserves. Regularliquidation_callworks up to ~6-7 reserves per user bitmap. This is a known Soroban VM constraint, not a protocol bug. - DEX integration: Depth and liquidity of DEX pools that may impact liquidation is a known issue and is considered risk-parameter related and out of scope.
K2 is a decentralized borrowing and lending protocol built on Stellar's Soroban smart-contract platform. It adapts the proven design patterns of Aave V3 to Stellar's unique constraints, and lets users:
- Supply assets to earn interest (receive interest-bearing aTokens)
- Borrow assets against collateral at variable rates
- Liquidate undercollateralized positions
- Flash loan assets for atomic operations
The protocol is organized around a router contract (kinetic-router) that serves as the single entry point for all user operations. Interest accrues per-reserve via scaled balances divided by monotonically-increasing liquidity and borrow indices, matching the Aave V3 accounting model.
The implementation addresses several Soroban-specific constraints and capabilities:
- Two-step flash liquidation: Soroban's 100M CPU instruction limit per transaction necessitates splitting liquidation into separate
prepare_liquidationandexecute_liquidationcalls when a swap handler is involved. - Bitmap user configuration: User reserve participation is tracked with a 128-bit bitmap (2 bits per reserve: collateral flag + borrowing flag), supporting up to 64 reserves per pool.
- Oracle cascade: Prices are resolved in priority order (Manual Override → Custom Oracle → Reflector → Fallback Oracle) with a 20% circuit breaker that freezes the cache on anomalous price changes.
- U256 intermediate math: Value calculations use U256 intermediates to prevent overflow in
balance × price × oracle_to_wad / decimalsexpressions across mixed-decimal assets. - Separated admin roles: The emergency admin can pause but cannot unpause, ensuring that a compromised emergency key cannot unilaterally resume protocol operations.
- Previous audits:
- Documentation:
docs/ - Website: https://www.k2lend.com
- X/Twitter: https://x.com/K2_Lend
Note: The nSLoC counts in the following table have been automatically generated and may differ depending on the definition of what a "significant" line of code represents. As such, they should be considered indicative rather than absolute representations of the lines involved in each contract.
For a machine-readable version, see scope.txt.
| File/Directory | File Count |
|---|---|
| contracts/kinetic-router/fuzz/* (fuzz harnesses and corpora) | 23 |
| tests/* (unit tests, integration tests, and PoC submission template) | 81 |
| external/* (mock Reflector / Soroswap WASMs used in tests) | 4 |
| Total | 108 |
For a machine-readable version, see out_of_scope.txt.
- Solvency accounting & interest indices: Correctness of scaled-balance math, monotonicity of
liquidity_indexandvariable_borrow_index, rounding direction, and the relationship betweenaToken_total_supply_scaled × liquidity_indexandunderlying_balance + total_debt_scaled × borrow_index. - Health factor & liquidation math: Post-liquidation health factor improvement enforcement, partial vs full liquidation, liquidation bonus calculation, and bad debt socialization via deficit tracking.
- Oracle safety: Price cache TTL, staleness rejection, circuit breaker behaviour on large price moves, and the fallback cascade ordering (Manual Override → Custom Oracle → Reflector → Fallback).
- Flash loan & flash liquidation atomicity: Premium collection (must always round up), repayment enforcement within the same transaction, and state consistency across the
prepare_liquidation/execute_liquidationsplit. - Bitmap bounds & reserve indexing: 64-reserve limit enforcement, reserve index collision after reserve drop and re-registration, and
UserConfigbitmap state transitions across supply / withdraw / borrow / repay. - DEX adapter integration: Slippage bounds (
min_swap_output_bps), swap-handler whitelist enforcement, and minimum output calculation on both the Aquarius and Soroswap adapters. - Admin role separation & upgrades: Two-step admin transfer, the asymmetry where emergency admin can pause but cannot unpause, per-contract upgrade authority, and the WASM upgrade flow.
- Reserve caps & debt ceilings: Supply cap, borrow cap, and minimum-remaining-debt enforcement under concurrent operations, including interactions with interest accrual rounding.
- Authorization boundaries:
require_auth()coverage at every state-changing entry point, repay-on-behalf authorization (repayer.require_auth()vsuser.require_auth()), and emergency admin scope limits. - Multi-asset U256 math: Overflow and precision in
balance × price × oracle_to_wad / decimalsacross mixed-decimal assets.
Solvency & Accounting
- Conservation of value: For every reserve,
aToken_total_supply_scaled * liquidity_indexmust not exceedunderlying_token_balance + total_debt_scaled * borrow_index(modulo accrued protocol fees) - No phantom value creation: aToken minting/burning must exactly correspond to underlying token deposits/withdrawals. Debt token minting/burning must exactly correspond to borrows/repayments.
- Supply-debt consistency:
sum(user_scaled_balances) == total_supply_scaledfor both aToken and debtToken contracts at all times
Health Factor & Collateralization
- Borrowing collateralization: After any borrow or withdraw, the user's health factor must be ≥ 1.0 WAD (1e18), or the transaction reverts with
HealthFactorTooLow(Error #8) - Liquidation improvement: After any liquidation, the borrower's health factor must improve (or remain within
LIQUIDATION_HF_TOLERANCE_BPS= 1 bp tolerance for rounding noise) - Liquidation eligibility: Only positions with health factor <
HEALTH_FACTOR_LIQUIDATION_THRESHOLD(1.0 WAD) can be liquidated
Index Monotonicity
- Liquidity index is non-decreasing:
liquidity_index(t+1) >= liquidity_index(t)- interest always accrues forward - Borrow index is non-decreasing:
variable_borrow_index(t+1) >= variable_borrow_index(t)- no interest regression - Indices never drop below RAY: Both indices start at
RAY(1e27) and can only increase
Authorization
- Privileged operations require authorization: All admin, configuration, and treasury operations require
require_auth()from the appropriate role - Emergency admin asymmetry: Emergency admin can pause but CANNOT unpause - only pool admin can unpause
- User operations require user auth: Supply, borrow, repay, withdraw all require
user.require_auth()(except repay-on-behalf, which requiresrepayer.require_auth())
Flash Loans
- Atomicity: Flash loaned assets must be repaid with premium within the same transaction, or the transaction reverts with
FlashLoanNotRepaid - Premium collection: Flash loan premium is always collected and rounds UP (
percent_mul_up) - the protocol never receives less than the expected fee
Protocol Safety
- Pause halts all state-changing operations: When paused, all user operations (supply, borrow, repay, withdraw, liquidation, flash loan, swap) revert with
AssetPaused. Read-only queries remain functional. - Reserve capacity limits: Supply and borrow caps are enforced - no reserve can exceed its configured cap
- Bitmap bounds: User configuration bitmap supports at most 64 reserves (2 bits per reserve).
reserve_index >= 64is rejected
| Role | Description |
|---|---|
| Pool Admin | - Initializes and configures reserves (via pool-configurator) - Updates risk parameters: LTV, liquidation threshold, liquidation bonus, reserve factor - Sets supply/borrow caps, min remaining debt, debt ceiling - Sets flash loan premium (max 100 bps) - Sets/changes treasury, incentives, DEX router/factory addresses - Sets swap handler whitelist, reserve whitelists/blacklists, liquidation whitelists/blacklists - Unpauses the protocol (emergency admin cannot) - Proposes new pool admin or emergency admin (two-step transfer) - Upgrades contract WASM - Flushes oracle config cache |
| Emergency Admin | - pause() - halts all user operations - Cannot unpause, configure reserves, or perform any other admin action - Rationale: separation ensures a compromised emergency key cannot unilaterally resume operations after an attack |
| Oracle Admin | - Adds/removes assets from oracle - Sets manual price override (with expiry timestamp) - Sets custom oracle, fallback oracle, batch oracle per asset - Sets price cache TTL - Configures/resets circuit breaker - Pauses/unpauses the oracle |
| Pool Configurator Admin | - Deploys and initializes new reserves (deploys aToken + debtToken contracts) - Configures collateral parameters - Updates interest rate strategy per reserve - Drops reserves |
| Emission Manager (Incentives) | - Configures per-asset reward emissions - Sets emission rate (tokens/second) - Sets distribution end timestamp - Funds reward pool with tokens - Pauses/unpauses incentives |
| Treasury Admin | - Withdraws accumulated protocol fees - Syncs token balances |
| Interest Rate Strategy Admin | - Sets base variable borrow rate - Sets variable rate slope 1 and slope 2 |
| Upgrade Admin (per contract) | - upgrade(new_wasm_hash) - replaces contract bytecode - Two-step transfer: propose_admin → accept_admin |
Current holders (all roles): The deployer key initially holds every role above. The migration target is a Volta 2-of-5 multisig for the pool admin, with a separate key for the emergency admin.
git clone https://github.com/code-423n4/2026-04-k2
cd 2026-04-k2Prerequisites:
- Rust (stable) with the
wasm32v1-nonetarget (automatically installed viarust-toolchain.tomlwhen using rustup) - Stellar CLI for building WASM contracts
Build all contracts to WASM (compile + optimize):
./build.shRun the PoC test suite (the starting point for warden submissions):
cargo test --package k2-c4Run the full unit test suite:
cargo test --package k2-unit-testsRun the integration test suite:
cargo test --package k2-integration-testsEmployees of K2 and employees' family members are ineligible to participate in this audit.
Code4rena's rules cannot be overridden by the contents of this README. In case of doubt, please check with C4 staff.