深入解读 Starknet 合约开发与部署:从 Cairo 编程到智能合约声明与部署

深入解读Starknet合约开发与部署:从Cairo编程到智能合约声明与部署随着区块链技术的发展,Starknet作为以太坊的Layer2解决方案,正吸引越来越多的开发者。它通过零知识证明(ZKP)大幅提高了交易效率和安全性。在Starknet中,Cairo作为智能合约的编程

深入解读 Starknet 合约开发与部署:从 Cairo 编程到智能合约声明与部署

随着区块链技术的发展,Starknet 作为以太坊的 Layer 2 解决方案,正吸引越来越多的开发者。它通过零知识证明(ZKP)大幅提高了交易效率和安全性。在 Starknet 中,Cairo 作为智能合约的编程语言,不仅提供了强大的功能,还保持了与 Rust 语言的相似性,方便开发者上手。本篇文章将深入探讨如何在 Starknet 上进行智能合约的开发、声明与部署,帮助你快速掌握这项技能。

本文从 Cairo 编程语言的基础出发,详细介绍了如何编写和部署 Starknet 智能合约。我们将通过具体的代码示例展示如何定义合约模块、实现存储、声明合约类并最终部署到 Starknet 网络上。文章还涵盖了合约声明和部署的区别,帮助读者理解如何利用 Cairo 和 Starknet 的元编程特性,高效构建和操作 Layer 2 合约。

Cairo 智能合约编写指南

  • Cairo 合约的编写首先需要创建一个模块 (mod)。
  • 使用 #[starknet::contract] 属性注解合约,这是通过元编程实现的。
  • 属性(宏)会处理你在宏下定义的代码,并将其转换成其他形式的代码。在这种情况下,模块会被转化为智能合约。
  • 元编程在 Rust 中很流行,Cairo 也通过不同的宏和属性来实现类似的元编程技术。
  • 合约的存储定义为一个名为 Storage 的结构体,需使用 #[storage] 属性进行标注。
  • 在合约接口中,必须为 self 定义类型。

声明与部署的核心差异

在 Starknet 上,声明和部署是两个不同的操作:

  • 声明:将智能合约代码注册到 Starknet 网络,仅需执行一次。声明后的代码称为合约类,由类哈希标识,作为智能合约实例的蓝图,但没有存储,不用于直接交易或交互。

  • 部署:从合约类派生出实例,执行构造函数后生成具体的智能合约实例。实例有独立存储,并通过唯一的地址标识。可以通过同一个合约类部署多个实例,它们共享相同的代码,但各自的存储和地址是不同的。

Cairo 与 Starknet 生态概述

Cairo 是一种可证明的编程语言,语法类似于 Rust。Cairo 并不会直接编译为字节码,而是通过 Sequencer 编译为 Sierra 代码。这种编译过程确保即便交易失败,系统也能与零知识证明兼容,避免潜在的拒绝服务攻击。Sierra 使得 Cairo 虚拟机(VM)与字节码的演变相互独立,因此可以对两端分别进行修改,而无需重构整个系统。

Cairo 程序执行流程

  1. Cairo 程序首先编译为 Sierra (Cairo ProgramSierra)。

  2. Sierra 代码发送至 Sequencer (Send Sequencer)。

  3. Sequencer 将 Sierra 编译为 CASM (SierraCASM)。

  4. Cairo 虚拟机(VM)执行程序 (Run Cairo VM)。

  5. 证明者(Prover)通过 SHARP 跟踪并生成有效性证明 (Prover RUN trace)。

  6. 有效性证明被作为交易发送至以太坊,由验证者(Verifier)进行验证 (Validity proof → Ethereum Verifier)。

  7. 最终,交易结果返回 Starknet 网络。

实操

创建项目并用VS Code 打开项目

scarb new ownable
cd ownable
code .

image-20240925130649414.png

项目目录结构

ownable on  main [?] via 🅒 base 
➜ tree . -L 6 -I 'target'
.
├── Scarb.lock
├── Scarb.toml
├── src
│   └── lib.cairo
└── tests
    └── test_contract.cairo

3 directories, 4 files

Scarb.toml 文件

[package]
name = "ownable"
version = "0.1.0"
edition = "2023_11"

# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html

[dependencies]
starknet = "2.8.2"

[dev-dependencies]
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.30.0" }

[[target.starknet-contract]]
sierra = true

[scripts]
test = "snforge test"

lib.cairo 文件

#[starknet::contract]
mod ownable {
    use starknet::ContractAddress;

    #[storage]
    struct Storage {
        owner: ContractAddress,
    }

    #[constructor]
    fn constructor(ref self: ContractState, init_owner: ContractAddress) {
        self.owner.write(init_owner);
    }

    #[external(v0)]
    fn get_owner(self: @ContractState) -> ContractAddress {
        self.owner.read()
    }
}

构建 Scarb 项目

运行如下命令:

scarb build  

实操

ownable on  main [?] via 🅒 base 
➜ scarb build      

   Compiling snforge_scarb_plugin v0.1.0 (git+https://github.com/foundry-rs/starknet-foundry?tag=v0.30.0#196f06b251926697c3d66800f2a93ae595e76496)
    Finished `release` profile [optimized] target(s) in 0.19s
   Compiling ownable v0.1.0 (/Users/qiaopengjun/Code/starknet-code/hello_starknet/ownable/Scarb.toml)
    Finished release target(s) in 4 seconds

声明合约

在部署之前,我们需要声明合约。我们可以使用以下starkli declare命令执行此操作!

第一步:检查 starkli 是否已经安装:

ownable on  main [?] via 🅒 base took 4.3s 
➜ starkli --version

0.3.4 (9f6ea67)

第二步:创建 .env 文件,内容如下:

STARKNET_RPC=https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/YLxxxxxxx
STARKNET_ACCOUNT="~/.starkli-wallets/deployer/account.json"
STARKNET_KEYSTORE="~/.starkli-wallets/deployer/keystore.json"

ACCOUNT_ADDRESS=0x077a47bd6896cc52cd980a402ac3937629bd0bf63xxxxxxxxxxxx

第三步:加载 .env 文件

ownable on  main [?] via 🅒 base 
➜ source .env   

此命令会将 .env 文件中定义的环境变量加载到当前的 shell 会话中。确保 .env 文件在当前工作目录中,或者提供正确的文件路径。

验证环境变量是否加载成功:

运行以下命令可以输出环境变量 $STARKNET_RPC 的值:

ownable on  main [?] via 🅒 base 
➜ echo $STARKNET_RPC                                                                                            
https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/YLxxxxxxx

如果该变量已经在你的 .env 文件或系统中正确设置,该命令会显示其值。如果没有输出,可能是该变量尚未设置或未正确加载。

第四步:starkli declare 声明合约

ownable on  main [?] via 🅒 base 
➜ starkli declare target/dev/ownable_ownable.contract_class.json --account $STARKNET_ACCOUNT --rpc $STARKNET_RPC --keystore=$STARKNET_KEYSTORE
Enter keystore password: 
Sierra compiler version not specified. Attempting to automatically decide version to use...
Network detected: sepolia. Using the default compiler version for this network: 2.7.1. Use the --compiler-version flag to choose a different version.
Declaring Cairo 1 class: 0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3
Compiling Sierra class to CASM with compiler version 2.7.1...
CASM class hash: 0x0371cf27992c80dc88d829fbacb41ae842531e64595520e998a2086e70ee1339
Contract declaration transaction: 0x06f3f6ebdc79e8c6f432f0e9cc4a2f9d8656ab8adc319db84259e7692ed7adb9
Class hash declared:
0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3

合约部署

在使用 starknet deploy 部署合约时,如果合约的 constructor 具有参数,必须传递这些参数。Starknet 中的合约在部署时,构造函数负责初始化合约的状态,因此所有构造函数所要求的参数必须提供。

如果你省略构造函数参数,部署过程会失败,并提示你需要传递参数。

第一次部署没有传参,部署失败示例:

ownable on  main [?] via 🅒 base 
➜ starkli deploy 0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3 --account $STARKNET_ACCOUNT --rpc $STARKNET_RPC --keystore=$STARKNET_KEYSTORE
Enter keystore password: 
Deploying class 0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3 with salt 0x0441b06f2d59bba3046ceede8a8890f45863423028cb6cd815f7a56806960d4e...
The contract will be deployed at address 0x04178f8af9f60b6d1cc31ee353e07167401982effc69c3899e858b58145f11f4
Error: TransactionExecutionError (tx index 0): Transaction execution has failed:
0: Error in the called contract (contract address: 0x077a47bd6896cc52cd980a402ac3937629bd0bf63da6c318ee0be3409cc2e129, class hash: 0x00816dd0297efc55dc1e7559020a3a825e81ef734b558f03c83325d4da7e6253, selector: 0x015d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad):
Error at pc=0:20259:
Cairo traceback (most recent call last):
Unknown location (pc=0:309)
Unknown location (pc=0:5819)
Unknown location (pc=0:11008)

1: Error in the called contract (contract address: 0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf, class hash: 0x07b3e05f48f0c69e4a65ce5e076a66271a527aff2c34ce1083ec6e1526997a69, selector: 0x01987cbd17808b9a23693d4de7e246a443cfe37e6e7fbaeabd7d7e6532b07c3d):
Error at pc=0:32:
Cairo traceback (most recent call last):
Unknown location (pc=0:174)
Unknown location (pc=0:127)

2: Error in the contract class constructor (contract address: 0x04178f8af9f60b6d1cc31ee353e07167401982effc69c3899e858b58145f11f4, class hash: 0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3, selector: 0x028ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194):
Execution failed. Failure reason: 0x4661696c656420746f20646573657269616c697a6520706172616d202331 ('Failed to deserialize param #1').

第二次部署,传参成功部署:

ownable on  main [?] via 🅒 base took 7.0s 
➜ source .env                                                                                                                                 

ownable on  main [?] via 🅒 base 
➜ echo $STARKNET_RPC
https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/YLgbp9I-spejSR_9EHp_-UYDrIYdrwE1

ownable on  main [?] via 🅒 base 
➜ starkli deploy 0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3 $ACCOUNT_ADDRESS --account $STARKNET_ACCOUNT --rpc $STARKNET_RPC --keystore=$STARKNET_KEYSTORE
Enter keystore password: 
Deploying class 0x0238424dc7ec21465fc7b669a901adce95c3d4b7e3fcc13a4d4def68071d33b3 with salt 0x002d3fd8fd6e8fbbd6fe21e7219ee0b336c65dca11676845ed6d51fac0f945c3...
The contract will be deployed at address 0x00691d3d0a1d664b0d9540b2568d150f04081004059188d2fe84aaf851cbb9a7
Contract deployment transaction: 0x0794d3ee93837aa104cd59293c78617523525d1e9429062287269845d26ac62d
Contract deployed:
0x00691d3d0a1d664b0d9540b2568d150f04081004059188d2fe84aaf851cbb9a7

https://sepolia.starkscan.co/contract/0x00691d3d0a1d664b0d9540b2568d150f04081004059188d2fe84aaf851cbb9a7#read-write-contract-sub-read

https://sepolia.starkscan.co/contract/0x00691d3d0a1d664b0d9540b2568d150f04081004059188d2fe84aaf851cbb9a7#overview

image-20240925141837133.png

查看 class-code-history

https://sepolia.starkscan.co/contract/0x00691d3d0a1d664b0d9540b2568d150f04081004059188d2fe84aaf851cbb9a7#class-code-history

image-20240925142007216.png

调用方法get_owner

https://sepolia.starkscan.co/contract/0x00691d3d0a1d664b0d9540b2568d150f04081004059188d2fe84aaf851cbb9a7#read-write-contract-sub-read

image-20240925142145387.png

总结

通过本篇文章的学习,你已经掌握了在 Starknet 上进行智能合约开发的核心流程。从 Cairo 语言的编写到智能合约的声明与部署,每一个步骤都至关重要。Starknet 提供了一个高效且安全的环境,让开发者能够快速迭代合约并进行性能优化。未来,随着 Starknet 和 Cairo 的不断发展,这些技能将帮助你在去中心化应用(dApp)开发中站稳脚跟。

参考

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
寻月隐君
寻月隐君
0x750E...B6f5
不要放弃,如果你喜欢这件事,就不要放弃。如果你不喜欢,那这也不好,因为一个人不应该做自己不喜欢的事。