Clarity 智慧合約語言
理解 Stacks 的 Clarity 語言設計理念與安全性。
Clarity 智慧合約語言:完整開發指南
理解 Stacks 的 Clarity 語言設計理念與安全性。Clarity 是一種圖靈不完備的智慧合約語言,專為比特幣生態系統設計,採用可判定的執行模型,確保合約行為的可預測性和可驗證性。
Clarity 的可判定特性非常適合高審計需求合約。它不是追求圖靈完備,而是追求可預測、可驗證、可審計。這種設計選擇使其成為比特幣上構建金融應用的理想選擇。
Clarity 語言核心特性
為何選擇圖靈不完備
圖靈不完備的設計優勢:
1. 可判定性
- 執行前可確定最大 Gas 消耗
- 避免無限迴圈攻擊
- 合約成本可精確預測
2. 安全性
- 無法創建自毀合約
- 無法動態調用任意合約
- 減少攻擊向量
3. 可審計性
- 所有函數行為可在部署前分析
- 無隱藏執行路徑
- 形式化驗證更容易
資料類型系統
Clarity 採用強類型系統,所有變數必須明確宣告類型:
Clarity 基本類型:
────────────────────────────
類型 說明 範例
────────────────────────────
int 有符號整數 42
uint 無符號整數 123
bool 布林值 true
buff 位元組 0x01
string 字串 "hello"
principal 主體位址 ST1...
(list t) 清單 (list 1 2 3)
(tuple t) 結構體 {a: 1, b: 2}
(optional t) 可選值 (some 42)
(response t) 回應類型 (ok 42)
(err "error")
────────────────────────────
完整合約範例:簡單代幣合約
以下是一個完整的 RGB20 風格代幣合約,展示 Clarity 的實際開發:
;; Simple Token Contract
;; 相當於 ERC-20 的 Clarity 實現
;; ============== 常量定義 ==============
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_OWNER_ONLY (err u100))
(define-constant ERR_INSUFFICIENT_BALANCE (err u101))
(define-constant ERR_TRANSFER_FAILED (err u102))
(define-constant ERR_NOT_AUTHORIZED (err u103))
(define-constant MAX_SUPPLY u21000000)
(define-constant DECIMALS u8)
;; ============== 資料儲存 ==============
(define-map balances principal uint)
(define-map allowances (tuple (owner principal) (spender principal)) uint)
(define-data-var total-supply uint u0)
(define-data-var token-name (string-ascii 32) "BitcoinFi Token")
(define-data-var token-symbol (string-ascii 10) "BFT")
;; ============== 讀取函數 ==============
(define-read-only (get-name)
(ok (var-get token-name)))
(define-read-only (get-symbol)
(ok (var-get token-symbol)))
(define-read-only (get-decimals)
(ok DECIMALS))
(define-read-only (get-total-supply)
(ok (var-get total-supply)))
(define-read-only (get-balance (account principal))
(default-to u0 (map-get? balances account)))
(define-read-only (get-allowance (owner principal) (spender principal))
(default-to u0 (map-get? allowances {owner: owner, spender: spender})))
;; ============== 內部函數 ==============
(define-private (transfer-internal (amount uint) (from principal) (to principal))
(let (
(from-balance (get-balance from))
)
(asserts! (>= from-balance amount) ERR_INSUFFICIENT_BALANCE)
(map-set balances from (- from-balance amount))
(map-set balances to (+ (get-balance to) amount))
(ok true)
)
))
;; ============== 公開函數 ==============
;; 鑄造代幣(僅合約擁有者)
(define-public (mint (amount uint) (to principal))
(begin
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
(var-set total-supply (+ (var-get total-supply) amount))
(map-set balances to (+ (get-balance to) amount))
(ok amount)
)
)
;; 銷毀代幣
(define-public (burn (amount uint))
(let (
(sender-balance (get-balance tx-sender))
)
(asserts! (>= sender-balance amount) ERR_INSUFFICIENT_BALANCE)
(var-set total-supply (- (var-get total-supply) amount))
(map-set balances tx-sender (- sender-balance amount))
(ok amount)
)
)
;; 轉帳
(define-public (transfer (amount uint) (to principal))
(transfer-internal amount tx-sender to)
)
;; 授權轉帳
(define-public (approve (spender principal) (amount uint))
(begin
(map-set allowances {owner: tx-sender, spender: spender} amount)
(ok amount)
)
)
;; 從授權額度轉帳
(define-public (transfer-from (amount uint) (from principal) (to principal))
(let (
(allowance-amount (get-allowance from tx-sender))
)
(asserts! (>= allowance-amount amount) ERR_NOT_AUTHORIZED)
(try! (transfer-internal amount from to))
(map-set allowances {owner: from, spender: tx-sender}
(- allowance-amount amount))
(ok amount)
)
)
;; ============== 事件(可選) ==============
;; Clarity 不支持事件,但可通過鑄造 NFT 實現類似功能
多重簽名合約範例
展示如何使用 Clarity 實現 2-of-3 多重簽名:
;; Multi-Signature Wallet Contract
(define-constant ERR_NOT_SIGNER (err u100))
(define-constant ERR_ALREADY_SIGNED (err u101))
(define-constant ERR_INSUFFICIENT_SIGNATURES (err u102))
(define-constant ERR_INVALID_THRESHOLD (err u103))
;; 閾值:需要多少個簽名
(define-data-var threshold uint u2)
;; 授權者清單
(define-list signers-list (list
#principal with address 1
#principal with address 2
#principal with address 3
))
;; 待處理的交易
(define-map pending-txs
uint
{
recipient: principal,
amount: uint,
executed: bool,
signer-count: uint
}
)
;; 交易簽名記錄
(define-map tx-signatures
(tuple (tx-id uint) (signer principal))
bool
)
;; 交易計數器
(define-data-var tx-counter uint u0)
;; 內部:檢查是否為授權者
(define-private (is-signer (principal principal))
(is-some (index-of signers-list principal))
)
;; 建立交易
(define-public (create-transaction (recipient principal) (amount uint))
(let (
(tx-id (var-get tx-counter))
)
(asserts! (is-signer tx-sender) ERR_NOT_SIGNER)
(map-set pending-txs tx-id {
recipient: recipient,
amount: amount,
executed: false,
signer-count: u0
})
(var-set tx-counter (+ (var-get tx-counter) u1))
(ok tx-id)
)
)
;; 簽名交易
(define-public (sign-transaction (tx-id uint))
(let (
(tx (unwrap! (map-get? pending-txs tx-id) ERR_INVALID_THRESHOLD))
)
(asserts! (is-signer tx-sender) ERR_NOT_SIGNER)
(asserts! (not (is-some (map-get? tx-signatures {tx-id: tx-id, signer: tx-sender}))) ERR_ALREADY_SIGNED)
(map-set tx-signatures {tx-id: tx-id, signer: tx-sender} true)
(map-set pending-txs tx-id (merge tx {
signer-count: (+ (get signer-count tx) u1)
}))
(ok (get signer-count tx))
)
)
;; 執行交易
(define-public (execute-transaction (tx-id uint))
(let (
(tx (unwrap! (map-get? pending-txs tx-id) ERR_INVALID_THRESHOLD))
(signer-count (get signer-count tx))
)
(asserts! (>= signer-count (var-get threshold)) ERR_INSUFFICIENT_SIGNATURES)
(asserts! (not (get executed tx)) ERR_INVALID_THRESHOLD)
(map-set pending-txs tx-id (merge tx {executed: true}))
(ok true)
)
)
可重現的實作流程
測試驅動開發
Clarity 支援合約內測試,可在部署前驗證邏輯正確性:
;; ============== 合約內測試 ==============
(define-public (test-transfer)
(let (
;; 設置測試環境
(user1 'ST1SJ3DVE5Q0P54RX3D5T7XY4X7G9K5Z5T4X7G9K5)
(user2 'ST2CY5V39HJ8K8D7X5Z3T7XY4X7G9K5Z5T4X7G9K6)
(initial-balance u1000)
(transfer-amount u500)
)
;; 初始化餘額
(map-set balances user1 initial-balance)
(map-set balances user2 u0)
;; 執行轉帳
(try! (transfer transfer-amount user2))
;; 驗證結果
(asserts! (= (get-balance user1) (- initial-balance transfer-amount))
(err u999))
(asserts! (= (get-balance user2) transfer-amount)
(err u999))
(ok "Test passed")
)
)
本地測試框架
使用 clarinet 進行本地測試:
# 安裝 clarinet
cargo install clarinet
# 初始化專案
clarinet new my-token
cd my-token
# 編輯合約
# contracts/token.clar
# 單元測試
clarinet test
# 互動式控制台
clarinet console
# 檢查合約成本
clarinet check
// test/_token.test.ts
import { Clarinet, Tx, Chain, Account, types } from '@hirosystems/clarinet-sdk';
Clarinet.test({
name: "transfer reduces sender balance",
fn(chain: Chain, accounts: Map<string, Account>) {
const deployer = accounts.get('deployer')!;
const wallet1 = accounts.get('wallet_1')!;
const wallet2 = accounts.get('wallet_2')!;
// 鑄造代幣給 wallet1
let block = chain.mineBlock([
Tx.contractCall('token', 'mint', [
types.uint(1000),
types.principal(wallet1.address)
], deployer.address)
]);
// 執行轉帳
block = chain.mineBlock([
Tx.contractCall('token', 'transfer', [
types.uint(500),
types.principal(wallet2.address)
], wallet1.address)
]);
// 驗證餘額
const wallet1Balance = chain.callReadOnlyFn(
'token', 'get-balance', [types.principal(wallet1.address)]
);
const wallet2Balance = chain.callReadOnlyFn(
'token', 'get-balance', [types.principal(wallet2.address)]
);
expect(wallet1Balance.result).toBe(types.uint(500));
expect(wallet2Balance.result).toBe(types.uint(500));
}
});
Clarity 與比特幣的整合
STX 轉帳與比特幣確認
Stacks 區塊鏈每個區塊都會等待比特幣確認,這提供了比特幣級的安全性:
;; ============== 比特幣確認整合 ==============
;; 讀取當前比特幣區塊高度
(define-public (get-btc-block-height)
(ok block-height)
)
;; 根據比特幣確認數設定時間鎖
(define-public (create-timelocked-transfer
(recipient principal)
(amount uint)
(btc-confirmations uint))
(let (
(unlock-height (+ block-height (* btc-confirmations u6)))
)
;; 儲存時間鎖資訊
(map-set time-locks recipient {
amount: amount,
unlock-height: unlock-height,
claimed: false
})
(ok unlock-height)
)
)
;; 領取時間鎖定的資金
(define-public (claim-timelocked ()
(let (
(lock (unwrap! (map-get? time-locks tx-sender) (err u404)))
)
(asserts! (>= block-height (get unlock-height lock))
(err u100))
(asserts! (not (get claimed lock))
(err u101))
(map-set time-locks tx-sender (merge lock {claimed: true}))
(ok (get amount lock))
)
)
SIP-010 代幣標準
Stacks 的 FT 標準介面:
;; SIP-010 Trait Definition
(define-trait sip010-token (
;; Transfer tokens from tx-sender to a recipient
(transfer (uint principal principal) (response bool uint))
;; Get the token balance of an owner
(get-balance (principal) (response uint uint))
;; Get the number of decimals
(get-decimals () (response uint uint))
;; Get the token symbol
(get-symbol () (response (string-ascii 10) uint))
;; Get the token name
(get-name () (response (string-ascii 32) uint))
;; Get the total supply
(get-total-supply () (response uint uint))
;; Get the token URI
(get-token-uri () (response (optional (string-utf8 256)) uint))
))
部署與成本管理
Clarity 成本模型
Clarity 執行需要支付成本,主要來自以下操作:
成本因素分析:
─────────────────────────────
操作類型 成本單位
─────────────────────────────
儲存寫入 1 per byte
函數調用 1 per call
算術運算 1-10 per op
清單操作 O(n) per op
MAP 操作 O(log n)
─────────────────────────────
成本優化策略:
1. 批量操作
❌ 不推薦:
(map-set map k1 v1)
(map-set map k2 v2)
(map-set map k3 v3)
✅ 推薦:
(define-map map ...)
(map-set map {k1: v1, k2: v2, k3: v3})
2. 減少儲存
- 使用 integer 而非 map
- 合併相關資料
- 定期清理過期資料
3. 計算優化
- 避免不必要的迴圈
- 使用 early return
- 快取重複計算
升級模式
Clarity 合約不可變,但可以通過代理模式實現升級:
;; 代理升級模式
;; 儲存當前實作位址
(define-data-var implementation principal .implementation-v1)
;; 委託調用
(define-public (forward (fn-name (string-oriascii 256)) (args (list 2000 (buff 1))))
(contract-call? (var-get implementation) fn-name args)
)
;; 升級(僅擁有者)
(define-public (upgrade (new-implementation principal))
(begin
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
(var-set implementation new-implementation)
(ok true)
)
)
例外情境:測試一定要覆蓋
- 權限分支遺漏
- 成本估算錯誤
- 升級未回歸
設計取捨:成本、風險與維運的平衡點
Stacks 類題目需要把鏈上共識、跨鏈資產與應用風控一起評估。任何單點優化,若破壞整體一致性,都會在壓力情境中反噬。
如果你要把這篇主題直接導入現有服務,建議先做小流量灰度:
- 先讓新流程與舊流程並行一段時間,確認輸出一致性
- 指標達標後再放大流量,避免一次性切換造成不可逆影響
- 保留回滾開關,確保故障時能在分鐘級恢復
把 EVM 的動態寫法直接搬到 Clarity,通常會在語義與成本上踩雷。
重點回收
Clarity 的價值在可判定性,適合高審計需求場景。圖靈不完備的設計確保了:
- 執行成本可預測
- 無無限迴圈風險
- 形式化驗證可行
如果你要把本文主題用在生產環境,建議先完成「可重現測試、監控告警、失敗回滾」三件事,再擴大資金與流量。
相關文章:
相關文章
- 什麼是 Stacks 區塊鏈? — 理解比特幣 L2 智慧合約平台的設計與使命。
- Stacks 中本聰升級 — Stacks 中本聰升級介紹
- Stacks Stacking 詳解 — Stacks Stacking 機制與收益
- STX 與 sBTC 詳解 — 理解 Stacks 生態的代幣經濟與比特幣錨定。
- Stacks DeFi 生態 — 探索 Stacks 上的去中心化金融應用。
延伸閱讀與來源
- Clarity 文档 Clarity 語言文檔
- Clarity Book Clarity 開發書籍
這篇文章對您有幫助嗎?
請告訴我們如何改進:
0 人覺得有帮助
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!