Cairo 之旅 VIII:用 Protostar 编写、部署第一个 Starknet 合约

作者:Darington Nnam
原文:Journey Through Cairo VIII — Writing And Depolying your first Starknet Contract With Protostar
翻译:Louis Wang
校对:「StarkNet 中文社区」

欢迎来到我们的系列文章「Cairo之旅」第八讲!上篇文章中学习了内置程序、提示和撤销引用。学到这里我们已经掌握了很多 Cairo 知识。

今天,我们将做一件非常棒的事情:编写和部署第一个 Starknet 合约! 像往常一样,如果你是中途加入,建议从头开始看我们的文章。

对于本地开发,我们将使用 Protostar,所以如果你还没有在本地设置 Protostar,请阅读本系列第一篇文章

P.S:终于能使用 Cairo0.10 版本了!今天不用 Starklings 了。别忘记更新你的 Cairo 版本和 Protostar 0.4.2 版本!

目标

在本篇教程中学会:

  1. 用 Cairo 编写一个简单的 Starknet 合约。

  2. 理解事件 (Event)、构造器 (Constructor)、外部函数 (External) 和视图函数 (View)。

  3. 使用 Protostar 部署你的合约。

  4. 通过 Voyager 与合约进行交互。

项目描述

我们将建立一个简单的 Starknet 命名服务 (Naming Service),将名字映射到钱包地址。

这是关于如何使用 Starknet.js 构建前端应用程序的教程中使用过类似的合同,查看 demo

开始工作

首先完成准备工作,用 Protostar 建立本地环境,为了减少篇幅精简知识点,在此不再过多叙述如何设置 Protostar

初始化一个新的 Protostar 项目

安装好 Protostar 后,我们通过运行命令来初始化一个新项目:

protostar init

此步骤要求我们提供项目名称和 lib 名称,需要输入这些信息以成功创建一个新项目。

在我们的代码编辑器中打开新项目查看已创建的文件夹:

注意 protostar 创建了一个 main.cairo 文件包含一些已预先写好的 Cairo 代码。你可以在我们完成后查看一下,并尝试修改它以巩固知识点。

src 文件夹包含我们的合约代码,lib 文件夹包含所有的外部进口,如 openzeppelin 合同等,test 文件夹包含测试脚本,protostar.toml 文件是我们的项目配置文件。

创建新的文件

下一步,我们在 src 文件夹中创建命名 Starknet.cairo 新文件,开始编写我们的合约代码。

导入

首先,类似于我们用 pragma solidity 完成 solidity 合约,用 %lang starknet 指令来指定文件中包含 Starknet 合约的代码。

然后,我们将导入所有可能在合约中使用到的,必要的库函数。

%lang starknet
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.starknet.common.syscalls import get_caller_address

导入 HashBuiltin 是因为我们的 pedersen 内置程序需要它,而 get_caller_address 则是为了获取 msg.sender (调用合约函数的用户地址)。

存储变量

详情阅读 Cairo 存储文章,我们的合约只有一个单一的存储变量,作为一个地址到名字的映射。

@storage_var
func names(address) -> (name: felt) {
}

事件

事件允许合约以特定的格式向区块链记录状态的变化,用于允许虚拟机轻松检索和过滤它们。

@event 修饰器在 Cairo 中创建一个事件。每当我们存储一个新名字,事件将发出调用者地址和输入名。

@event
func stored_name(address: felt, name: felt){
}

构造器

构造器是编写合约的重要部分。你可以用它们来在合约部署时初始化某些状态变量。

使用 @constructor 修饰器在 Cairo 中创建一个构造函数。

虽然我们目前的项目不一定需要构造函数,但为了演示如何使用,我们将创建一个构造函数,为调用者设置一个默认名。

@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt) {
let (caller) = get_caller_address();
names.write(caller, _name);
return ();
}

外部函数

如果你学过 Solidity,你会习惯于四种函数类型 (public, private, external, internal),但在 Cairo 中只有两种类型的函数,外部函数和视图函数

外部函数:改变区块链状态的函数,使用 @external 修饰器创建。

对于我们的合约,创建一个外部函数 store_name,它能接收一个输入名,并更新名字变量

@external
func store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt){
let (caller) = get_caller_address();
names.write(caller, _name);
stored_name.emit(caller, _name);
return ();
}

正如代码展示的,首先使用我们先前导入的 get_caller_address 库函数获得调用者,然后更新名字存储变量,最后发出一个 stored_name 事件。

视图函数

视图函数:getter 函数,它们不改变区块链的状态,使用 @view 修饰器创建。

对于我们的合约,创建一个视图函数 get_name,它能接收一个输入地址,并返回对应名称。

@view
func get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_address: felt) -> (name: felt){
let (name) = names.read(_address);
return (name,);
}

检查我们的代码是否完整:

%lang starknet
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.starknet.common.syscalls import get_caller_address
@storage_var
func names(address) -> (name: felt) {
}
@event
func stored_name(address: felt, name: felt){
}
@constructor
func constructor{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt) {
let (caller) = get_caller_address();
names.write(caller, _name);
return ();
}
@external
func store_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_name: felt){
let (caller) = get_caller_address();
names.write(caller, _name);
stored_name.emit(caller, _name);
return ();
}
@view
func get_name{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(_address: felt) -> (name: felt){
let (name) = names.read(_address);
return (name,);
}

下面开始在 StarkNet 上部署它!

部署合约

有了 Protostar,可以轻松地部署你的合约。我们需要做的第一件事是更新配置文件 protostar.toml 中的 protostar.contracts 部分,可以包含合约代码的路径。

下一步,建立我们的合约。在 Protostar 中建立合约类似于用 Hardhat 编译合约:

protostar build

成功显示为:

我们需要一个 Python 脚本来进行字符串到 felts 的转换,因为我们的构造函数需要一个 felts 类型的名字

第一步,在你的根目录下创建一个 utils.py 文件,粘贴以下代码:

MAX_LEN_FELT = 31

def str_to_felt(text):
if len(text) > MAX_LEN_FELT:
raise Exception("Text length too long to convert to felt.")
return int.from_bytes(text.encode(), "big")

def felt_to_str(felt):
length = (felt.bit_length() + 7) // 8
return felt.to_bytes(length, byteorder="big").decode("utf-8")

def str_to_felt_array(text):
return [str_to_felt(text[i:i+MAX_LEN_FELT]) for i in range(0, len(text), MAX_LEN_FELT)]

def uint256_to_int(uint256):
return uint256[0] + uint256[1]*2**128

def uint256(val):
return (val & 2128-1, (val & (2256-2**128)) >> 128)

def hex_to_felt(val):
return int(val, 16)

打开终端运行:

python3 -i utils.py

检查:

我们将短字符串转换成 felts:

str_to_felt("Darlington")

你可以在终端看到产生的短字符串。

完成以上步骤,准备好部署我们的合约!

Proostar 部署命令

运行 Protostar 部署命令,传入测试网络和 felt 格式的名称输入:

protostar deploy ./build/starknet.json --network testnet -i 322918500091226412576622
  • deploy 命令是 protostar 的一个内置命令。

  • ./build/main.json 指定编译文件的路径。

  • --network 变量用于指定需要部署到的网络。

  • --i 变量用于指定构造函数所需的输入。

阅读更多的部署指令

部署完毕后,我们会在屏幕上得到合约地址和交易哈希,在 Voyager 上复制并进行交互。

与合约交互

合约部署完成后,可以通过 Voyager 检查它并进行交互。

读取合约部分是我们可以与视图函数交互的地方,而写入合约部分是我们与外部函数交互的地方。

通过 Voyager 存储名字

通过 Voyager 读取名字

最后

恭喜你现在已经成功部署了第一个 Starknet 合约!

在本节课中,你学会了部署合约,用Voyager 交互合约,也可以自己做一个 UI 进行交互。

下节课中,我们将写测试脚本。敬请期待!如果觉得本教程对你有帮助,转发分享给其他人吧~

Subscribe to Starknet 中文
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.