Cardano智能合约速成:30分钟解锁Plutus开发新姿势!

学习 2025-03-08 53

艾达币(Cardano)合约开发教程

前言

本文旨在提供一个关于在Cardano区块链上进行智能合约开发的详细且专业的教程。我们将深入探讨核心概念,例如 Plutus Core, Extended UTXO (EUTXO) 模型以及脚本验证,并提供一些实用的、可执行的例子,旨在帮助初学者和有经验的开发者快速入门。为了顺利进行后续的学习,请确保你已经正确安装并配置了所有必要的开发工具,包括 Plutus Application Framework (PAF)、必要的 Cardano 节点(例如`cardano-node` 和 `cardano-cli`),以及 Haskell 工具链 (GHC, Cabal)。 特别强调,正确配置 Cardano 节点,使其与测试网络(如 Preprod 或 Preview)同步,是成功部署和测试智能合约的关键步骤。

环境准备

在深入 Plutus 智能合约开发之前,务必确保您的开发环境已妥善配置。以下是详细的环境搭建步骤:

  • 安装 Cardano 节点 : 这是与 Cardano 区块链进行交互的基础。您需要从 Cardano 官方网站下载最新版本的 Cardano 节点软件。下载完成后,按照官方指南完成安装和配置,并启动节点。请耐心等待节点同步完成,同步过程可能需要数小时甚至数天,具体取决于您的网络连接速度和区块链的大小。节点同步完成后,您才能与 Cardano 网络进行交互,查询链上数据、提交交易等。强烈建议使用SSD硬盘以加速同步过程。
  • 安装 Plutus Application Framework (PAF) : PAF 是一个强大的 Plutus 智能合约开发和测试框架,它提供了一系列工具和库,简化了智能合约的开发流程。请参考 Cardano 官方文档或 Plutus 开发文档,下载并安装 PAF。安装过程中,请确保您的系统满足 PAF 的依赖要求,并按照说明进行配置。PAF 包括 Plutus Tx 和 PAB(Plutus Application Backend)等核心组件。Plutus Tx负责将Haskell代码编译为链上可执行的脚本,而PAB则负责处理链下交互,例如钱包交互、API调用等。
  • 安装 GHC 和 Cabal : Plutus 智能合约使用 Haskell 编程语言编写,因此您需要安装 GHC (Glasgow Haskell Compiler),它是 Haskell 的标准编译器。同时,您还需要安装 Cabal,它是 Haskell 的包管理和构建工具。您可以从 Haskell 官方网站下载 GHC 和 Cabal 的最新版本。安装完成后,请确保 GHC 和 Cabal 的可执行文件已添加到您的系统环境变量中,以便您可以在命令行中直接使用它们。建议使用 GHCup 进行安装,方便管理不同版本的GHC。
  • 配置 Nix 环境 (可选但强烈推荐) : Nix 是一个强大的包管理器和系统配置工具,它可以帮助您创建一个隔离的、可重现的开发环境。使用 Nix,您可以确保您的开发环境始终保持一致,避免因不同版本的依赖库而导致的问题。如果您选择使用 Nix,请按照 Nix 官方文档进行安装和配置。安装完成后,您可以使用 Nix 创建一个包含所有 Plutus 依赖项的环境,从而避免与系统中其他项目的依赖冲突。Nix 环境可以确保项目在不同机器上的可移植性和可重复性。 通过 `nix-shell` 命令,您可以方便地进入和退出 Nix 环境。

Plutus 核心概念

Plutus 平台采用 Haskell 作为智能合约的主要编程语言。Plutus 智能合约的设计围绕着链上和链下组件的协同工作,实现安全且高效的去中心化应用。

  • 链上代码 (On-Chain Code) : 链上代码是指在 Cardano 区块链上直接执行的智能合约逻辑。其核心职责是验证交易,确保交易符合合约预设的规则和条件。验证通过后,交易才能被确认并记录到区块链上。链上代码必须高度安全和高效,以避免潜在的安全漏洞和性能问题。
  • 链下代码 (Off-Chain Code) : 链下代码则在区块链之外运行,负责构建交易和与用户进行交互。链下代码允许更复杂的计算和数据处理,而无需消耗昂贵的链上资源。典型的链下操作包括构建交易、签名交易、以及将交易提交到区块链。链下代码可以方便地集成各种外部数据源和服务。

Plutus 采用基于 UTXO (Unspent Transaction Output) 模型的扩展智能合约模型,该模型提供了一种清晰的状态管理方式。合约状态存储在 UTXO 中,通过交易来更新状态,保证了合约状态的可追溯性和安全性。要深入理解 Plutus 合约,需要熟悉以下关键概念:

  • UTXO (Unspent Transaction Output) : UTXO 代表未花费的交易输出,是 Cardano 区块链的最小基本单位。 每个 UTXO 都包含一定数量的加密货币,以及可能包含的合约状态数据 (Datum)。合约的状态信息持久化存储在 UTXO 中。当合约需要更新状态时,就会创建一个新的交易,消耗现有的 UTXO,并创建包含更新状态的新 UTXO。
  • Validator : Validator 是一种用 Haskell 编写的函数,其作用是验证交易是否满足合约的执行条件。Validator 函数接收交易输入、交易输出和脚本上下文 (Script Context) 作为参数。通过分析这些参数,Validator 能够判断交易是否被授权执行,以及交易是否符合合约逻辑。Validator 的结果决定了 UTXO 是否可以被解锁和花费。
  • Datum : Datum 是存储在 UTXO 中的任意数据,代表合约的状态或者任何需要存储在链上的信息。Datum 可以是简单的数值,也可以是复杂的数据结构。Datum 的值在 UTXO 创建时被写入,在 UTXO 被花费时被读取。通过 Datum,合约可以跟踪其状态,并根据状态的变化执行不同的操作。
  • Redeemer : Redeemer 是解锁 UTXO 的输入参数,提供验证器函数所需的信息,用于判断交易是否有效。Redeemer 可以包含各种数据,例如签名、参数、或者其他证明交易有效性的信息。Validator 根据 Redeemer 的值来执行相应的验证逻辑。不同的 Redeemer 值可以触发合约的不同行为。
  • Script Context : Script Context 包含当前交易的详细信息,例如输入、输出、签名、以及其他相关的交易元数据。Validator 函数可以利用 Script Context 来验证交易的各个方面,例如验证签名是否有效,验证输入和输出是否符合预期,以及验证交易是否满足时间锁定的条件。Script Context 提供了 Validator 所需的上下文信息,以做出正确的验证决策。

编写 Plutus 合约

我们将创建一个简单的“Hello World”合约,该合约允许用户锁定 Ada,并在满足特定条件后解锁。这个合约展示了 Plutus 合约的基本结构,包括数据类型定义、验证器逻辑以及链上和链下交互。

  1. 定义 Datum, Redeemer 和 Validator 参数类型

在 Plutus 合约中,Datum、Redeemer 和 Script Context 是三个核心要素。 Datum 存储在合约地址上的状态信息,Redeemer 用于解锁或操作合约的状态,而 Script Context 提供了关于当前交易的上下文信息。正确定义这些类型是编写健壮合约的关键。

HelloDatum 包含了一个字符串 helloMessage ,作为合约锁定的信息。 HelloRedeemer 包含 unlockMessage ,用于验证解锁交易。使用 makeLift unstableMakeIsData 函数可以方便地将 Haskell 数据类型转换为 Plutus Core 可以理解的格式。


{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}

module Hello where

import PlutusTx
import PlutusTx.Prelude hiding (String)
import Ledger
import Ledger.Constraints as Constraints
import qualified Ledger.Typed.Scripts as Scripts
import qualified Plutus.V1.Ledger.Scripts as Plutus
import qualified Plutus.V1.Ledger.Value as Value

-- 定义 Datum 类型
data HelloDatum = HelloDatum
    { helloMessage :: BuiltinByteString
       }

PlutusTx.makeLift ''HelloDatum
PlutusTx.unstableMakeIsData ''HelloDatum

-- 定义 Redeemer 类型
data HelloRedeemer = HelloRedeemer
    { unlockMessage :: BuiltinByteString
      }

PlutusTx.makeLift ''HelloRedeemer
PlutusTx.unstableMakeIsData ''HelloRedeemer
  1. 编写 Validator 函数

Validator 函数是合约的核心逻辑。它决定了在什么条件下允许交易执行。该函数接收 Datum、Redeemer 和 Script Context 作为输入,并返回一个布尔值。如果返回 True ,则交易有效,否则无效。

mkValidator 函数检查 Redeemer 中的 unlockMessage 是否与 Datum 中的 helloMessage 匹配。如果匹配,则交易被认为是有效的,否则合约将拒绝该交易。使用 traceIfFalse 函数可以在 Plutus 模拟器中添加调试信息,方便开发者调试合约。

validator 使用 Plutus.mkValidatorScript PlutusTx.compile 将 Haskell 代码编译成 Plutus Core,这是链上运行的实际代码。编译后的验证器脚本将被用于验证所有与该合约相关的交易。


-- 定义 Validator 函数
{-# INLINABLE mkValidator #-}
mkValidator :: HelloDatum -> HelloRedeemer -> ScriptContext -> Bool
mkValidator datum redeemer ctx = traceIfFalse "Incorrect unlock message" (unlockMessage redeemer == helloMessage datum)

validator :: Plutus.Validator
validator = Plutus.mkValidatorScript
    $$(PlutusTx.compile [|| mkValidator ||])
  1. 创建 Typed Validator

Typed Validator 提供了一种更类型安全的方式来与 Plutus 合约交互。通过定义 ValidatorTypes 实例,可以将 Datum 和 Redeemer 类型与验证器脚本关联起来,从而在编译时捕获类型错误。

HelloType 是一个空类型,用于创建 ValidatorTypes 实例。 DatumType RedeemerType 分别被设置为 HelloDatum HelloRedeemer typedValidator 使用 Scripts.mkTypedValidator 函数创建 Typed Validator。 wrap 函数用于将普通的验证器函数转换为 Typed Validator 可以使用的格式。


data HelloType
instance Scripts.ValidatorTypes HelloType where
      type instance DatumType HelloType = HelloDatum
     type instance RedeemerType HelloType = HelloRedeemer

typedValidator :: Scripts.TypedValidator HelloType
typedValidator = Scripts.mkTypedValidator @HelloType
     $validator
     $$(PlutusTx.compile [|| wrap ||])
   where
     wrap = Scripts.wrapValidator @HelloDatum @HelloRedeemer
  1. 链下代码 (Off-Chain Code)

链下代码负责与 Plutus 合约进行交互,构建并提交交易。 这部分代码运行在 Cardano 节点之外,通常使用 Cardano 客户端库和 Plutus Application Framework (PAF) 与区块链交互。链下代码需要处理交易的构建、签名和提交,以及监控合约状态等任务。

  • 锁定 Ada :

lock 函数负责将 Ada 锁定到合约地址。它接收 Datum 和要锁定的 Value 作为输入。该函数首先获取 Typed Validator 的脚本地址,然后创建一个包含 mustPayToTheScript 约束的交易。 mustPayToTheScript 约束确保指定的 Value 被支付到合约地址,并将 Datum 存储在该地址上。使用 submitTxConstraints 函数提交交易。


-- 锁定 Ada
lock :: HelloDatum -> Value.Value -> IO ()
lock datum value = do
     let script = Scripts.validatorScript typedValidator
           tx       = Constraints.mustPayToTheScript datum value
     ledgerTx <- submitTxConstraints typedValidator tx
     print $ "submitted lock transaction " ++ show ledgerTx
  • 解锁 Ada :

unlock 函数负责从合约地址解锁 Ada。它接收 Datum、Redeemer、TxOutRef 和 TxOutTx 作为输入。TxOutRef 指向合约地址上的 UTXO (Unspent Transaction Output),TxOutTx 包含关于该 UTXO 的详细信息。 unlock 函数创建一个包含 mustSpendScriptOutput 约束的交易。 mustSpendScriptOutput 约束确保指定的 UTXO 被花费,并且使用正确的 Datum 和 Redeemer。该函数还添加了 mustBeSignedBy mustPayToPubKey 约束,以确保交易由正确的用户签名,并将解锁的 Ada 支付给正确的地址。使用 submitTxConstraintsWith 函数提交交易。


-- 解锁 Ada
unlock :: HelloDatum -> HelloRedeemer -> Ledger.TxOutRef -> Ledger.TxOutTx -> IO ()
unlock datum redeemer oref odata = do
       let script = Scripts.validatorScript typedValidator
         val    = Ledger.txOutValue $ Ledger.txOutTxOut odata
           lookups = Constraints.typedValidatorLookups typedValidator
                   Constraints.otherScript script
                 Constraints.unspentOutputs (Map.singleton oref odata)
        tx        = Constraints.mustSpendScriptOutput oref (Scripts.datumHash datum) redeemer
                    Constraints.mustBeSignedBy (Ledger.txOutAddress $ Ledger.txOutTxOut odata)
                   Constraints.mustPayToPubKey (Ledger.txOutAddress $ Ledger.txOutTxOut odata) val

ledgerTx <- submitTxConstraintsWith @HelloType lookups tx
print $ "submitted unlock transaction " ++ show ledgerTx
  1. 编译合约

使用 cabal build stack build 命令编译合约。这些构建工具会自动处理依赖关系,并将 Haskell 代码编译成 Plutus Core 脚本。编译成功后,可以在链上部署和执行合约。

测试 Plutus 合约

在 Cardano 区块链上部署 Plutus 合约之前,进行全面的测试至关重要。 可以使用 Plutus Application Framework (PAF) 或直接利用 Cardano 节点提供的命令行工具,甚至结合二者进行测试。 选择合适的测试方法取决于测试的需求,例如,对合约逻辑的快速验证,或是模拟真实链上环境的完整集成测试。

  1. 使用 Plutus Playground 进行快速原型验证

Plutus Playground 是一款基于浏览器的交互式集成开发环境 (IDE),它提供了一个沙盒环境,专门用于测试和实验 Plutus 合约。 无需设置本地开发环境,用户即可在线编写、编译和模拟执行 Plutus Core 代码。 该环境支持模拟交易,允许开发者检查合约的执行流程、验证链上逻辑,并评估合约在不同输入条件下的行为。 Plutus Playground 非常适合快速原型设计、初步测试和学习 Plutus 编程。 但请注意,Playground 模拟的是一个简化版的 Cardano 环境,某些复杂场景可能无法完全模拟。

  1. 使用 PAF (Plutus Application Framework) 进行本地测试与集成

PAF (Plutus Application Framework) 提供了一个更高级的本地测试环境,允许开发者模拟 Cardano 区块链的完整功能。 开发者可以使用 PAF 启动一个本地的 Cardano 节点,并部署和执行 Plutus 合约。 与 Plutus Playground 相比,PAF 提供了更大的灵活性和控制权,允许开发者模拟更复杂的交易场景,例如多方参与的合约、并发交易和链上数据访问。 PAF 还支持与其他工具和库的集成,例如单元测试框架和模拟数据生成器,从而实现更全面的测试。 使用 PAF 需要一定的技术知识和开发经验,但它可以提供更准确和可靠的测试结果。 开发者还可以使用 PAF 构建模拟的链下应用程序 (Off-chain code),与 Plutus 合约进行交互,从而进行端到端的集成测试。

部署合约

将智能合约部署到 Cardano 区块链是一个涉及多个步骤的过程,其核心是将高级语言编写的合约转化为区块链可执行的代码,并将其永久存储在链上。这一过程首先需要将合约代码编译为 Plutus Core 脚本,Plutus Core 是 Cardano 用于智能合约执行的低级函数式编程语言。编译后的 Plutus Core 脚本代表了合约的逻辑,并将在满足特定条件时在区块链上执行。将合约存储在区块链上,确保合约的不可篡改性和永久可用性。

  1. 获取合约脚本

从成功编译后的 Cardano 智能合约中提取 Plutus Core 脚本是部署流程的关键一步。Plutus Core 脚本包含了合约的完整逻辑,它是合约在 Cardano 区块链上执行的基础。提取过程通常涉及使用 Cardano 提供的命令行工具或 SDK,例如 cardano-cli ,它可以从编译后的合约文件(通常是 .plutus 扩展名)中提取出 Plutus Core 脚本。

  1. 创建合约地址

在 Cardano 区块链上,每个智能合约都与一个唯一的地址相关联。要创建一个合约地址,你需要使用 Cardano 节点提供的工具,例如 cardano-cli address build 命令。创建地址的过程涉及指定脚本哈希(从 Plutus Core 脚本计算得出)以及网络魔术(指示地址属于哪个 Cardano 网络,例如主网或测试网)。合约地址用于接收锁定在该合约中的资金和触发合约执行的交易。合约地址本质上是锁定资金的“保管人”,只有满足合约逻辑规定的条件才能释放这些资金。

  1. 将合约脚本注册到区块链

为了让其他用户能够与你的智能合约交互,你需要将合约脚本注册到 Cardano 区块链。这通常通过提交一个包含合约脚本的特殊交易来实现。这个交易会将合约脚本存储在区块链上,并将其与你创建的合约地址关联起来。一旦注册完成,其他用户就可以通过向合约地址发送交易并包含适当的脚本见证人(证明满足合约条件)来执行合约。注册合约脚本是使合约可供整个 Cardano 网络使用的关键步骤,允许任何人验证合约逻辑并与之交互。

最佳实践

  • 编写清晰、简洁且可读性高的代码 : Plutus 合约作为智能合约,需要在 Cardano 区块链上执行,因此代码的效率和简洁性至关重要。 避免冗余代码,使用有意义的变量名,并添加适当的注释以提高代码的可维护性。优化代码逻辑,减少 gas 消耗,提高合约的执行效率。
  • 使用全面的单元测试和集成测试 : 仅仅验证合约的逻辑是否正确是不够的。需要编写全面的单元测试,覆盖各种可能的输入和边界条件,确保合约在各种情况下都能正常运行。还需要进行集成测试,模拟合约与其他合约或外部系统的交互,验证合约的整体功能。 使用专门的 Plutus 测试框架来简化测试流程。
  • 进行严格的安全审计和形式化验证 : 在部署合约之前,进行专业的安全审计是必不可少的,这包括代码审查、模糊测试等方法,以发现潜在的安全漏洞,例如重入攻击、溢出漏洞等。 考虑使用形式化验证工具,对合约的代码进行数学建模,证明合约的安全性。
  • 持续关注 Cardano 社区和 Plutus 平台更新 : 积极参与 Cardano 和 Plutus 开发者社区,例如论坛、邮件列表等,了解最新的开发动态、安全漏洞报告和最佳实践分享。 及时关注 Plutus 平台的更新,包括新的特性、优化和安全补丁。根据社区的反馈和平台的更新,不断改进和优化合约。

这是一个关于如何在 Cardano 区块链上进行合约开发的入门教程。希望本文能帮助你开始你的 Plutus 合约开发之旅。