You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Auth:contract-caller must be one of stacker, stacker-2, stacker-3, or stacker-4 from DAO registry (L80–86).
Effect: Writes a {height: burn-height} record into stacking-unlock-burn-height map (L90).
release-stacked-stx (Lines 219–247)
Auth: None (public).
Guards: Emergency shutdown check (L221–227), collateral token must be "xSTX" (L228), vault must be liquidated (L229), stacked tokens > 0 (L230), burn-block-height >= stored unlock height (L231–237).
Critical: Line 234 calls unwrap-panic on map-get? stacking-unlock-burn-height. If the stacker-name was never registered, map-get? returns none and unwrap-panicaborts the entire transaction — see Finding AR-04.
redeem-stx (Lines 255–267)
Auth: Public (anyone).
Effect: Burns min(stx-redeemable, ustx-amount) xSTX and redeems equivalent STX for tx-sender.
toggle-freddie-shutdown (Lines 269–275)
Auth:tx-sender must equal DAO guardian address (L271).
Effect: Flips freddie-shutdown-activated boolean.
collateralize-and-mint (Lines 288–371)
Auth: Anyone (vault owner becomes tx-sender at L299).
Guards: Shutdown checks (L305–311), authorized trait contracts (L312–320), collateral ratio floor (L322), debt ceiling (L323–329), collateral token match (L330–336).
Effect: Delegates collateral to reserve, mints USDA (L339 — via as-contract), creates vault record.
deposit (Lines 380–427)
Auth:tx-sender must equal vault owner (L412).
Note: Shutdown and authorization checks appear after computing the new collateral value (L391–394) — minor ordering inefficiency but no exploitable consequence.
withdraw (Lines 437–499)
Auth:tx-sender must equal vault owner (L466).
Guards: Not liquidated (L465), owner check (L466), amount > 0 (L467), amount <= collateral (L468), not stacking (L469), post-withdrawal ratio >= floor (L493).
mint (Lines 501–540) (not shown in full — summarized)
Auth: Vault owner via tx-sender.
Effect: Accrues stability fee, mints extra USDA, updates vault debt.
burn (Lines 542–595) (summarized)
Auth: Anyone (pays off debt for any vault).
Effect: Pays stability fees, burns USDA, decrements vault debt.
close-vault (Lines 597–650) (summarized)
Auth: Vault owner via tx-sender.
Effect: Burns full debt, withdraws all collateral, marks vault closed.
liquidate (Lines 782–840)
Auth:contract-caller must equal DAO liquidator contract (L794).
Effect: Flags vault as liquidated, mints xSTX if collateral was stacking, returns collateral/debt data.
finalize-liquidation (Lines 848–874)
Auth:contract-caller must equal auction-engine (L861).
Effect: Records leftover collateral, marks auction ended, adjusts debt totals.
redeem-tokens (Lines 975–992)
Auth: NONE — fully public. Only gate is time: block-height - block-height-last-paid > BLOCKS-PER-DAY * 31 (L977).
Effect: Resets block-height-last-paid to current block-height (L979), then transfers USDA and/or DIKO from contract to DAO payout address.
Critical: See Finding AR-01.
migrate-funds (Lines 1000–1010)
Auth:contract-caller must equal get-dao-owner (L1002).
Effect: Transfers full token balance to a new vault manager contract.
Private Functions (key)
stability-fee-helper (Lines 705–721): Computes accrued fee as number-of-blocks * (debt * fee / 10^decimals) — see Finding AR-02.
All vault mutation functions (deposit, withdraw, mint, close-vault, withdraw-leftover-collateral) compare tx-sender against the vault owner field. tx-sender is the originating EOA and never equals a contract principal. This design prevents:
Smart-contract wallet users from owning vaults
Automated vault managers / DeFi composability (e.g., a yield router that manages a user's vault)
Multi-sig treasury vaults
Vaults can only be created and managed by raw EOA keys.
BP-02: Division-before-multiplication causes stability fee precision loss (Lines 714–719)
At L716, interest = (debt * fee) / 10^decimals is computed per block before multiplying by number-of-blocks. For small debt * fee values (e.g. debt = u1000000, fee = u1, decimals = 6), integer division yields (/ 1000000 1000000) = u1. Then interest * number-of-blocks = 1 * 4320 (30-day accrual). The correct value is (debt * fee * number-of-blocks) / 10^decimals = 4320. The result is accidentally correct here, but for any debt * fee < 10^decimals, integer division produces u0 and the vault pays zero stability fees forever, breaking the protocol's revenue model.
BP-03: unwrap-panic on map lookup without guaranteed key presence (Lines 71–72, 234)
;; Line 71
(ok (getheight (unwrap-panic (map-get?stacking-unlock-burn-height { stacker-name: name }))))
;; Line 234
(getheight (unwrap-panic (map-get?stacking-unlock-burn-height { stacker-name: (getstacker-name vault) })))
Both call unwrap-panic on a map-get? result. If the stacker-name was never written by an authorized stacker contract (e.g., the vault was created pre-stacking, or the stacker-name field is malformed), map-get? returns none and unwrap-panic causes an immediate transaction abort. In release-stacked-stx (L234), this abort permanently blocks release of the stacked STX — the function can never succeed for that vault.
6. Findings Table
ID
Severity
Function
Lines
Title
Description
Recommended Fix
AR-01
High
redeem-tokens
975–992
Unrestricted Public Payout Timer Reset — Permanent DAO Revenue DoS
redeem-tokens has no caller restriction. The only gate is the time-lock at L977: (> (- block-height block-height-last-paid) (* BLOCKS-PER-DAY u31)). Any address can call redeem-tokens (u1 u0) — transferring 1 micro-USDA and 0 DIKO — which: (1) satisfies the time-lock, (2) resets block-height-last-paid to block-height (L979), and (3) restarts the 31-day (≈4,464 blocks) clock. An attacker executing this every 31 days spends negligible gas while permanently preventing the Arkadiko DAO from collecting any meaningful stability fee revenue. Since usda-amount and diko-amount are caller-supplied with no minimum, even a 1 micro-token call resets the full timer.
Restrict redeem-tokens to an authorized caller: (asserts! (is-eq contract-caller (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "payout-manager"))) (err ERR-NOT-AUTHORIZED)). Alternatively, add a minimum transfer floor so that token amounts of u0 or u1 are rejected.
AR-02
Medium
stability-fee-helper
712–719
Division-Before-Multiplication Causes Zero Stability Fee for Small Vaults
At L716: (interest (/ (* debt fee) (pow u10 decimals))). When debt * fee < 10^decimals, integer division rounds to u0. The vault then accrues zero fees for every block — indefinitely. Example:debt = u500000 (0.5 USDA), fee = u1, decimals = 6: interest = (/ 500000 1000000) = u0. This vault will never pay a stability fee regardless of time elapsed. Any position below ~1 USDA with low fee rates is permanently exempt.
Reorder the arithmetic to multiply first: (interest (/ (* (* debt fee) number-of-blocks) (pow u10 decimals))). Move the number-of-blocks multiplication inside and divide once at the end to preserve full precision.
All vault management functions compare tx-sender to the vault owner field (e.g. L412 in deposit, L466 in withdraw). tx-sender is the originating EOA and can never equal a contract principal. This blocks: multi-sig safe owners, smart-contract wallets, automated DeFi strategies, and any composability layer from managing user vaults.
Implement a delegated authorization model: add a vault-delegates map (uint → (list 10 principal)) and check both tx-sender and entries in the delegate list. Alternatively, replace tx-sender with contract-caller to support contract-based interaction patterns.
unwrap-panic on Unregistered Stacker Name Permanently Locks STX
Line 234 calls unwrap-panic on map-get? stacking-unlock-burn-height using the vault's stacker-name. If this entry was never written (race condition, upgrade path, or malformed vault state), unwrap-panic aborts the transaction. For any such vault, release-stacked-stxcan never succeed — the stacked STX collateral is permanently locked with no recovery path. The same panic exists in the get-stacking-unlock-burn-height read-only function at L71.
Replace unwrap-panic with unwrap! and an explicit error, or use default-to with a safe fallback: (default-to u0 (get height (map-get? stacking-unlock-burn-height { stacker-name: (get stacker-name vault) }))). Add a guard asserting the returned height is > u0 before proceeding.