Zest Protocol rewards-v8

Rewards distribution contract for stSTX and stSTXbtc liquid staking on Stacks

ContractSP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.rewards-v8
ProtocolZest Protocol โ€” liquid staking (stSTX, stSTXbtc) on Stacks
SourceVerified on-chain via Hiro API
Lines of Code~373
Clarity VersionPre-Clarity 4 (uses legacy as-contract)
Audit DateFebruary 24, 2026
Confidence๐ŸŸข HIGH โ€” self-contained rewards distributor; all findings verified against on-chain source
0
Critical
0
High
2
Medium
2
Low
2
Info

Overview

This contract manages the collection and linear distribution of staking rewards for Zest Protocol's liquid staking tokens (stSTX for STX stacking rewards, stSTXbtc for sBTC stacking rewards). Rewards earned during PoX cycle X are distributed gradually throughout cycle X+1, released in equal portions across configurable intervals.

The contract is well-structured with proper DAO access control on administrative functions. The process-rewards function validates all trait-based contract parameters against both stored addresses and DAO protocol membership. However, there are concerns around sBTC reward loss when token supplies reach zero, and the pre-Clarity 4 as-contract usage grants blanket authority to commission trait contracts.

Architecture

Findings

MEDIUM

M-01: sBTC rewards silently lost when both ststxbtc token supplies are zero

Location: process-rewards โ€” sBTC distribution block

Description: When rewards-protocol-sbtc > 0, the contract calculates distribution between v1 and v2 token holders based on supply:

(let (
  (ststxbtc-supply (unwrap-panic (contract-call? .ststxbtc-token get-total-supply)))
  (ststxbtc-supply-v2 (unwrap-panic (contract-call? .ststxbtc-token-v2 get-total-supply)))
  (total-supply (+ ststxbtc-supply ststxbtc-supply-v2))
  (rewards-v1 (if (is-eq total-supply u0)
    u0
    (/ (* rewards-protocol-sbtc ststxbtc-supply) total-supply)
  ))
  (rewards-v2 (if (is-eq total-supply u0)
    u0
    (- rewards-protocol-sbtc rewards-v1)
  ))
)

When total-supply = 0, both rewards-v1 and rewards-v2 are set to u0. Neither tracking contract receives any sBTC. However, the function still increments processed-protocol-sbtc by the full rewards-protocol-sbtc amount. The sBTC remains in the contract with no mechanism to distribute it to future holders.

Impact: If all stSTXbtc holders exit (both v1 and v2 supply reach zero) before rewards for a cycle are processed, those sBTC rewards are effectively locked. The get-sbtc admin function provides an escape hatch (DAO can recover the funds), but automatic distribution is permanently lost for those rewards. This is a realistic scenario during token migration periods.

Recommendation: Either revert when total-supply = 0 to defer processing until holders exist, or route orphaned rewards to the reserve:

(if (is-eq total-supply u0)
  ;; No holders โ€” send to reserve or revert
  (try! (as-contract (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token 
    transfer rewards-protocol-sbtc tx-sender reserve-address none)))
  (begin
    ;; Normal v1/v2 split...
  )
)
MEDIUM

M-02: as-contract grants blanket authority to commission trait contracts

Location: process-rewards โ€” commission calls

Description: The contract calls commission contracts using as-contract:

(try! (as-contract (contract-call? commission-ststx-contract add-commission 
  staking-contract rewards-commission-stx)))

In pre-Clarity 4, as-contract gives the callee full authority to act as the rewards contract โ€” including transferring any STX or tokens held by the contract, not just the intended commission amount. While the commission contract must be both DAO-approved and match the stored address, a compromised or malicious commission contract could drain all funds.

Impact: If a malicious commission contract is approved by the DAO (via governance attack or compromised multisig), it could drain the entire STX and sBTC balance of the rewards contract during a process-rewards call. The dual validation (stored address + DAO check) provides good defense-in-depth, but the pre-Clarity 4 as-contract makes the blast radius larger than necessary.

Recommendation: Migrate to Clarity 4 and use as-contract? with explicit asset allowances:

;; Only allow transferring the exact commission amount
(try! (as-contract? 
  (with-stx rewards-commission-stx)
  (contract-call? commission-ststx-contract add-commission 
    staking-contract rewards-commission-stx)))
LOW

L-01: Systematic rounding bias in sBTC distribution favors v2 token holders

Location: process-rewards โ€” sBTC v1/v2 split

Description: The v1/v2 reward split uses integer division for v1, then gives the remainder to v2:

(rewards-v1 (/ (* rewards-protocol-sbtc ststxbtc-supply) total-supply))
(rewards-v2 (- rewards-protocol-sbtc rewards-v1))

Integer division truncates, so v1 always rounds down and v2 always gets the rounding dust. Over many cycles, v2 holders systematically receive slightly more than their proportional share.

Impact: The per-cycle bias is at most 1 sat (the truncation remainder). Over hundreds of cycles, v2 holders accumulate at most a few hundred sats more than strictly proportional. This is economically negligible but represents a systematic unfairness.

Recommendation: This is a known pattern in integer-math DeFi. Alternating which side gets the remainder (e.g., based on cycle parity) would eliminate the systematic bias, though the economic impact doesn't warrant the added complexity.

LOW

L-02: No minimum amount validation in add-rewards functions

Location: add-rewards and add-rewards-sbtc

Description: Both reward addition functions accept any stx-amount / sbtc-amount, including zero. With small amounts, the commission calculation (/ (* amount commission) DENOMINATOR_BPS) can round to zero, meaning the full amount bypasses commission. A zero-amount call succeeds, emitting events and updating maps with +0 values.

Impact: No direct financial loss (caller pays their own funds), but zero/dust-amount calls pollute event logs and waste block space. With very small amounts, commission rounding to zero means pool owners receive nothing while the protocol gets the full amount.

Recommendation: Add a minimum amount check: (asserts! (> stx-amount u0) (err ERR_INVALID_AMOUNT)). Consider a higher minimum to ensure commission calculations are meaningful.

INFO

I-01: Pre-Clarity 4 โ€” recommend migration to as-contract?

Location: Entire contract

Description: The contract uses as-contract in multiple locations for STX transfers and sBTC transfers, as well as trait-based calls to commission and tracking contracts. Clarity 4 introduces as-contract? with explicit asset allowances (with-stx, with-ft) that limit the scope of authority granted to callees.

Recommendation: When deploying rewards-v9, target Clarity 4 and replace all as-contract calls with scoped as-contract?. This would mitigate M-02 at the language level.

INFO

I-02: Hardcoded sBTC token contract address

Location: add-rewards-sbtc, get-sbtc

Description: The sBTC token address is hardcoded as 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token throughout the contract. If sBTC migrates to a new contract (e.g., a v2 token), a new rewards contract version would need to be deployed.

Recommendation: This is acceptable for the current version since sBTC is canonical. A future version could use a configurable token address (DAO-settable), but the additional complexity may not be warranted given that a rewards contract upgrade would likely be needed anyway for other reasons.

Positive Observations

Access Control Summary

FunctionAccessNotes
add-rewardsPermissionlessCaller pays own funds โ€” no restriction needed
add-rewards-sbtcPermissionlessCaller pays own funds โ€” no restriction needed
process-rewardsPermissionless (DAO-gated traits)Anyone can trigger, but all contracts must be DAO-approved + match stored addresses
get-stxDAO protocol onlyEmergency fund recovery
get-sbtcDAO protocol onlyEmergency fund recovery
set-rewards-interval-lengthDAO protocol onlyValidates interval divides cycle length
set-ststx-commission-contractDAO protocol onlyCorrectly guarded
set-ststxbtc-commission-contractDAO protocol onlyCorrectly guarded
set-staking-contract-addressDAO protocol onlyCorrectly guarded

Priority Score

MetricScoreWeightWeighted
Financial Risk3 โ€” DeFi staking rewards (holds STX + sBTC)39
Deployment Likelihood3 โ€” deployed on mainnet26
Code Complexity2 โ€” ~373 lines, multi-token distribution logic24
User Exposure3 โ€” Zest is a prominent Stacks protocol1.54.5
Novelty2 โ€” first rewards/vesting contract in collection1.53
Total Score2.65 / 3.0

Clarity version penalty: -0.5 (pre-Clarity 4) โ†’ Adjusted: 2.15 / 3.0 โ€” well above 1.8 threshold.