Zest Protocol borrow-helper-v2-1-7

Lending Helper / Router Contract โ€” Independent Security Audit

Contract
SP2VCQJGH7PHP2DJK7Z0V48AGBHQAW3R3ZW1QF4N.borrow-helper-v2-1-7
Source
On-chain (Hiro Explorer)
Clarity Version
Pre-Clarity 4 (โˆ’0.5 penalty noted; however no as-contract usage)
Lines of Code
~370
Audit Date
February 24, 2026
Auditor
cocoa007
Confidence
๐ŸŸก MEDIUM โ€” thin helper; security depends on unaudited pool-borrow-v2-4
Priority Score
2.5 / 3.0 (Financial=3, Deploy=3, Complexity=2, Exposure=3, Novelty=1)

Findings Summary

0Critical
0High
1Medium
2Low
3Info

Overview

borrow-helper-v2-1-7 is a thin helper/router contract for the Zest Protocol lending platform on Stacks mainnet. It wraps the core pool-borrow-v2-4 contract, adding:

The contract holds no state and no funds. All token transfers and access control are delegated to downstream contracts. This is an inherently low-risk architecture โ€” the helper itself cannot lose funds. The primary risk surface is incorrect event data misleading off-chain systems.

Architecture

FunctionDelegates ToExtra Logic
supplypool-borrow-v2-4.supplyClaims incentive rewards first
supply-allsupply (ร—10 assets)Batch convenience wrapper
borrowpool-borrow-v2-4.borrowPyth feed update
repaypool-borrow-v2-4.repayEvent logging
withdrawpool-borrow-v2-4.withdrawPyth feed + incentive claim
claim-rewardsincentives.claim-rewardsRewards contract validation
liquidation-callpool-borrow-v2-4.liquidation-callPyth feed update
set-e-modepool-borrow-v2-4.set-e-modePyth feed update
set-user-use-reserve-as-collateralpool-borrow-v2-4.set-user-use-reserve-as-collateralPyth feed update

Positive Observations

Findings

M-01: Incorrect Reserve State in Liquidation Event

MEDIUM
Location: liquidation-call โ€” print statement
Description: The collateral-reserve-state field in the liquidation event log queries debt-asset-principal instead of collateral-asset-principal. This emits the debt reserve state twice and never emits the actual collateral reserve state.
;; BUG: uses debt-asset-principal instead of collateral-asset-principal
collateral-reserve-state: (try! (contract-call? .pool-0-reserve-v2-0
  get-reserve-state debt-asset-principal)),
;;                        ^^^^^^^^^^^^^^^^^^^
;; Should be: collateral-asset-principal
Impact: Off-chain indexers, liquidation bots, and dashboards consuming this event will receive incorrect collateral reserve data. Liquidation bots that rely on this event to calculate collateral values or liquidation profitability could make incorrect decisions โ€” either missing profitable liquidations or attempting unprofitable ones. In a fast-moving market, stale/wrong reserve data could delay liquidations and increase protocol bad debt.
Recommendation: Deploy a new helper version with the corrected field:
collateral-reserve-state: (try! (contract-call? .pool-0-reserve-v2-0
  get-reserve-state collateral-asset-principal)),

L-01: Owner Parameter Not Validated Against tx-sender

LOW
Location: supply, borrow, withdraw, claim-rewards, set-user-use-reserve-as-collateral, set-e-mode
Description: Multiple functions accept an owner (or who/user) parameter without verifying it equals tx-sender. The contract checks (is-eq tx-sender contract-caller) but not (is-eq tx-sender owner). Whether this is exploitable depends entirely on the downstream pool-borrow-v2-4 access control.
Impact: If pool-borrow-v2-4 does not independently verify ownership:
Recommendation: Either add (asserts! (is-eq tx-sender owner) ERR_UNAUTHORIZED) to functions where owner must equal caller, or document explicitly that pool-borrow-v2-4 enforces this. The "supply on behalf of" pattern should use a separate function.

L-02: No Transaction Deadline on Price-Sensitive Operations

LOW
Location: borrow, liquidation-call, withdraw
Description: Price-sensitive operations (borrow, liquidation, withdraw) have no block-height deadline parameter. A transaction sitting in the mempool for many blocks could execute at a stale price even though Pyth feeds are updated inline โ€” the Pyth data itself was fresh when the transaction was submitted but the market may have moved.
Impact: In high-congestion periods, delayed transactions could execute at unfavorable prices. This is partially mitigated by Pyth's staleness checks, but a deadline would provide stronger user protection.
Recommendation: Add an optional deadline parameter:
(asserts! (< stacks-block-height deadline) (err u9999))

I-01: liquidation-call Returns Hardcoded (ok u0)

INFO
Location: liquidation-call
Description: The function discards the return value from pool-borrow-v2-4.liquidation-call and returns (ok u0). All other functions return (ok true). The inconsistency means callers cannot distinguish between different liquidation outcomes.
Recommendation: Propagate the underlying result or return (ok true) for consistency.

I-02: Hardcoded Token Addresses in supply-all

INFO
Location: supply-all
Description: The supply-all function hardcodes 10 specific token contract addresses (ststx, aeusdc, wstx, diko, usdh, susdt, usda, sbtc, ststxbtc, alex) and their corresponding z-token LP contracts. Adding or removing supported assets requires deploying a new helper version.
Recommendation: This is an acceptable trade-off for Clarity's static dispatch model. Document the asset list in protocol docs so users know which version supports which assets.

I-03: Pre-Clarity 4 Contract

INFO
Location: Entire contract
Description: The contract does not use Clarity 4 features. However, since this contract never uses as-contract and holds no assets, the practical impact is zero. Clarity 4's as-contract? with explicit allowances would be relevant if the contract ever needed to act with its own authority.
Recommendation: No action needed for this specific contract. Future helper versions should adopt Clarity 4 if they introduce any as-contract patterns.

Trust Assumptions

This contract's security is almost entirely dependent on downstream contracts. The helper is a pass-through โ€” it cannot independently lose funds. Key trust boundaries:

Conclusion

This is a well-structured, low-risk helper contract. It follows the common Aave-style "helper" pattern โ€” a thin routing layer that bundles oracle updates, incentive claims, and event emission around core protocol calls. The one material finding (M-01) is a copy-paste bug in the liquidation event log that could mislead off-chain systems. The architectural decision to keep the helper stateless and custody-free is sound and limits the blast radius of any bugs in this layer.