Stacks 比特幣智慧合約完整開發指南:Clarity 語言實戰與比特幣整合

從 Stacks 2.0 區塊鏈架構到 Clarity 智能合約開發的完整實戰指南。涵蓋傳輸證明(PoX)共識機制、Clarity 語言設計哲學與安全特性、SIP010/SIP009 代幣標準實作、比特幣時間戳 Oracle、比特幣托管合約、去中心化交易所 AMM 合約開發,以及主網部署與安全審計最佳實踐。

Stacks 比特幣智慧合約完整開發指南:Clarity 語言實戰與比特幣整合

概述與學習目標

Stacks(前稱 Blockstack)是比特幣智慧合約領域中最具創新性的 Layer 2 解決方案之一。與 BitVM 和 RGB 的鏈下計算範式不同,Stacks 在比特幣區塊鏈上直接建立了一層智能合約網路,利用比特幣作為共識基礎和價值結算層。本指南旨在提供從 Stacks 區塊鏈基礎架構到 Clarity 智能合約開發的完整實戰教學,幫助開發者掌握比特幣原生智慧合約的開發技術。

本指南涵蓋的核心主題包括:Stacks 2.0 的共識機制與比特幣整合原理、Clarity 語言的設計哲學與安全特性、開發環境搭建與工具鏈配置、Clarity 智能合約的完整開發流程、Stacks 與比特幣的雙向橋接機制、DeFi 應用開發實例、以及主網部署與安全審計最佳實踐。

第一章:Stacks 區塊鏈架構與比特幣整合原理

1.1 Stacks 2.0 技術架構

Stacks 2.0 是 2021 年上線的主網版本,採用了完全重新設計的共識機制——傳輸證明(Proof of Transfer,PoX)。這一機制將 Stacks 區塊鏈與比特幣網路深度綁定,創建了業界首個可編程的比特幣 Layer 2 解決方案。

雙區塊鏈結構

Stacks 的核心設計是雙區塊鏈並行結構:

這種設計的關鍵特點是:比特幣礦工不需要運行 Stacks 節點,而 Stacks 礦工只需要關注比特幣區塊頭即可驗證共識狀態。這大大降低了參與共識的門檻。

區塊生成機制

Stacks 採用 Microblocks 機制來提升交易吞吐量:

  1. 主區塊(Stacks Block):每五分鐘生成一個主區塊,包含狀態承諾和智慧合約執行結果。主區塊頭的哈希被寫入比特幣區塊。
  2. 微區塊(Microblocks):在主區塊之間,礦工可以持續生成微區塊處理交易。微區塊不寫入比特幣區塊,但提供了即時的交易確認。
  3. 靈活性:微區塊是可選的,礦工可以選擇是否生成微區塊,這影響交易確認的時間和安全性。

1.2 傳輸證明(Proof of Transfer)共識機制

PoX 是 Stacks 的核心共識創新,它要求礦工在挖掘 Stacks 區塊時鎖定和「燃燒」比特幣(向指定的 Bitcoin 地址轉帳),作為參與共識的代價。作為回報,遵守規則的礦工將獲得 Stacks 代幣獎勵。

PoX 生命周期

1. 註冊階段
   礦工向比特幣網路提交 PoX 承諾交易
   承諾在下一個比特幣區塊開獎時燃燒比特幣
   承諾包含:礦工 STX 地址、燃燒目標列表

2. 選舉階段
   比特幣區塊被開採後
   所有承諾交易被收集
   根據偽隨機函數(結合比特幣區塊哈希和 VRF)選舉獲勝者
   獲勝礦工獲得優先出塊權

3. 區塊生成階段
   獲勝礦工生成 Stacks 區塊
   包含交易、智慧合約執行結果、狀態根
   區塊頭哈希寫入下一個比特幣區塊

4. 獎勵分配階段
   礦工燃燒的比特幣分配給 STX 持有者(通過鎖倉)
   礦工獲得新鑄造的 STX 代幣作為獎勵

PoX 的安全性分析

PoX 共識的安全性依賴比特幣網路的安全性和不可篡改性:

與工作量證明的比較

特性PoW(比特幣)PoX(Stacks)
能源消耗低(無額外計算)
礦工激勵新鑄造代幣燃燒比特幣 + 新代幣
安全假設誠實多數算力誠實多數比特幣算力
最終確定性概率性通過比特幣 commitment 確認

1.3 Stacks 與比特幣的雙向橋接

Stacks 的另一個核心創新是建立了 Stacks 與比特幣之間的雙向橋接機制。這使得 Stacks 智慧合約可以原生「讀取」比特幣區塊鏈狀態。

比特幣讀取(Bitcoin Read)

Stacks 智慧合約可以透過內建函數讀取比特幣區塊鏈的數據:

;; Clarity 內建的比特幣讀取函數示例

;; 讀取比特幣區塊哈希
(define-read-only (get-bitcoin-block-hash (block-height uint))
  (contract-call? .bitcoin-swap get-block-hash block-height))

;; 讀取比特幣交易詳情
(define-read-only (verify-bitcoin-tx (tx-id (buff 32)) (min-confirmations uint))
  (contract-call? .bitcoin-oracle verify-transaction tx-id min-confirmations))

;; 讀取比特幣地址餘額
(define-read-only (get-bitcoin-balance (btc-address (buff 34)))
  (contract-call? .bitcoin-swap get-balance btc-address))

這些函數允許智慧合約根據比特幣區塊鏈的狀態做出决策,例如:

比特幣寫入(Bitcoin Write)

Stacks 智慧合約也可以觸發比特幣區塊鏈上的操作:

;; 觸發比特幣交易的複雜操作
(define-public (initiate-bitcoin-swap 
    (recipient-btc-address (buff 34))
    (amount uint)
    (lock-duration uint))
  (let
    ((lock-height (+ block-height lock-duration))
     (swap-id (sha256 (+ recipient-btc-address (unwrap! (get-burn-block-info? id-header-hash) (err u100)))))
    
    ;; 儲存 swap 狀態
    (map-set swaps swap-id {
      recipient: recipient-btc-address,
      amount: amount,
      lock-height: lock-height,
      claimed: false
    })
    
    ;; 返回 swap ID,用戶需要將比特幣發送到指定的托管地址
    (ok swap-id)))

1.4 Stacking 共識獎勵機制

Stacks 的 Stacking(質押)機制是連接礦工獎勵和普通 STX 持有者的橋樑。通過鎖定 STX 代幣,持有者可以成為 PoX 共識的一部分,獲得礦工燃燒比特幣的一部分作為獎勵。

Stacking 工作流程

;; Stacking 合約的核心邏輯

(define-map participants
  (buff 20)  ;; 委託人 STX 地址
  {
    locked-amount: uint,
    unlock-burn-height: uint,
    reward-recipient: principal
  })

(define-public (stack 
    (amount uint)
    (unlock-burn-height uint)
    (reward-recipient principal))
  (let
    ((stacker tx-sender))
    
    ;; 驗證鎖定金額
    (asserts! (>= (stx-get-balance stacker) amount) (err u1))
    
    ;; 驗證解鎖時間有效性
    (asserts! (> unlock-burn-height burn-block-height) (err u2))
    
    ;; 鎖定 STX
    (try! (stx-transfer? amount stacker (as-contract tx-sender)))
    
    ;; 記錄參與者
    (map-set participants (Principal-to-buff20 stacker) {
      locked-amount: amount,
      unlock-burn-height: unlock-burn-height,
      reward-recipient: reward-recipient
    })
    
    (ok true)))

委託質押(Delegated Stacking)

Stacks 支持委託質押功能,允許小額 STX 持有者通過質押池參與共識:

;; 質押池委託合約
(define-public (delegate-stx 
    (amount uint)
    (pool-address principal)
    (unlock-burn-height uint))
  (let
    ((delegator tx-sender))
    
    ;; 轉移 STX 到質押池
    (try! (stx-transfer? amount delegator pool-address))
    
    ;; 質押池記錄委託關係
    (contract-call? pool-address register-delegation 
      delegator amount unlock-burn-height)))

第二章:Clarity 語言設計與安全特性

2.1 Clarity 的設計哲學

Clarity 是一種專為區塊鏈智能合約設計的編程語言,其核心設計哲學是「可預測性優先於表現力」。與 Solidity 等語言的「圖靈完整但不可判定」特性不同,Clarity 故意放棄了圖靈完整性,換取合约行为的可靜態分析和確定性執行。

為什麼放棄圖靈完整性?

以太坊 Solidity 的設計者選擇了圖靈完整的 EVM,這帶來了極大的表達能力,但同時也帶來了不可預測性——著名的 DAO 攻擊和無數的智能合約漏洞都與 EVM 的複雜性相關。

Clarity 的設計者意識到:區塊鏈智能合約並不需要圖靈完整來實現大多數實用功能。通過限制某些特性,可以實現:

  1. 靜態分析:合約的所有可能的執行路徑都可以在部署前被完整分析
  2. Gas 消耗可預測:每個操作的 Gas 成本是確定的,沒有動態跳轉帶來的不確定性
  3. 形式化驗證友好:沒有隱式控制流,更容易進行數學證明

Clarity 的語法特點

Clarity 採用 Lisp 風格的 S-表達式語法:

;; Clarity 代碼示例
(define-public (transfer (recipient principal) (amount uint))
  (let ((sender tx-sender))
    (asserts! (>= (stx-get-balance sender) amount) (err u1))
    (try! (stx-transfer? amount sender recipient))
    (ok true)))

這種語法的特點是:

2.2 Clarity 類型系統

Clarity 採用強類型系統,所有變量和函數參數都有明確的類型。

基本類型

;; 整數類型
(define-data-var counter int u0)

;; 無符號整數(推薦用於貨幣)
(define-data-var balance uint u100)

;; 緩衝區類型(固定長度位元組陣列)
(define-map messages ((key (buff 32))) ((message (buff 256))))

;; 主要類型
;; Principal:Stacks 地址類型
(define-public (get-contract-owner ()
  (ok contract-owner))

;; Boolean
(define-data-var is-active bool true)

;; Response:用於錯誤處理的類型
(define-public (safe-divide (a int) (b int))
  (if (is-eq b u0)
    (err "Division by zero")
    (ok (/ a b))))

複合類型

;; Tuple(元組/結構)
(define-map user-profiles ((id principal)) 
  ((name (string-ascii 50))
   (email (string-utf8 100))
   (age uint)))

;; List(列表)
(define-data-var order-ids (list 100 uint) (list))

;; Optional(可選值)
(define-read-only (get-user-name (user principal))
  (match (map-get? user-profiles user)
    profile (some (get name profile))
    none))

2.3 Clarity 的安全性保障

Clarity 通過多種機制保障智能合約的安全性:

防止重入攻擊

Solidity 的重入攻擊源於外部調用的非原子性執行。在 Clarity 中,所有函數調用都是原子的,不存在「執行中途將控制權交給外部合約」的情況:

;; 安全的轉帳模式
(define-public (withdraw (amount uint))
  (let ((sender tx-sender))
    
    ;; 1. 先更新狀態
    (asserts! (>= (stx-get-balance tx-sender) amount) (err u1))
    (var-set balances (+ (var-get balances) amount))
    
    ;; 2. 後執行轉帳
    (stx-transfer? amount tx-sender sender)))
    
;; 不可能在轉帳過程中被重入

避免整數溢出

Clarity 的整數運算在超出範圍時會回滾交易:

;; 安全的代幣轉帳
(define-public (transfer-tokens (to principal) (amount uint))
  (let ((from tx-sender)
        (from-balance (default-to u0 (map-get? token-balances from)))
        (to-balance (default-to u0 (map-get? token-balances to))))
    
    ;; 自動檢查溢出
    (asserts! (>= from-balance amount) (err u1))
    
    (map-set token-balances from (- from-balance amount))
    (map-set token-balances to (+ to-balance amount))
    (ok true)))

非可重入合約標記

Clarity 合約可以宣告為非可重入:

;; 合約開頭宣告
(implements standard-non-fungible-token)

;; 這自動防止了跨合約重入攻擊

第三章:Clarity 開發環境搭建

3.1 安裝 Clarinet 開發工具鏈

Clarinet 是 Stacks 官方提供的 Clarity 智能合約開發工具,提供了編譯器、測試框架、調試器和 REPL 環境。

Linux/macOS 安裝

# 使用 Homebrew 安裝(macOS)
brew install clarinet

# 使用 cargo 安裝(通用方法)
cargo install clarinet --locked

# 下載預編譯二進制
curl -L https://github.com/hirosystems/clarinet/releases/latest/download/clarinet-linux-x64-glibc.tar.gz -o clarinet.tar.gz
tar -xzf clarinet.tar.gz
sudo mv clarinet /usr/local/bin/

Windows 安裝

# 使用 Chocolatey
choco install clarinet

# 或者下載 MSI 安裝包
# https://github.com/hirosystems/clarinet/releases

驗證安裝

clarinet --version
# 應輸出:clarinet 1.x.x

3.2 創建第一個 Clarity 項目

# 創建新項目
clarinet new my-first-contract
cd my-first-contract

# 查看項目結構
ls -la

# 結構:
# contracts/          - 智能合約目錄
# tests/              - 測試文件目錄
# settings/            - 網路配置文件
# Clarinet.toml       - 項目配置

Clarinet.toml 配置

[project]
name = "my-first-contract"
authors = ["Your Name <your.email@example.com>"]
description = "My first Clarity smart contract"
stack_clarity_version = "Clarity2"

[contracts]
# 自動發現 contracts/ 目錄中的 .clar 文件

[repl]
# 調試配置
trace = true

3.3 Clarity 合約開發基礎

Hello World 合約

創建 contracts/hello-world.clar

;; hello-world.clar
;; 我的第一個 Clarity 智能合約

;; 定義一個簡單的 greeting 變量
(define-data-var greeting (string-ascii 20) "Hello, Stacks!")

;; 讀取 greeting
(define-read-only (get-greeting)
  (ok (var-get greeting)))

;; 設置 greeting(僅合約所有者)
(define-public (set-greeting (new-greeting (string-ascii 20)))
  (begin
    (asserts! (is-eq tx-sender .hello-world) (err u1))
    (var-set greeting new-greeting)
    (ok true)))

;; 簡單的 counter 合約
(define-data-var counter int u0)

(define-public (increment)
  (let ((current (var-get counter)))
    (var-set counter (+ current u1))
    (ok (var-get counter))))

(define-public (decrement)
  (let ((current (var-get counter)))
    (asserts! (> current u0) (err u1))
    (var-set counter (- current u1))
    (ok (var-get counter))))

(define-read-only (get-counter)
  (ok (var-get counter)))

啟動 REPL 測試

clarinet console

# 進入 REPL 環境
# 可以直接執行 Clarity 代碼

3.4 單元測試框架

Clarinet 內置了測試框架,基於 JavaScript/TypeScript:

創建 tests/hello-world_test.ts

import { Clarinet, Tx, Chain, Account, types } from '@hirosystems/clarinet-sdk';

Clarinet.test({
  name: "hello-world: get-greeting returns stored greeting",
  fn(chain: Chain, accounts: Map<string, Account>) {
    const deployer = accounts.get("deployer")!;
    
    // 調用合約
    const block = chain.callReadOnlyFn(
      "hello-world",
      "get-greeting",
      [],
      deployer.address
    );
    
    // 驗證結果
    block.result.expectOk().expectUtf8("Hello, Stacks!");
  }
});

Clarinet.test({
  name: "hello-world: set-greeting only by owner",
  fn(chain: Chain, accounts: Map<string, Account>) {
    const deployer = accounts.get("deployer")!;
    const user = accounts.get("wallet_1")!;
    
    // 用戶嘗試設置,應該失敗
    const block1 = chain.tx(
      `(contract-call? .hello-world set-greeting "Hi!")`,
      user.address
    );
    block1.expectErr().expectUint(1);
    
    // 所有者設置,應該成功
    const block2 = chain.tx(
      `(contract-call? .hello-world set-greeting "Hi, Stacks!")`,
      deployer.address
    );
    block2.expectOk();
  }
});

Clarinet.test({
  name: "counter: increment and decrement",
  fn(chain: Chain, accounts: Map<string, Account>) {
    const deployer = accounts.get("deployer")!;
    
    // 初始值為 0
    let block = chain.callReadOnlyFn(
      "counter", "get-counter", [], deployer.address
    );
    block.result.expectOk().expectInt(0);
    
    // 遞增
    chain.tx(
      `(contract-call? .counter increment)`, deployer.address
    ).expectOk();
    
    // 驗證值為 1
    block = chain.callReadOnlyFn(
      "counter", "get-counter", [], deployer.address
    );
    block.result.expectOk().expectInt(1);
    
    // 遞減
    chain.tx(
      `(contract-call? .counter decrement)`, deployer.address
    ).expectOk();
    
    // 驗證值回到 0
    block = chain.callReadOnlyFn(
      "counter", "get-counter", [], deployer.address
    );
    block.result.expectOk().expectInt(0);
  }
});

運行測試

clarinet test

# 輸出示例:
# hello-world_test.ts
#   ✓ hello-world: get-greeting returns stored greeting
#   ✓ hello-world: set-greeting only by owner
#   ✓ counter: increment and decrement
#
# 3 passing (50ms)

第四章:Clarity 代幣合約開發實戰

4.1 SIP010 代幣標準

SIP010 是 Stacks 的代幣標準,定義了 fungible token(可替代代幣)的接口。遵循此標準的代幣可以與錢包、去中心化交易所和其他合約無縫交互。

SIP010 接口定義

;; sip010-trait.clar
;; 定義標準代幣接口

(define-trait sip010-token
  (
    ;; 轉帳代幣
    (transfer (uint principal principal (optional (buff 34))) (response bool uint))
    
    ;; 代幣名稱
    (get-name () (response (string-ascii 32) uint))
    
    ;; 代幣符號
    (get-symbol () (response (string-ascii 10) uint))
    
    ;; 精度(小數位數)
    (get-decimals () (response uint uint))
    
    ;; 總供應量
    (get-total-supply () (response uint uint))
    
    ;; 查詢餘額
    (get-balance (principal) (response uint uint))
    
    ;; 代幣 URI(用於元數據)
    (get-token-uri () (response (optional (string-utf8 256)) uint))
  ))

完整代幣合約實現

創建 contracts/my-token.clar

;; my-token.clar
;; SIP010 代幣合約實現

;; 引入標準接口
(use-trait sip010-token 'STTXWD3RCFYBHQZN5FKTV3JSKQPYYCDB5J63JX4G.sip010-trait.sip010-token)

;; 代幣元數據
(define-fungible-token my-token u100000000000)  ;; 總供應量:1000 MYT(精度 8)

;; 合約所有者
(define-data-var contract-owner principal tx-sender)

;; 精度:8 位小數
(define-constant decimals u8)

;; 轉帳函數(SIP010 標準)
(define-public (transfer 
    (amount uint)
    (sender principal)
    (recipient principal)
    (memo (optional (buff 34))))
  (begin
    (asserts! (is-eq tx-sender sender) (err u1))
    (ft-transfer? my-token amount sender recipient)
    (print memo)
    (ok true)))

;; 查詢函數
(define-read-only (get-name)
  (ok "My Token"))

(define-read-only (get-symbol)
  (ok "MYT"))

(define-read-only (get-decimals)
  (ok decimals))

(define-read-only (get-total-supply)
  (ok (ft-get-supply my-token)))

(define-read-only (get-balance (who principal))
  (ok (ft-get-balance my-token who)))

(define-read-only (get-token-uri)
  (ok (some u"https://example.com/mytoken.json")))

;; 管理函數
(define-public (mint (amount uint) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) (err u2))
    (ft-mint? my-token amount recipient)))

(define-public (burn (amount uint) (owner principal))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) (err u2))
    (ft-burn? my-token amount owner)))

;; 質押功能示例
(define-map stakes 
  ((holder principal))
  ((amount uint)
   (reward-per-share uint)))

(define-data-var reward-pool uint u0)

(define-public (stake (amount uint))
  (let ((stacker tx-sender))
    (asserts! (>= (ft-get-balance my-token stacker) amount) (err u3))
    
    ;; 領取之前的獎勵
    (claim-reward)
    
    ;; 質押代幣(轉移到合約)
    (try! (ft-transfer? amount stacker tx-sender))
    
    ;; 更新質押狀態
    (map-set stakes (tuple (holder stacker)))
    (ok true)))

(define-read-only (get-stake (who principal))
  (ok (default-to {amount: u0, reward-per-share: u0} 
    (map-get? stakes (tuple (holder who))))))

;; 領取獎勵
(define-public (claim-reward)
  (let ((stacker tx-sender)
        (stake-info (get-stake stacker))
        (staked-amount (get amount stake-info))
        (accumulated (get reward-per-share stake-info))
        (current (var-get reward-pool))
        (pending-rewards (if (> staked-amount u0)
          (* staked-amount (- current accumulated))
          u0)))
    
    (if (> pending-rewards u0)
      (begin
        (try! (stx-transfer? pending-rewards tx-sender stacker))
        (map-set stakes (tuple (holder stacker)) {
          amount: staked-amount,
          reward-per-share: current
        }))
      true)
    (ok true)))

4.2 SIP009 NFT 標準

SIP009 定義了 Stacks 的非同質化代幣(NFT)標準:

;; my-nft.clar
;; SIP009 NFT 合約實現

(use-trait nft-trait 'STNXW2KJQNPKZ8XWDXHSD4Z10V31XSHCBQRK7Q.sip009-trait.nft-trait)

;; NFT 定義
(define-non-fungible-token my-nft (string-ascii 50))

;; NFT 元數據
(define-map token-meta
  ((id uint))
  ((name (string-ascii 100))
   (description (string-utf8 500))
   (image (string-utf8 256))))

;; 合約所有者
(define-data-var token-id-nonce uint u0)

(define-data-var contract-owner principal tx-sender)

;; mint NFT
(define-public (mint (name (string-ascii 100)) 
                  (description (string-utf8 500))
                  (image (string-utf8 256)))
  (let ((new-id (+ (var-get token-id-nonce) u1)))
    
    ;; mint NFT
    (try! (nft-mint? my-nft new-id tx-sender))
    
    ;; 設置元數據
    (map-set token-meta (tuple (id new-id))) {
      name: name,
      description: description,
      image: image
    })
    
    ;; 更新 nonce
    (var-set token-id-nonce new-id)
    
    (ok new-id)))

;; 轉帳 NFT
(define-public (transfer (id uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender sender) (err u1))
    (nft-transfer? my-nft id sender recipient)))

;; 查詢所有權
(define-read-only (get-owner (id uint))
  (ok (nft-get-owner? my-nft id)))

;; 查詢元數據 URI
(define-read-only (get-token-uri (id uint))
  (ok (some 
    (concat 
      (concat u"https://api.example.com/nft/" (uint-to-ascii id))
      u".json"))))

;; 燒毀 NFT
(define-public (burn (id uint) (owner principal))
  (begin
    (asserts! (is-eq tx-sender owner) (err u1))
    (nft-burn? my-nft id owner)))

4.3 去中心化交易所合約

以下是一個簡化的自動做市商(AMM)合約示例:

;; amm-swap.clar
;; 簡化版 AMM 合約

(use-trait sip010-token 'STTXWD3RCFYBHQZN5FKTV3JSKQPYYCDB5J63JX4G.sip010-trait.sip010-token)

;; 流動性池狀態
(define-map pools
  ((token-x principal)
   (token-y principal))
  ((reserve-x uint)
   (reserve-y uint)
   (liquidity-token-supply uint)
   (k uint)))  ;; 常數積 k = x * y

;; 流動性提供
(define-public (add-liquidity 
    (token-x-trait <sip010-token>)
    (token-y-trait <sip010-token>)
    (amount-x uint)
    (amount-y uint))
  (let
    ((sender tx-sender)
     (pool-key (tuple (token-x (contract-of token-x-trait))
                      (token-y (contract-of token-y-trait))))
     (pool (get-pool pool-key))
     (reserve-x (get reserve-x pool))
     (reserve-y (get reserve-y pool))
     (liquidity-supply (get liquidity-token-supply pool))
     
     ;; 計算新流動性代幣數量
     (new-liquidity (if (is-eq liquidity-supply u0)
       (sqrt (* amount-x amount-y))
       (min 
         (/ (* amount-x liquidity-supply) reserve-x)
         (/ (* amount-y liquidity-supply) reserve-y))))
    
    ;; 轉移代幣
    (try! (contract-call? token-x-trait transfer amount-x sender tx-sender none))
    (try! (contract-call? token-y-trait transfer amount-y sender tx-sender none))
    
    ;; 更新池狀態
    (map-set pools pool-key {
      reserve-x: (+ reserve-x amount-x),
      reserve-y: (+ reserve-y amount-y),
      liquidity-token-supply: (+ liquidity-supply new-liquidity),
      k: (* (+ reserve-x amount-x) (+ reserve-y amount-y))
    })
    
    (ok new-liquidity)))

;; 交換
(define-public (swap 
    (token-in-trait <sip010-token>)
    (token-out-trait <sip010-token>)
    (amount-in uint)
    (min-amount-out uint))
  (let
    ((sender tx-sender)
     (token-in (contract-of token-in-trait))
     (token-out (contract-of token-out-trait))
     (pool-key (tuple (token-x token-in) (token-y token-out)))
     (pool (get-pool pool-key))
     (reserve-in (get reserve-x pool))
     (reserve-out (get reserve-y pool))
     (k (get k pool))
     
     ;; 計算輸出金額(恒定積公式)
     (amount-in-with-fee (* amount-in u997))
     (numerator (* amount-in-with-fee reserve-out))
     (denominator (+ (* reserve-in u1000) amount-in-with-fee))
     (amount-out (/ numerator denominator))
     
     ;; 驗證最低輸出
     (asserts! (>= amount-out min-amount-out) (err u1))
     
     ;; 轉移代幣
     (try! (contract-call? token-in-trait transfer amount-in sender tx-sender none))
     (try! (contract-call? token-out-trait transfer amount-out tx-sender sender none))
    
    (ok amount-out)))

;; 查詢池狀態
(define-read-only (get-pool (pool-key {token-x principal, token-y principal}))
  (ok (default-to 
    {reserve-x: u0, reserve-y: u0, liquidity-token-supply: u0, k: u0}
    (map-get? pools pool-key))))

;; 輔助函數
(define-private (sqrt (n uint))
  (fold (lambda (x i)
    (/ (+ x (/ n x)) u2))
    (list u0 u18446744073709551615 u4294967296 u65536 u256 u16 u4 u2 u1)
    n))

(define-private (min (a uint) (b uint))
  (if (< a b) a b))

第五章:比特幣整合進階應用

5.1 比特幣時間戳Oracle

利用 Stacks 讀取比特幣區塊時間戳的特性,可以創建可信的時間戳 Oracle:

;; bitcoin-timestamp.clar
;; 比特幣時間戳 Oracle

(define-map timestamps
  ((data-hash (buff 32)))
  ((timestamp uint)
   (bitcoin-block-height uint)
   (provider principal)))

;; 記錄數據的時間戳
(define-public (record-timestamp (data-hash (buff 32)))
  (let
    ((provider tx-sender)
     (current-height burn-block-height)
     (current-hash (unwrap! (get-burn-block-info? id-header-hash) (err u100))))
    
    ;; 檢查是否已記錄
    (asserts! (is-none (map-get? timestamps (tuple (data-hash data-hash)))) (err u2))
    
    ;; 記錄時間戳
    (map-set timestamps (tuple (data-hash data-hash))) {
      timestamp: burn-block-time,
      bitcoin-block-height: (get-burn-block-info? burn-block-header-hash),
      provider: provider
    })
    
    (ok {
      height: current-height,
      time: burn-block-time
    })))

;; 驗證數據是否在某個時間點之前存在
(define-read-only (verify-existence 
    (data-hash (buff 32))
    (max-height uint))
  (match (map-get? timestamps (tuple (data-hash data-hash)))
    record
      (if (<= (get bitcoin-block-height record) max-height)
        (ok (get timestamp record))
        (err u1))
    (err u2)))

5.2 比特幣托管合約

實現比特幣與 Stacks 代幣的原子化交換:

;; btc-stx-swap.clar
;; 比特幣-STX 原子化交換合約

(use-trait sip010-token 'STTXWD3RCFYBHQZN5FKTV3JSKQPYYCDB5J63JX4G.sip010-token)

(define-map swaps
  ((swap-id (buff 32)))
  ((maker principal)
   (stx-amount uint)
   (btc-address (buff 34))
   (min-confirmations uint)
   (btc-received bool)
   (created-at uint)))

;; 創建 swap(Maker 鎖定 STX)
(define-public (create-swap 
    (stx-amount uint)
    (btc-address (buff 34))
    (min-confirmations uint))
  (let
    ((maker tx-sender)
     (swap-id (sha256 (concat btc-address (unwrap! (get-burn-block-info? id-header-hash) (err u100))))))
    
    ;; 轉移 STX 到合約
    (try! (stx-transfer? stx-amount maker tx-sender))
    
    ;; 記錄 swap
    (map-set swaps (tuple (swap-id swap-id))) {
      maker: maker,
      stx-amount: stx-amount,
      btc-address: btc-address,
      min-confirmations: min-confirmations,
      btc-received: false,
      created-at: burn-block-height
    })
    
    (ok swap-id)))

;; 標記比特幣已收到(需要比特幣 Oracle 驗證)
(define-public (confirm-btc-received (swap-id (buff 32)))
  (let
    ((swap (unwrap! (map-get? swaps (tuple (swap-id swap-id))) (err u1)))
    (maker (get maker swap))
    (stx-amount (get stx-amount swap)))
    
    ;; 這裡需要比特幣 Oracle 確認
    ;; 實際應用中應該由 Oracle 觸發
    (asserts! (is-eq tx-sender .bitcoin-oracle) (err u2))
    
    ;; 釋放 STX 給 Maker
    (try! (as-contract (stx-transfer? stx-amount tx-sender maker)))
    
    (ok true)))

;; 取消 swap(超時後 Maker 可取消)
(define-public (cancel-swap (swap-id (buff 32)))
  (let
    ((swap (unwrap! (map-get? swaps (tuple (swap-id swap-id))) (err u1)))
    (maker (get maker swap))
    (stx-amount (get stx-amount swap))
    (created-at (get created-at swap)))
    
    ;; 驗證發起者和超時
    (asserts! (is-eq tx-sender maker) (err u3))
    (asserts! (>= burn-block-height (+ created-at u144)) (err u4))  ;; 24小時超時
    
    ;; 退還 STX
    (as-contract (stx-transfer? stx-amount tx-sender maker))))

5.3 比特幣預言機餵價合約

創建比特幣計价的價格預言機:

;; btc-price-oracle.clar
;; 比特幣預言機合約

(define-map price-data
  ((asset (string-ascii 10)))
  ((price uint)           ;; 每 BTC 價格(以 asset 計)
   (last-update uint)      ;; 最後更新區塊高度
   (validity-window uint))) ;; 有效期(區塊數)

(define-map oracle-providers
  ((provider principal))
  ((weight uint)
   (active bool)))

;; 設置價格
(define-public (update-price 
    (asset (string-ascii 10))
    (price uint)
    (validity-blocks uint))
  (let
    ((provider tx-sender)
     (provider-info (unwrap! (map-get? oracle-providers (tuple (provider provider))) (err u1))))
    
    (asserts! (get active provider-info) (err u2))
    
    (map-set price-data (tuple (asset asset))) {
      price: price,
      last-update: burn-block-height,
      validity-window: validity-blocks
    })
    
    (ok true)))

;; 讀取價格(帶有效性檢查)
(define-read-only (get-price (asset (string-ascii 10)))
  (match (map-get? price-data (tuple (asset asset)))
    data
      (if (< (+ (get last-update data) (get validity-window data)) burn-block-height)
        (err u1)  ;; 價格已過期
        (ok (get price data)))
    (err u2)))

;; 加權平均價格
(define-read-only (get-weighted-price (asset (string-ascii 10)))
  (let
    ((prices (map-get? price-data (tuple (asset asset)))))
    (ok (get price (default-to {price: u0, last-update: u0, validity-window: u0} prices)))))

第六章:主網部署與安全審計

6.1 部署流程

本地測試網部署

# 啟動本地 devnet
clarinet devnet start

# 部署合約
clarinet deploy --manifest Clarinet.toml

測試網部署

# 配置測試網錢包
clarinet wallet create --testnet
# 保存輸出的助記詞

# 部署到測試網
clarinet deploy --testnet --manifest Clarinet.toml --wallet "mnemonic..."

主網部署

# 配置主網錢包(注意安全)
clarinet wallet create --mainnet
# 強烈建議使用硬體錢包

# 部署到主網
clarinet deploy --mainnet --manifest Clarinet.toml --wallet "mnemonic..."

# 確認部署交易
# 記錄合約地址

6.2 安全審計要點

常見漏洞及防護

  1. 權限控制漏洞
;; 錯誤示例
(define-public (admin-only (new-value uint))
  (var-set state new-value))  ;; 沒有權限檢查!

;; 正確示例
(define-public (admin-only (new-value uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err u1))
    (var-set state new-value)
    (ok true)))
  1. 重入風險(Clarity 中風險較低,但仍需注意)
;; 安全模式:先更新狀態,後執行外部調用
(define-public (withdraw (amount uint))
  (let ((sender tx-sender)
        (balance (stx-get-balance sender)))
    ;; 1. 狀態檢查
    (asserts! (>= balance amount) (err u1))
    ;; 2. 狀態更新(這裡餘額減去,但實際餘額未變)
    (var-set balances (- balance amount))
    ;; 3. 轉帳(安全,因為餘額已減去)
    (stx-transfer? amount tx-sender sender)))
  1. 算術溢出
;; Clarity 會自動在溢出時回滾
;; 但應注意使用 uMAX (18446744073709551615) 作為初始值檢查

6.3 形式化驗證工具

Clarinet 支持與 CertiK 等形式化驗證工具集成:

# 安裝 CertiK 插件
npm install @certik/clarity-analyzer

# 運行靜態分析
clarinet analyze

# 查看潛在漏洞報告

結論

Stacks 為比特幣智慧合約開發提供了一條獨特的技術路徑。通過 Clarity 語言的安全設計、PoX 共識與比特幣的深度整合,以及成熟的開發工具鏈,開發者可以在比特幣區塊鏈的安全基礎上構建功能豐富的去中心化應用。

從簡單的代幣合約到複雜的 DeFi 協議,Clarity 的表現力足以支撐大多數實用場景。其可靜態分析的特性和明確的錯誤處理機制,大幅降低了智能合約的安全風險。

隨著比特幣 Layer 2 生態的持續發展,Stacks 在比特幣原生智慧合約領域的地位將進一步鞏固。掌握 Clarity 開發技術,將使開發者能夠充分把握比特幣網路下一階段創新的機遇。

本文包含

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!