波卡Polkadot智能合约部署指南:Substrate与Ink!实践

目录: 讲师 阅读:111

波卡Polkadot智能合约部署:从Substrate到Ink!

准备工作

在开始之前,请确保你已充分准备,并满足以下先决条件,这些准备工作对于顺利进行后续操作至关重要:

  • 硬件钱包: 拥有并正确设置硬件钱包,例如 Ledger Nano S/X、Trezor Model T 等。硬件钱包作为冷存储设备,能够有效隔离私钥与网络,显著提升安全性。请务必从官方渠道购买,并验证其真伪,防止遭受钓鱼攻击。
  • 助记词备份: 安全备份硬件钱包的助记词(通常为 12 或 24 个单词)。助记词是恢复钱包资产的唯一途径,务必将其抄写在纸上并存放在安全、防火、防水的地方,切勿以电子形式存储或分享给任何人。
  • MetaMask 浏览器扩展: 安装并配置 MetaMask 浏览器扩展。MetaMask 是一款流行的 Web3 钱包,可与 DApp(去中心化应用程序)进行交互。确保从官方网站或浏览器扩展商店下载,并仔细检查其权限请求。
  • 足够的 ETH: 在你的 MetaMask 钱包中准备足够的以太坊 (ETH) 用于支付交易 Gas 费用。Gas 费用是执行以太坊区块链上交易所需的计算资源成本,费用高低取决于网络拥堵程度。
  • 了解 Gas 费用: 理解以太坊 Gas 费用的概念及其影响。Gas 价格以 Gwei 为单位,需要根据当前网络状况合理设置 Gas Limit 和 Gas Price,避免交易失败或支付过高的费用。可以使用 Gas Tracker 工具(如 Etherscan Gas Tracker)来估算 Gas 费用。
  • 了解合约地址: 确认要交互的智能合约地址的准确性。错误的合约地址可能导致资金损失,务必从可信来源获取,例如项目官方网站或文档。
  • 风险意识: 充分了解加密货币和 DeFi (去中心化金融) 的风险。包括但不限于智能合约风险、无常损失风险、市场波动风险等。不要将所有资产投入 DeFi 项目,分散投资,并做好风险管理。
  • 网络安全: 确保你的计算机和网络环境安全。使用强密码,启用双因素认证 (2FA),避免点击不明链接或下载可疑文件,定期进行病毒扫描。
Rust环境: Polkadot生态系统中的智能合约开发主要使用Rust语言。你需要安装Rust编译器和 Cargo 包管理器。可以参考Rust官方文档进行安装: https://www.rust-lang.org/tools/install
  • Substrate 开发环境: Substrate 是一个用于构建区块链的框架,Ink! 是 Substrate 的智能合约语言。我们需要安装Substrate开发环境,包括 Substrate CLI 工具。
  • bash

    下载并安装 Nightly Rust Toolchain

    Rust 编程语言的 Nightly Toolchain 提供了最新的特性和实验性功能。它每日构建,因此包含前沿的改进,但也可能存在未知的错误。如果开发者需要使用最新的语言特性或为 Rust 本身做出贡献,那么 Nightly Toolchain 非常有用。

    要安装 Nightly Toolchain,可以使用 rustup 这个 Rust 版本管理工具。rustup 允许开发者轻松地安装、更新和管理多个 Rust Toolchain 版本。使用以下命令安装 nightly 版本:

    rustup toolchain install nightly

    此命令会从 Rust 官方仓库下载并安装最新的 Nightly Toolchain。安装完成后,开发者需要将 Nightly Toolchain 设置为默认 Toolchain,以便在编译 Rust 代码时默认使用 Nightly 版本。使用以下命令设置默认 Toolchain:

    rustup default nightly

    现在,所有后续的 Rust 编译操作都将默认使用 Nightly Toolchain。开发者可以通过指定特定的 Toolchain 版本来覆盖默认设置。例如,要使用稳定版本的 Rust 编译代码,可以使用以下命令:

    rustup run stable cargo build

    确保定期更新 Nightly Toolchain,以获取最新的特性和修复。可以使用以下命令更新:

    rustup update nightly

    为 Rust 项目添加 WebAssembly (wasm32-unknown-unknown) 目标

    为了将 Rust 代码编译成 WebAssembly 模块,以便在浏览器或其他 WebAssembly 运行时环境中执行,需要添加 wasm32-unknown-unknown 作为 Rust 的编译目标。此目标专为生成可在各种 WebAssembly 环境中运行的独立可移植的 wasm 文件而设计。执行以下命令即可完成添加:

    rustup target add wasm32-unknown-unknown

    该命令使用 rustup 工具,它是 Rust 官方的工具链安装器和版本管理器。 target add 子命令指示 rustup 安装指定的目标架构的支持文件和库。 wasm32-unknown-unknown 目标指定了一个 32 位的 WebAssembly 环境,没有特定的操作系统或标准库依赖,使其成为构建可移植 WebAssembly 模块的理想选择。安装完成后,您就可以配置您的 Rust 项目,以便编译成 WebAssembly。

    安装 Substrate CLI 工具

    通过 Cargo 包管理器安装 Substrate CLI 工具,使用以下命令可以从指定的 Git 仓库和标签安装:

    cargo install --force --git https://github.com/paritytech/substrate.git --tag polkadot-v0.9.43 substrate

    这条命令会强制重新安装 Substrate CLI 工具,并确保你使用的是 polkadot-v0.9.43 标签对应的版本。正确安装后,你就可以使用 substrate 命令来创建和管理 Substrate 项目。

    • Ink! CLI 工具: Ink! CLI 工具是开发 Rust 编写的智能合约的关键组件,用于编译、测试和部署 Ink! 智能合约到 Substrate 区块链。

    安装 Ink! CLI 工具同样使用 Cargo:

    cargo install cargo-contract --force

    安装完成后,你可以使用 cargo contract 命令来编译、测试和部署你的 Ink! 智能合约。 确保你的 Rust 环境配置正确,并且 Cargo 能够正常工作。

    • 节点连接: 为了与区块链交互,你需要连接到一个波卡或 Kusama 的测试网络,或者本地运行的 Substrate 节点。你可以使用 Polkadot JS Apps ( https://polkadot.js.org/apps/ ) 或者其他支持 Substrate 协议的工具连接到节点。在 Polkadot JS Apps 中,你需要指定节点的 RPC 端点。

    如果使用本地节点,请确保节点已启动并正在监听 RPC 请求。 常用的本地节点 RPC 端点是 ws://127.0.0.1:9944 。 选择正确的网络和节点对于测试和部署智能合约至关重要。

    创建 Ink! 智能合约项目

    开始 Ink! 智能合约开发的第一步是创建新的项目。使用 cargo contract new 命令,它会自动生成包含必要文件和目录的初始项目结构。

    在终端或命令提示符中执行以下命令:

    cargo contract new my_contract
    cd my_contract
    

    上述命令会创建一个名为 my_contract 的新目录,并在其中初始化一个基本的 Ink! 智能合约项目。 cd my_contract 命令将当前工作目录更改为新创建的项目目录,以便进行后续操作。

    创建的项目目录结构如下:

    my_contract/
    ├── Cargo.toml
    └── src/
        └── lib.rs
    

    Cargo.toml 文件是 Rust 项目的清单文件,用于声明项目的元数据,包括名称、版本、作者以及项目依赖项。对于 Ink! 智能合约项目,它还包含与合约编译和部署相关的配置信息。

    src/lib.rs 文件是智能合约的源代码文件,包含合约的业务逻辑、数据结构和函数。默认情况下,它包含一个简单的模板合约,可以作为开发新合约的起点。开发者可以在此文件中定义合约的状态变量、可调用函数(消息)和事件。

    编写智能合约代码

    要开始编写你的智能合约,请打开 src/lib.rs 文件。 该文件通常包含智能合约的源代码,使用 Rust 语言编写。你会看到一个简单的 Flipper 合约示例,这是一个用于演示基本智能合约结构的例子。 你可以根据你的需求修改这个合约,学习它的结构,或者完全创建一个全新的合约。

    举例来说,我们可以创建一个简单的代币合约。 代币合约是一种常见的智能合约类型,用于发行和管理数字代币。 创建代币合约通常涉及到定义代币的名称、符号、总量,以及实现转账、余额查询等核心功能。

    要实现一个代币合约,你需要定义合约的状态变量,例如代币的总量、每个账户的余额等。 你还需要实现合约的函数,例如转账函数、批准函数等。 Rust 的所有权系统和类型系统可以帮助你编写安全可靠的智能合约代码。

    在 Rust 环境中编写智能合约,需要使用特定的库和工具。 你可以使用 cargo 来管理项目依赖,并使用 ink! 框架来简化智能合约的开发。 ink! 框架提供了一组宏和工具,可以帮助你轻松地定义智能合约的状态变量和函数,并将其编译成 WebAssembly 代码。

    src/lib.rs 是合约逻辑的核心所在地。在开始编写更复杂的逻辑之前,理解 Flipper 示例至关重要。它可以帮助你掌握合约的结构、数据的存储和处理方式,以及如何与区块链进行交互。

    请注意,智能合约的开发需要谨慎对待。 智能合约一旦部署到区块链上,就很难进行修改。 因此,在部署智能合约之前,一定要进行充分的测试和审计,以确保其安全性和正确性。

    代码示例 (Rust):

    ![cfgattr(not(feature = "std"), nostd)]

    [ink::contract]

    mod my_token {

    use ink::storage::Mapping;
    
    #[ink(storage)]
    pub struct MyToken {
        total_supply: Balance,
        balances: Mapping,
    }
    
    #[ink(event)]
    pub struct Transfer {
        #[ink(topic)]
        from: Option,
        #[ink(topic)]
        to: Option,
        value: Balance,
    }
    
    impl MyToken {
        #[ink(constructor)]
        pub fn new(total_supply: Balance) -> Self {
            let caller = Self::env().caller();
            let mut balances = Mapping::new();
            balances.insert(caller, &total_supply);
            Self::env().emit_event(Transfer {
                from: None,
                to: Some(caller),
                value: total_supply,
            });
            Self {
                total_supply,
                balances,
            }
        }
    
        #[ink(message)]
        pub fn total_supply(&self) -> Balance {
            self.total_supply
        }
    
        #[ink(message)]
        pub fn balance_of(&self, owner: AccountId) -> Balance {
            self.balances.get(&owner).unwrap_or(0)
        }
    
        #[ink(message)]
        pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<(), Error> {
            let caller = self.env().caller();
            let caller_balance = self.balance_of(caller);
    
            if caller_balance < value {
                return Err(Error::InsufficientBalance);
            }
    
            self.balances.insert(caller, &(caller_balance - value));
            let to_balance = self.balance_of(to);
            self.balances.insert(to, &(to_balance + value));
    
            self.env().emit_event(Transfer {
                from: Some(caller),
                to: Some(to),
                value,
            });
    
            Ok(())
        }
    }
    
    #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
    pub enum Error {
        InsufficientBalance,
    }
    
    /// Unit tests in Rust are normally defined within such a module and are
    /// supported in ink! contracts as well.
    #[cfg(test)]
    mod tests {
        /// Imports all the definitions from the outer scope so we can use them here.
        use super::*;
    
        /// We test if the default constructor does its job.
        #[ink::test]
        fn default_works() {
            let mut my_token = MyToken::new(1000);
            assert_eq!(my_token.total_supply(), 1000);
            assert_eq!(my_token.balance_of(AccountId::from([0x01; 32])), 1000);
        }
    
        /// We test a simple use case of our contract.
        #[ink::test]
        fn transfer_works() {
            let mut my_token = MyToken::new(1000);
            let alice = AccountId::from([0x01; 32]);
            let bob = AccountId::from([0x02; 32]);
    
            assert_eq!(my_token.transfer(bob, 100), Ok(()));
            assert_eq!(my_token.balance_of(alice), 900);
            assert_eq!(my_token.balance_of(bob), 100);
        }
    }
    

    }

    这个 ink! 合约定义了一个简单的可替代代币(fungible token),具有以下核心功能:

    • new : 构造函数,用于初始化代币。它接收一个 total_supply 参数,代表代币发行的总量。构造函数会将这个总量分配给部署合约的账户(即 caller ),并触发一个 Transfer 事件,记录代币的初始分配。
    • total_supply : 查询函数,用于获取代币的总发行量。它返回一个 Balance 类型的值,代表代币的总量。
    • balance_of : 查询函数,用于查询指定账户的代币余额。它接收一个 AccountId 类型的参数,代表要查询的账户地址。如果该账户存在余额,则返回其 Balance ;否则,返回 0。
    • transfer : 交易函数,用于将代币从一个账户转移到另一个账户。它接收两个参数: to ( AccountId 类型),代表接收代币的账户地址; value ( Balance 类型),代表要转移的代币数量。
      • 该函数首先检查调用者( caller )的余额是否足够支付转移的代币数量。如果余额不足,则返回一个 Error::InsufficientBalance 错误。
      • 如果余额充足,则从调用者的余额中扣除转移的代币数量,并将这些代币添加到接收者的余额中。
      • 该函数触发一个 Transfer 事件,记录代币转移的详细信息,包括发送者、接收者和转移的代币数量。

    合约还定义了一个 Error 枚举类型,用于表示可能的错误情况,目前只包含 InsufficientBalance 错误,即余额不足错误。

    合约还包含一个 tests 模块,其中定义了两个单元测试,用于验证合约的功能是否正常:

    • default_works : 测试构造函数是否正确初始化代币总量,并将其分配给部署者。
    • transfer_works : 测试 transfer 函数是否能够正确地将代币从一个账户转移到另一个账户,并更新相应的余额。

    该合约遵循 ERC-20 代币标准的基本原则,实现了代币发行和转账的核心功能。开发者可以在此基础上扩展合约的功能,例如添加授权、冻结账户等功能,以满足更复杂的需求。

    编译智能合约

    使用 cargo contract build 命令编译智能合约。此命令利用 Rust 的 Cargo 构建系统,并结合 ink! 框架提供的特定工具链,将你的智能合约代码转换为可在区块链上执行的 WebAssembly (Wasm) 格式。

    bash

    cargo contract build

    编译成功后,将在 target/ink 目录下生成以下关键文件。 target 目录是 Cargo 构建系统默认的输出目录,而 ink 子目录则专门用于存放 ink! 智能合约编译后的相关文件。

    • my_contract.contract : 合约的元数据文件,这是一个 JSON 格式的文件,包含合约的应用二进制接口 (ABI)、合约的名称、版本、构造函数、消息(函数)定义以及其他描述合约结构和交互方式的重要信息。这个文件是与合约进行交互的关键,例如,在使用 Polkadot.js 或其他工具调用合约函数时,需要用到此文件。
    • my_contract.wasm : 合约的 WebAssembly 代码。这是智能合约的可执行代码,是以 WebAssembly 字节码形式存在的。当智能合约部署到区块链上时,区块链的 Wasm 虚拟机将执行此代码。 Wasm 是一种低级、可移植的字节码格式,被设计为在各种平台上安全高效地执行。

    部署智能合约

    你可以使用 Polkadot JS Apps 这类图形化界面工具,或者通过命令行工具,例如 cargo-contract ,来部署智能合约到波卡(Polkadot)或 Kusama 网络。 部署过程涉及将编译后的合约代码上传到链上,并创建一个合约实例。

    1. 连接节点: 你需要连接到你想部署合约的目标网络。这可以是波卡或 Kusama 的公共测试网络,例如 Rococo 或 Westend,也可以是本地运行的 Substrate 节点。 在Polkadot JS Apps中,你需要配置正确的节点地址。
    2. 上传合约: 在 Polkadot JS Apps 中,导航到 "Contracts" -> "Upload & Instantiate Contract" 界面。 这一步会将合约的编译代码上传到区块链上,为后续的实例化做准备。
    3. 上传 WASM 代码: 上传 my_contract.wasm 文件。 这是智能合约的 WebAssembly (WASM) 字节码,包含了合约的逻辑。 确保你上传的是通过 cargo-contract 等工具编译生成的 WASM 文件。
    4. 上传元数据: 上传 my_contract.contract 文件。 该文件包含合约的元数据,例如合约的接口定义(ABI),构造函数,方法,事件等。 Polkadot JS Apps 使用此元数据来呈现合约的可用功能,并允许你与之交互。
    5. 实例化合约: 选择合约的构造函数,并提供所需的参数。 构造函数是合约部署时执行的特殊函数,用于初始化合约的状态。 根据合约的定义,你可能需要提供一些参数,例如初始余额、所有者地址等。
    6. 签名并发送交易: 使用你的账户签名并发送交易来部署合约。 这一步会将包含合约代码和初始状态的交易发送到区块链网络。 确保你有足够的余额来支付交易费用。 交易被打包进区块后,你的合约就被成功部署到链上了。

    与智能合约交互

    智能合约部署成功后,你需要与之进行交互才能发挥其功能。你可以选择多种工具来完成这项任务,例如 Polkadot JS Apps,或者任何其他兼容的工具。Polkadot JS Apps 是一个常用的网页界面,允许用户连接到不同的 Polkadot 和 Kusama 网络,并与部署在这些网络上的智能合约进行交互。

    1. 连接节点: 你需要建立与目标网络的连接。这通常涉及到连接到你想与之交互的波卡(Polkadot)或 Kusama 的测试网络,亦或是你本地运行的 Substrate 节点。确保你连接的网络上部署了你的智能合约。在 Polkadot JS Apps 中,这通常通过选择正确的端点来实现。
    2. 选择合约: 在 Polkadot JS Apps 界面中,导航到 "Contracts" -> "Contracts" 选项卡。这将打开合约交互界面。
    3. 选择合约地址: 在合约交互界面中,你需要输入你之前部署的智能合约的地址。这个地址是唯一的,用于标识网络上的特定合约实例。务必仔细核对地址的准确性,确保与你想要交互的合约相符。
    4. 调用合约方法: 选择你想调用的特定合约方法。智能合约由多个方法组成,每个方法执行不同的功能。你需要根据你的需求选择正确的方法。在选择方法后,你需要提供该方法所需的参数。参数是方法执行所需的输入数据,参数类型和数量取决于方法的设计。
    5. 签名并发送交易: 在提供所有必需的参数后,你需要使用你的账户对交易进行签名并发送。签名是验证交易合法性的过程,它证明你授权执行该交易。发送交易会将你的请求广播到网络,网络中的节点会验证并执行该交易。交易成功执行后,合约的状态可能会发生改变,这取决于你调用的合约方法的功能。在 Polkadot JS Apps 中,你需要使用连接的账户(例如,通过 Polkadot{.js} extension)进行签名。

    智能合约调试方法详解

    在智能合约的开发生命周期中,调试是至关重要的一个环节。为了确保合约的功能正确性、安全性和效率,我们需要对其进行全面的测试和调试。一种常用的方法是利用 cargo contract test 命令运行单元测试。该命令会自动编译合约代码,并执行预先编写的测试用例,从而验证合约的各项功能是否符合预期。通过编写全面的单元测试,可以尽早发现并修复潜在的 bug,避免在部署到生产环境后出现问题。

    示例:

    cargo contract test

    除了单元测试外, cargo contract debug 命令提供了一种更强大的调试手段。该命令会在本地启动一个完整的 Substrate 节点,并将智能合约部署到该节点上。这使得开发者可以模拟真实的网络环境,并使用调试器逐步执行合约代码,观察变量的值、调用栈信息等,从而深入了解合约的运行机制。通过这种方式,可以更容易地定位和解决复杂的 bug,例如 gas 消耗过高、逻辑错误等。还可以利用 Substrate 节点提供的日志输出、监控指标等工具,对合约的性能进行评估和优化。

    示例:

    cargo contract debug

    安全注意事项

    • 代码审计: 在部署智能合约之前,务必进行全面且深入的代码审计,由经验丰富的安全专家审查,识别并修复潜在的安全漏洞,例如重入攻击、拒绝服务攻击、以及未经授权的访问控制等。审计应涵盖合约逻辑、数据存储、以及与外部合约的交互等方面。
    • 风险控制: 智能合约的部署和使用涉及固有风险,包括代码缺陷、市场波动、监管变化以及人为错误。应谨慎评估并理解这些风险,制定风险管理策略,例如设置交易限额、使用多重签名钱包、以及实施紧急暂停功能。
    • 参数验证: 在智能合约中,对所有用户提供的输入参数进行严格验证,确保数据类型、范围和格式符合预期,防止恶意攻击者利用无效或恶意构造的输入来破坏合约状态或执行不当操作。例如,检查地址是否有效、数值是否在合理范围内、以及字符串是否符合预期格式。
    • 溢出检查: 确保智能合约中的所有算术运算,例如加法、减法、乘法和除法,都实施了溢出和下溢检查机制。这可以防止因数值超出数据类型范围而导致的意外行为,并避免潜在的安全漏洞,例如代币凭空产生或账户余额异常减少。可以使用安全的数学库来简化溢出检查的实现。

    希望这篇教程能够帮助你成功部署你的 Ink! 智能合约到波卡或 Kusama 网络。 请务必理解并遵守所有安全注意事项,以保护你的合约和用户资金。

    相关推荐: