stacking-dao-core-v5
StackingDAO Core v5 — Security Audit
Liquid stacking protocol — deposit STX, receive stSTX, withdraw via NFT receipt or idle pool (v5 upgrade)
V4 → V5 Changes
- stSTX burned at init-withdraw, not at withdraw. In v4,
init-withdrawtransferred stSTX to the contract and burned it duringwithdraw. In v5, stSTX is burned immediately duringinit-withdrawviaburn-for-protocol. This eliminates the contract holding stSTX tokens between init and withdraw — a meaningful improvement that reduces contract surface area. - Commission address validation. New
commission-addressvariable with setter and getter.deposit,withdraw-idle, andwithdrawnow assert the passed commission contract matches the configured address. Prevents using an arbitrary (but protocol-registered) commission contract. - Data source upgrade. Exchange rate now read from
.data-core-v3(was.data-core-v2in v4). - Print event fix. Deposit event field corrected from
stxstx-amount→ststx-amount.
Summary
| Severity | Count |
|---|---|
| MEDIUM | 2 |
| LOW | 2 |
| INFO | 3 |
Findings
MEDIUM M-01: No slippage protection on deposit and withdrawal
Location: deposit, withdraw-idle, withdraw
Description: None of the user-facing functions accept a minimum output parameter. The stSTX/STX exchange rate is read from .data-core-v3 at execution time. Between transaction submission and inclusion in a block, the rate can change due to stacking rewards, other deposits/withdrawals, or oracle updates. Users may receive fewer stSTX (on deposit) or less STX (on withdrawal) than expected.
;; deposit: no min-ststx-out parameter
(stx-ststx (try! (contract-call? .data-core-v3 get-stx-per-ststx reserve)))
(ststx-amount (/ (* stx-user-amount DENOMINATOR_6) stx-ststx))
Impact: Users are exposed to exchange rate changes between tx submission and execution. In a volatile period or during a large rebase event, users could receive significantly less than expected. While Stacks blocks are relatively slow (~10 min), the exchange rate changes at PoX cycle boundaries which could coincide with pending transactions.
Recommendation: Add a min-ststx-out parameter to deposit and min-stx-out parameters to withdraw-idle and withdraw. Assert the output meets the minimum before completing the operation.
MEDIUM M-02: as-contract blanket authority (pre-Clarity 4)
Location: Multiple — deposit (line ~140), withdraw-idle (lines ~187-195), init-withdraw (lines ~229-231), withdraw (lines ~273-280)
Description: The contract uses as-contract (Clarity 3) which grants blanket authority to move any asset the contract holds. Clarity 4 introduced as-contract? with explicit asset allowances (with-stx, with-ft, with-nft) that restrict which assets can be moved within the scope.
;; v5 uses blanket as-contract
(try! (as-contract (contract-call? reserve request-stx-for-withdrawal stx-user-amount receiver)))
;; Clarity 4 would restrict to only STX:
(try! (as-contract? (with-stx stx-user-amount)
(contract-call? reserve request-stx-for-withdrawal stx-user-amount receiver)))
Impact: If any of the trait-parameterized contracts (reserve, commission-contract, staking-contract, direct-helpers) contains a vulnerability or is upgraded to a malicious implementation, the as-contract scope could be abused to move any asset held by this contract. The DAO protocol validation (check-is-protocol) mitigates this significantly, but a compromised DAO could register a malicious contract.
Recommendation: Upgrade to Clarity 4 and use as-contract? with explicit with-stx allowances. This provides language-level enforcement that only STX can be moved, regardless of what the called contracts attempt.
LOW L-01: unwrap-panic in user-facing functions can cause silent transaction failures
Location: deposit (line ~129: get-idle-cycle), withdraw-idle (line ~170), init-withdraw (lines ~212, ~219: get-withdraw-unlock-burn-height and get-last-token-id)
Description: Multiple unwrap-panic calls are used in user-facing public functions. If the unwrapped value is none or (err ...), the entire transaction aborts with a runtime error rather than returning a meaningful error code.
(idle-cycle (unwrap-panic (get-idle-cycle)))
(unlock-burn-height (unwrap-panic (get-withdraw-unlock-burn-height)))
(nft-id (unwrap-panic (contract-call? .ststx-withdraw-nft-v2 get-last-token-id)))
Impact: Users see opaque "runtime error" instead of a descriptive error code. Difficult to debug failed transactions. In practice, these functions should always return valid values, so the risk is low.
Recommendation: Replace unwrap-panic with unwrap! and specific error constants for better debuggability.
LOW L-02: Fee parameters can be set up to 100%
Location: set-stack-fee, set-unstack-fee, set-withdraw-idle-fee
Description: Fee setters only check (<= fee DENOMINATOR_BPS), allowing fees up to 10000 bps (100%). A compromised or malicious DAO governance action could set fees to confiscate all user deposits or withdrawals.
(define-public (set-stack-fee (fee uint))
(begin
(try! (contract-call? .dao check-is-protocol contract-caller))
(asserts! (<= fee DENOMINATOR_BPS) (err ERR_WRONG_BPS))
(var-set stack-fee fee)
(ok true)
)
)
Impact: If governance is compromised, fees could be set to extract all user value. Current DAO governance provides the primary protection. This is a defense-in-depth concern.
Recommendation: Add a reasonable maximum fee cap (e.g., 1000 bps / 10%) as a hard-coded constant. This limits damage even if governance is compromised.
INFO I-01: Rounding consistently favors the protocol
Location: deposit, withdraw-idle, withdraw
Description: All integer divisions round down (Clarity's default behavior). On deposit, ststx-amount rounds down (user gets slightly less stSTX). On withdrawal, stx-amount rounds down (user gets slightly less STX). Fee calculations also round down (protocol gets slightly less fee). This is standard practice and not a vulnerability, but worth noting for completeness.
Impact: Negligible dust amounts (< 1 microSTX per operation) lost by users. Consistent with industry standard.
INFO I-02: No minimum deposit or withdrawal amount
Location: deposit, withdraw-idle, init-withdraw
Description: There is no minimum amount enforced for deposits or withdrawals. Extremely small amounts (e.g., 1 microSTX) could be deposited, which would mint 0 stSTX due to rounding, effectively donating the deposit to the protocol.
Impact: User error only — no economic attack vector. The gas cost of a transaction far exceeds any dust amount lost.
INFO I-03: Commission address is a single point of trust
Location: set-commission-address, deposit, withdraw-idle, withdraw
Description: The v5 upgrade adds commission address validation, which is an improvement over v4. However, the commission-address can be changed by any protocol-authorized caller via set-commission-address. If governance sets this to a malicious commission contract, all fees would be redirected.
Impact: Governance-dependent risk. The commission contract must be protocol-registered AND match the configured address, providing two layers of validation. This is a documented trust assumption, not a vulnerability.
Positive Observations
- Burn-at-init pattern (new in v5). Burning stSTX immediately during
init-withdrawinstead of holding it untilwithdrawis a significant improvement. Reduces the contract's token surface area and eliminates the risk of stSTX being stuck if the withdrawal flow fails. - Commission address pinning (new in v5). Requiring the passed commission contract to match the configured address prevents using an arbitrary protocol-registered contract as the commission target.
- Consistent protocol validation. All four trait parameters are validated via
check-is-protocolin every function. - Granular shutdown controls. Four independent shutdown flags allow disabling specific operations without affecting others.
- Clean fee handling. Fee-on-top model with explicit zero-check avoids unnecessary transfers.
Architecture Notes
StackingDAO Core v5 is part of a multi-contract system. Key trust dependencies:
.dao— access control (check-is-enabled, check-is-protocol).data-core-v1— withdrawal NFT data, cycle offsets.data-core-v2— idle STX tracking, withdraw inset.data-core-v3— exchange rate (stx-per-ststx) — new in v5.ststx-token— SIP-010 liquid stacking token (mint/burn).ststx-withdraw-nft-v2— withdrawal receipt NFTs.commission-v2— default commission handler- Four trait-parameterized contracts passed by the caller (all validated)
The exchange rate source upgrade from .data-core-v2 to .data-core-v3 is the most significant external dependency change.
Independent audit by cocoa007.btc · Full audit portfolio