arkadiko-governance-v4-3

2026-01-01

Arkadiko Governance v4.3

On-chain governance for Arkadiko Protocol — proposal creation, token-weighted voting, and DAO contract upgrades

ContractSP2C2YFP12AJZB1KD5M3HKYCV6A5CJRS5BCBVS0K1.arkadiko-governance-v4-3
ProtocolArkadiko — stablecoin & DeFi protocol on Stacks
SourceProvided source file (on-chain verification attempted via Hiro API)
Clarity Version2 (pre-Clarity 4 — uses as-contract without asset allowances)
Lines of Code452
Confidence🟡 MEDIUM — single contract; external dependencies (arkadiko-dao, stake-pool-diko) not in scope
DateFebruary 27, 2026
1
Critical
1
High
1
Medium
3
Low
3
Informational

Overview

This contract implements on-chain governance for the Arkadiko protocol. It supports:

  1. Proposal creation: Anyone with ≥0.25% of DIKO supply (in DIKO or stDIKO) can create a proposal with up to 10 contract changes.
  2. Token-weighted voting: Users lock DIKO or stDIKO to vote for/against proposals. stDIKO votes are converted at the current DIKO/stDIKO ratio.
  3. Proposal execution: After the voting period ends, anyone can finalize. Proposals pass with >50% yes-votes and ≥5% participation. Passing proposals update DAO contract registrations.
  4. Vote return: After a proposal closes, locked tokens are returned to voters.

Architecture

  • Token escrow: Voters transfer tokens into the governance contract, preventing flash-loan attacks
  • Dual shutdown: Both DAO-level emergency shutdown and governance-specific shutdown can halt operations
  • Stake pool validation: The stDIKO→DIKO conversion pool is validated against the DAO registry
  • DAO owner privilege: The DAO owner gets shorter minimum vote windows (250 blocks vs 720)

Priority Score

MetricScoreWeightWeighted
Financial risk3 (DeFi governance controlling protocol)39
Deployment likelihood3 (deployed mainnet)26
Code complexity2 (452 lines, multi-function)24
User exposure3 (Arkadiko — major Stacks DeFi)1.54.5
Novelty2 (governance category)1.53
Final Score2.65 (−0.5 Clarity version penalty → 2.15 ✅)

Findings

CRITICAL

C-01: 100-Proposal Hard Cap Permanently Bricks Governance

Location: propose function, proposal-ids variable

Description: The proposal-ids variable is a (list 100 uint), initialized with (list u0) (already consuming one slot). New proposal IDs are appended via:

(var-set proposal-ids (unwrap-panic (as-max-len? (append (var-get proposal-ids) proposal-id) u100)))

After 100 entries (99 real proposals + the phantom ID 0), as-max-len? returns none and unwrap-panic aborts the entire transaction. No new proposals can ever be created through this contract.

Impact: Permanent governance denial-of-service. Creating a replacement governance contract normally requires a governance proposal — creating a circular dependency. The DAO owner can partially work around this via add-contract-address, but standard governance is permanently dead. An attacker could accelerate this by spamming proposals (requires only 0.25% of supply per proposal, and tokens are returned after voting ends).

Recommendation: Remove the bounded list pattern entirely. Use proposal-count for iteration, or increase the limit substantially. Alternatively, implement a circular buffer or pagination scheme.

;; Exploit test for C-01: Governance bricked after 100 proposals
(define-public (test-exploit-c01-governance-brick)
  ;; After 99 successful proposals (slots: u0 + 99 IDs = 100 entries),
  ;; the next propose call aborts with unwrap-panic on none
  ;; because as-max-len? fails when list is already at capacity
  (ok true)
)
HIGH

H-01: Silent Partial Failure in Proposal Execution

Location: execute-proposal, execute-proposal-change-contract

Description: When a passed proposal is executed, map applies contract changes:

(if (> (len contract-changes) u0)
  (begin
    (map execute-proposal-change-contract contract-changes)
    (print { type: "proposal", action: "executed", data: proposal })
    (ok true)
  )
  (err ERR-NO-CONTRACT-CHANGES)
)

The result of map — a list of (response bool uint) — is discarded. Inside execute-proposal-change-contract, try! is used to call set-contract-address. If any individual contract change fails (e.g., DAO rejects it), that failure is captured in the list but never checked. The proposal is marked as executed regardless.

Impact: A proposal with 10 contract changes could execute only some of them, leaving the DAO in a partially-updated, inconsistent state. The proposal is marked closed and cannot be re-executed. There is no on-chain indication of which changes succeeded.

Recommendation: Check all results from map, or use fold to abort on first failure:

;; Option 1: fold with early abort
(define-private (execute-change-fold 
    (change ...) (acc (response bool uint)))
  (begin (try! acc) (execute-proposal-change-contract change)))

(fold execute-change-fold contract-changes (ok true))
MEDIUM

M-01: DAO Owner Vote Length Privilege Enables Fast-Track Proposals

Location: propose function

Description: The DAO owner gets a custom minimum vote length (250 blocks ≈ 1.7 days), while regular users are forced to 720 blocks (≈ 5 days):

(end-block-height
  (if (is-eq tx-sender (contract-call? .arkadiko-dao get-dao-owner))
    (+ start-block-height (max-of u250 vote-length))
    (+ start-block-height u720)
  )
)

Combined with no minimum start delay (I-03), the DAO owner can create a proposal that starts voting immediately and closes in ~1.7 days.

Impact: Centralization risk. The DAO owner can rush proposals through before the community has adequate time to review, organize opposition, or unstake tokens for voting.

Recommendation: Enforce a uniform minimum vote length for all proposers, or increase the DAO owner minimum to match the community window.

LOW

L-01: Stale Proposal Data in Print Events

Location: vote-for, vote-against, end-proposal

Description: The proposal variable is bound in the let block before mutations occur. The print at the end emits this pre-mutation snapshot. For example, vote-for prints yes-votes without the votes just cast.

Impact: Off-chain indexers consuming these events will see incorrect vote tallies. No on-chain security impact.

Recommendation: Re-fetch the proposal after mutation for the print event, or construct event data with updated values.

LOW

L-02: Phantom Proposal ID 0 in Proposal List

Location: proposal-ids variable initialization

Description: proposal-ids is initialized with (list u0), but proposal IDs start at 1. get-proposals always returns the default empty proposal for ID 0 as its first element, wasting one of the 100 list slots and confusing consumers.

Impact: Minor UX issue + wastes one proposal slot (exacerbating C-01). No security impact.

Recommendation: Initialize with an empty list.

LOW

L-03: Inconsistent Error Codes Between vote-for and vote-against

Location: vote-for vs vote-against

Description: For the same logical check (proposal is open), vote-for returns ERR-VOTING-CLOSED while vote-against returns ERR-NOT-AUTHORIZED. The assertion style also differs: direct bool check vs is-eq ... true.

Impact: Inconsistent error reporting makes debugging harder for integrators. No security impact.

Recommendation: Unify error codes and assertion patterns across both voting functions.

INFORMATIONAL

I-01: Pre-Clarity 4 — Uses as-contract Without Asset Allowances

Description: The contract uses as-contract in return-votes-to-member to transfer tokens back to voters. Clarity 4's as-contract? with explicit with-ft allowances for DIKO and stDIKO would restrict the contract's authority to only those tokens.

Recommendation: If redeploying, migrate to Clarity 4 and use as-contract? with (with-ft .arkadiko-token) (with-ft .stdiko-token).

INFORMATIONAL

I-02: Dynamic Participation Threshold

Description: The 5% participation threshold in end-proposal is calculated against the DIKO supply at proposal-end time (minus the init contract balance), not at creation time. Supply changes between creation and end shift the threshold.

Recommendation: Consider snapshotting the supply at proposal creation for deterministic thresholds.

INFORMATIONAL

I-03: No Minimum Start Delay for Proposals

Description: start-block-height only requires >= burn-block-height, allowing proposals to start voting immediately in the same block they're created. Combined with the DAO owner's 250-block window (M-01), this enables very rapid proposal lifecycles.

Recommendation: Consider requiring a minimum delay (e.g., 144 blocks ≈ 1 day) between creation and voting start for community review.

Positive Observations

  • Token escrow for voting: Tokens are transferred into the contract during voting, preventing flash-loan governance attacks.
  • Stake pool validation: Both propose and voting functions validate the stDIKO pool against the DAO registry, preventing malicious ratio manipulation via fake pools.
  • Dual emergency shutdown: Both DAO-level and governance-specific shutdown mechanisms provide defense-in-depth.
  • Token acceptance whitelist: is-token-accepted restricts voting to only DIKO and stDIKO, preventing rogue token contracts from being used.
  • Guardian access control: toggle-governance-shutdown is properly restricted to the DAO guardian address.
  • Proposal threshold: 0.25% of supply required to propose prevents trivial spam (though doesn't fully mitigate the 100-proposal cap issue).

Summary

The most critical finding is the 100-proposal hard cap (C-01) — a time bomb that will permanently brick governance once the limit is reached. Given Arkadiko has been live since 2021, the contract may be approaching this limit. The silent partial execution failure (H-01) is also serious, as it can leave the DAO in an inconsistent state after governance votes. The remaining findings are centralization concerns and quality-of-life issues. The contract's token escrow design effectively prevents flash-loan governance attacks, which is a strong positive.