sienna-enzo/stacks-yield-elite · Staking & Yield Vault · February 21, 2026
StacksYield Elite is a multi-tier liquid staking protocol where users deposit STX and earn YIELD-ANALYTICS-TOKEN rewards. It features three tiers (Explorer, Pioneer, Titan) with escalating yield multipliers, time-lock bonuses up to 3x, a cooldown-based withdrawal mechanism, and an on-chain governance system with weighted voting.
The contract is ~400 lines of Clarity in a single file. It handles real STX transfers, making the bugs especially consequential. The protocol is non-functional in its current state — staked funds cannot be withdrawn and rewards cannot be minted.
The withdrawal function attempts to return STX to the user but sends it back to the contract:
(define-public (complete-withdrawal)
(let (
(stake-position (unwrap! (map-get? StakingPositions tx-sender) ERR-NO-STAKE))
(cooldown-start (unwrap! (get cooldown-initiated stake-position) ERR-NOT-AUTHORIZED))
(withdrawal-amount (get staked-amount stake-position))
)
;; ...cooldown check...
(try! (as-contract (stx-transfer? withdrawal-amount tx-sender tx-sender)))
;; ...cleanup...
)
)
Inside as-contract, both occurrences of tx-sender resolve to the contract's own principal. The STX transfer goes from contract → contract. The user's position is deleted but they receive nothing.
Impact: All staked STX is permanently locked. No user can ever withdraw.
Fix: Capture the user's principal before entering as-contract:
(let ((user tx-sender))
;; ...
(try! (as-contract (stx-transfer? withdrawal-amount tx-sender user)))
)
When a user stakes additional tokens, the staking position is overwritten rather than accumulated:
(map-set StakingPositions tx-sender {
staked-amount: amount, ;; ← should be updated-stake-total
stake-start-block: stacks-block-height,
...
})
The UserPositions map correctly tracks updated-stake-total, but StakingPositions stores only the latest deposit. Previous deposits remain locked in the contract but are no longer tracked against the user.
Impact: Repeated staking silently orphans previously staked STX. A user staking 1M then 500K loses visibility of the first 1M — it's still in the contract but withdrawal can only recover 500K.
(define-fungible-token YIELD-ANALYTICS-TOKEN u0)
The second argument to define-fungible-token is the maximum supply. With u0, the token has a max supply of zero. Every call to ft-mint? will fail because minting any amount exceeds the cap.
Impact: claim-staking-rewards always reverts. No rewards can ever be distributed. The entire yield mechanism is broken.
Fix: Either remove the supply cap or set a meaningful maximum: (define-fungible-token YIELD-ANALYTICS-TOKEN)
(let (
(final-yield-multiplier (* (get yield-multiplier tier-details) time-lock-multiplier))
)
Both tier yield-multiplier (up to 300 for Titan) and time-lock-multiplier (up to 300 for 120-day lock) are expressed in basis points where 100 = 1x. Multiplying them gives up to 90,000 — which is 900x, not 9x as intended.
Impact: Titan-tier users with 120-day locks get 900x yield multiplier instead of 9x. Reward calculation produces absurdly inflated values. (Moot currently due to H-1, but would be exploitable if the FT bug were fixed.)
Fix: (/ (* tier-mult time-lock-mult) u100)
Neither initiate-withdrawal nor complete-withdrawal checks whether the lock period has elapsed:
;; initiate-withdrawal checks:
;; ✓ sufficient balance
;; ✓ no existing cooldown
;; ✓ not emergency mode
;; ✗ lock-duration elapsed? — NOT CHECKED
;; complete-withdrawal checks:
;; ✓ cooldown period complete
;; ✗ lock-duration elapsed? — NOT CHECKED
Impact: A user can stake with a 120-day lock (receiving the maximum 3x yield multiplier) then immediately initiate withdrawal. They get the enhanced rewards without honoring the lock commitment. The time-lock incentive structure is meaningless.
initiate-withdrawal starts the cooldown but doesn't reduce governance power. During the entire cooldown period (2,160 blocks / ~36 hours), the user retains full voting weight despite having signaled their intent to exit. They can vote on proposals with power derived from stake they're withdrawing.
Impact: Exiting stakers retain governance influence during cooldown, allowing last-minute voting before withdrawal completes.
Users deposit real STX but earn YIELD-ANALYTICS-TOKEN. There is no mechanism to:
The yield token has no utility beyond governance voting weight (which is also calculated from STX stake, not token balance). The "yield" is entirely illusory — users get a number in a map but no recoverable value.
initiate-withdrawal accepts a withdrawal-amount parameter and validates it against the staked balance, but never stores it. complete-withdrawal always withdraws the full staked-amount:
;; initiate-withdrawal: validates withdrawal-amount but only sets cooldown flag
;; complete-withdrawal: reads staked-amount from position (ignores original request)
Impact: Users cannot partially withdraw. The parameter creates a false expectation of partial withdrawal support. All-or-nothing withdrawals force users to fully exit and re-stake to reduce position size.
TierConfiguration entries don't exist until the owner calls initialize-protocol. Staking works before initialization because calculate-tier-assignment uses hardcoded values — but the TierConfiguration map entries that governance or UI might query will return none.
Additionally, initialize-protocol can be called multiple times, allowing the owner to silently change tier parameters.
The contract owner can:
None of these actions require governance approval or have a timelock delay. The governance system exists but has no power over protocol parameters — it's purely advisory.
| Aspect | Assessment |
|---|---|
| STX Custody | BROKEN Funds go in but can't come out (C-1) |
| Reward Distribution | BROKEN Zero-supply FT blocks all minting (H-1) |
| Staking Accounting | BROKEN Overwrites lose track of deposits (C-2) |
| Time-Lock Enforcement | BROKEN Never checked on withdrawal (H-3) |
| Governance System | WEAK Functional voting but no execution power |
| Tier System | OK Hardcoded fallback works but map is decorative |
| Access Control | CENTRALIZED Owner has full unilateral control |
StacksYield Elite is non-functional as deployed. The two critical bugs (self-transfer withdrawal, stake overwrite) mean funds deposited into this contract cannot be recovered. The zero-supply fungible token means no rewards can be distributed. The time-lock system provides yield bonuses but doesn't enforce the lock.
The governance system is the most complete subsystem — proposal creation, weighted voting, and double-vote prevention all work correctly. But governance has no teeth: it can't modify protocol parameters or execute any on-chain actions.
The core pattern here is a contract that looks sophisticated on the surface (tiers, multipliers, governance, cooldowns) but fails at the fundamental operations: depositing correctly, withdrawing at all, and distributing rewards.