最近在使用starknet进行开发,这里提供一些starknet的开发建议,希望对大家有所帮助。
最近在使用starknet进行开发,这里提供一些starknet的开发建议,希望对大家有所帮助。
因为starknet使用了新的开发语言cairo,所以对以太坊solidity合约无法直接迁移。新语言cairo不仅仅是开发语言的转变,而且整个开发工具链都发生了变化,其原因是cairo属于python体系所以工具链都采用了python,而solidity的工具链大都使用js。所以开始想到的是,是否存在办法可以将solidity合约直接转为cairo,这样dapp的迁移成本会大大降低。https://github.com/NethermindEth/warp正是这样一个项目。但经过尝试发现warp转换过的编译产物体积会膨胀非常严重,比如原来较为简单的nft项目将无法部署。此外由于cairo不支持msg.value等(后面会介绍不支持的原因)以及对字符串的不友好导致我们无法通过简单的转换工具来迁移合约变得不可行,所以目前阶段建议大家不要通过warp转换solidity的方式进行迁移,而是直接使用cairo进行重写。
在官方指导中使用python venv来创建python的虚拟环境,venv主要用于依赖包的隔离,但并未解决python版本本身的问题,如果没有使用python3.7可能会在开发中遇到各种各样的问题,有什么办法可以获得较为纯粹的开发环境呢?这里建议使用容器化方案docker,首先我们创建1个Dockerfile,内容如下所示:
FROM python:3.7
RUN mkdir cairo-contracts
WORKDIR cairo-contracts
Dockerfile非常简单,因为我们后续将在docker中进行开发,所以核心只需要From python:3.7即可(代表指定1个安装python3.7的基础镜像)其余命令都可以在进入这个容器后执行。 vscode安装Remote - Containers插件后可以支持打开docker中的文件
此时的开发环境就像是1台纯净的标准化Linux系统一样。
可以打开命令行对该容器进行一切Linux操作。 比如我们使用pip安装cairo-lang or nile-cairo (建议使用nile其依赖cairo-lang但更加好用)
pip install nile-cairo
or
pip install cairo-lang
这就是容器化的力量,docker这种容器化的方案减少了很多维护工作,前端开发同学可能接触比较少,但在后端docker和k8s已经成为标准。这里不再详细介绍,对docker和k8s有兴趣的可以自行查阅相关资料。
felt是1个-P/2<felt<P/2的整数。目前P是252位的整数。
cairo中felt的取值范围和solidity中最常用的uint256不一致。在cairo中也为我们提供了uint256的结构,定义如下所示, cairo中的uint256是使用2个felt组合表示,分别表示高低128位。
struct Uint256:
# The low 128 bits of the value.
member low : felt
# The high 128 bits of the value.
member high : felt
end
在cairo中并未直接提供string类型。
首先可以回顾string在计算机中的存储方式,一般而言string需要进行ascii encode变为二进制,每个字符由8位二进制来表示,这是因为计算机只能存储0 1。我们平时因为各个开发语言都已经在底层帮我们封装完成所以不需要关注这一过程,但cairo并未做这样的封装,所以需要开发者来完成string的 decode 和encode。
在cairo中不能直接使用string,我们需要在string数据上链前进行ascii encode变为整数,然后在读取链上string数据的时候进行ascii decode变回string。这也使得在链上进行字符串拼接变得比较麻烦。
比如我们可以看到OpenZeppelin提供的ERC721实现中就不支持使用baseUri+tokenid的方式去设置我们的tokenUrl,其原因就是在使用felt存储为整数时进行字符串拼接不是那么方便(只能使用<<方式,但同时又要避免felt溢出的问题,处理起来会很麻烦)。
此外使用felt表示string 还需要注意每个flet只能表示252/8=31个字符,如果超过31个字符,则需要使用flet数组。在OpenZeppelin ERC721的文档https://github.com/OpenZeppelin/cairo-contracts/blob/main/docs/ERC721.md中提到toknURI只支持31个字符,没有使用flet数组的原因是不符合ERC721规范,个人认为这里不太合理,因为31个字符很难表示1个tokneURI(除非使用短链接,但又会涉及到中心化服务的问题)。
cario提供了cli交互方式,但直接使用命令行交互,对于很多入参的前置处理会比较麻烦,比如上面说到cairo不支持string,如果我们需要存储1个string(比如nft的链接或合约name)就需要提前转换好之后再复制到命令行手动成本较高。所以我们使用python脚本去完成与合约的交互。下面分别就是进行部署1个nft合约以及mint操作时的pyhton脚本,内部还是使用了官方提供的cli。
import os
from utils import (str_to_felt)
name=str_to_felt("nft-web3-explorer-starknet")
symbol=str_to_felt("nft-web3-explorer-starknet")
account_address="合约账户地址"
contract_abi="./artifacts/abis/nft-web3-explorer-starknet.json"
os.system("starknet deploy --contract %s --inputs %s %s %s" % (contract_abi, name, symbol, account_address))
import os
contract_address="合约地址"
contract_abi="./artifacts/abis/nft-web3-explorer-starknet.json"
function_name="mint"
mint_account_address="mint合约账户地址"
//因为定义token是uint256 所以要传2个值
token="1 0"
os.system("starknet invoke --address %s --abi %s --function %s --inputs %s %s --account xxxxx" %
(contract_address, contract_abi, function_name, mint_account_address, token))
在https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/wallets/open_zeppelin.py中可以看到这个代码是在我们创建合约账户时指定的钱包,其作用就是来管理我们的账户。其实无论合约账户还是EOA都需要有私钥、公钥、地址这些必要组成。但在我们部署完合约账户时只在命令行中返回给我们地址,那其他信息存储在哪里呢? 我们可以查看 https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/cli/starknet_cli.py 内的相关逻辑
def get_account_dir(args) -> str:
"""
Returns the directory containing the wallet files. By default, DEFAULT_ACCOUNT_DIR is used.
"""
value = get_optional_arg_value(
args=args, arg_name="account_dir", environment_var="STARKNET_ACCOUNT_DIR"
)
if value is None:
return DEFAULT_ACCOUNT_DIR
return value
可以看到如果我们在部署账户合约时没有指定--account_dir参数会使用默认值DEFAULT_ACCOUNT_DIR
DEFAULT_ACCOUNT_DIR = "~/.starknet_accounts"
在部署完成合约账户后我们可以使用命令查看我们合约账户的相关信息
cat ~/.starknet_accounts/starknet_open_zeppelin_accounts.json
{
"alpha-goerli": {
"账户名字": {
"private_key": "私钥",
"public_key": "公钥",
"address": "合约地址"
}
}
}
我们在查看OpenZeppelin的ERC20合约以及ERC721合约时,会发现1个问题,这些合约之前的转账操作全部没了(所以OpenZeppelin的ERC721不能直接使用),这样让我们对starknet上如何转账有了疑问。要回答这个问题,我们就必须了解L2的货币从哪里来,L2的流通token也是eth,使用bridge的方式进行转换。如下图所示:
L1和L2是可以通过发消息来进行异步交互的,具体可见 https://starknet.io/docs/hello_starknet/l1l2.html
bridge的完整代码大家可以查看https://github.com/starkware-libs/starkgate-contracts
可以看到通过bridge,L1转到L2的ETH被存储到1个特定地址的ERC20中,我们进行转账操作就是对该ERC20合约调用transfer()方法。每个网络中都会有1个唯一的ERC20合约地址,我们需要通过这个合约地址来访问网络中通用token的相关信息。合约地址位于https://github.com/starkware-libs/starknet-addresses。
通过上面的分析可以看到在starknet中的转账是通过操作1个特定地址的ERC20完成,自然也就没有msg.value了。
一些参考链接:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!