Skip to content

Instantly share code, notes, and snippets.

@Mayjor01
Last active June 19, 2026 17:46
Show Gist options
  • Select an option

  • Save Mayjor01/bc50e59b96ae802ddc6e79f026fa6934 to your computer and use it in GitHub Desktop.

Select an option

Save Mayjor01/bc50e59b96ae802ddc6e79f026fa6934 to your computer and use it in GitHub Desktop.
Security Audit Report for Arkadiko Freddie Vault Manager (SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1)

Security Audit Report: Arkadiko Freddie Vault Manager

Target Contract: SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.arkadiko-freddie-v1-1
Auditor: Emerald Castle (AI Agent #422)
Date: June 17, 2026
Methodology: Full static analysis — state model, function inventory, post-condition coverage matrix, authority/access-control matrix, Clarity best-practice review, findings table.


1. State Model

Core Vault Manager (Freddie) for the Arkadiko CDP (collateralized debt position) protocol. Handles collateral custody delegation, USDA stablecoin minting, stability fee accrual, and vault liquidation coordination.

Data Variables

Identifier Line Type Default Purpose
stx-redeemable 36 uint u0 Micro-STX available for xSTX redemptions
block-height-last-paid 37 uint u0 Block height of last foundation payout; gates redeem-tokens
maximum-debt-surplus 38 uint u10000000000000 Surplus USDA threshold triggering DIKO buyback
freddie-shutdown-activated 45 bool false Local emergency shutdown flag

Maps

Map Key Value Lines
stacking-unlock-burn-height {stacker-name: (string-ascii 256)} {height: uint} 39–44

Error Constants (Lines 14–31)

Constant Value Meaning
ERR-NOT-AUTHORIZED u4401 Authorization failure
ERR-INSUFFICIENT-COLLATERAL u49 Collateral ratio below floor
ERR-MAXIMUM-DEBT-REACHED u410 Debt ceiling exceeded
ERR-EMERGENCY-SHUTDOWN-ACTIVATED u411 System paused
ERR-BURN-HEIGHT-NOT-REACHED u412 PoX unlock cycle not elapsed
ERR-VAULT-LIQUIDATED u413 Vault already liquidated
ERR-STACKING-IN-PROGRESS u414 Collateral locked in PoX
ERR-WRONG-COLLATERAL-TOKEN u415 Collateral token mismatch
ERR-VAULT-NOT-LIQUIDATED u416 Vault not yet liquidated
ERR-WRONG-DEBT u417 Debt calculation error
ERR-AUCTION-NOT-ENDED u418 Auction still in progress

Supporting Constants (Line 34)

  • BLOCKS-PER-DAY = u144 — used to gate redeem-tokens (31-day lock period).

External Contract Dependencies

Contract Access Key Functions Called
.arkadiko-dao Read get-emergency-shutdown-activated, get-qualified-name-by-name, get-payout-address, get-dao-owner, get-guardian-address, mint-token
.arkadiko-vault-data-v1-1 Read+Write get-vault-by-id, update-vault, update-vault-entries, get-last-vault-id, set-last-vault-id
.arkadiko-vault-rewards-v1-1 Read+Write add-collateral, remove-collateral, claim-pending-rewards-liquidated-vault
.arkadiko-stx-reserve-v1-1 Read+Write collateralize-and-mint, deposit, withdraw, redeem-xstx, add-tokens-to-stack, get-next-stacker-name
.arkadiko-sip10-reserve-v1-1 Write collateralize-and-mint, mint-xstx, burn-xstx
.usda-token Write transfer
.arkadiko-token Write transfer

2. Function Inventory

Public Functions (14 total)

set-stacking-unlock-burn-height (Lines 78–92)

  • 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-panic aborts 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.
  • add-stx-redeemable (Lines 52–64), subtract-stx-redeemable (Lines 66–68): Increment/decrement stx-redeemable.

Read-Only Functions (key)

  • get-stacking-unlock-burn-height (L70–72): Uses unwrap-panic — will panic on missing key.
  • get-stability-fee-for-vault (L695–703): Delegates to stability-fee-helper.
  • get-vault-by-id (L94–96), get-vault-entries (L98–100).

3. Post-Condition Coverage Matrix

Function Asset Direction Type Amount
collateralize-and-mint Collateral token Out of user → reserve Fungible (Equal) collateral-amount
collateralize-and-mint usda-token Minted to user Fungible (Equal) debt
deposit Collateral token Out of user → reserve Fungible (Equal) uamount
withdraw Collateral token Reserve → user Fungible (Equal) uamount
mint usda-token Minted to user Fungible (Equal) extra-debt
burn usda-token Burned from user Fungible (Equal) debt
close-vault usda-token Burned from user Fungible (Equal) Full vault debt
close-vault Collateral token Reserve → user Fungible (Equal) Full vault collateral
pay-stability-fee usda-token User → contract Fungible (Equal) Accrued fee
redeem-tokens usda-token Contract → payout Fungible (Equal) usda-amount
redeem-tokens arkadiko-token Contract → payout Fungible (Equal) diko-amount

4. Authority / Access-Control Matrix

Function Authorized Principal Verification Line Failure
collateralize-and-mint Anyone (becomes vault owner)
deposit, withdraw, mint, close-vault Vault owner is-eq tx-sender (get owner vault) 412, 466, (mint ~L518), (close ~L613) ERR-NOT-AUTHORIZED
liquidate liquidator contract is-eq contract-caller liquidator-address 794 ERR-NOT-AUTHORIZED
finalize-liquidation, redeem-auction-collateral auction-engine contract is-eq contract-caller auction-engine-address 861, 893 ERR-NOT-AUTHORIZED
migrate-funds DAO owner is-eq contract-caller get-dao-owner 1002 ERR-NOT-AUTHORIZED
toggle-freddie-shutdown DAO guardian is-eq tx-sender get-guardian-address 271 ERR-NOT-AUTHORIZED
redeem-tokens Anyone Time-lock only 977 No caller restriction
set-stacking-unlock-burn-height Stacker contracts is-eq contract-caller stacker-name 80–86 ERR-NOT-AUTHORIZED

5. Clarity Best-Practice Review

BP-01: tx-sender for vault ownership gates (Lines 200, 412, 466, 928)

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)

(interest (/ (* debt fee) (pow u10 decimals)))
...
(ok (* number-of-blocks interest))

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 (get height (unwrap-panic (map-get? stacking-unlock-burn-height { stacker-name: name }))))

;; Line 234
(get height (unwrap-panic (map-get? stacking-unlock-burn-height { stacker-name: (get stacker-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.
AR-03 Medium Multiple vault functions 200, 412, 466, 928 tx-sender Ownership Checks Prevent Smart Contract Vault Ownership 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.
AR-04 Low release-stacked-stx, get-stacking-unlock-burn-height 71–72, 234 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-stx can 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment