Cairo 编译器的第 2 版对 Starknet 语法进行了更改,使代码更加明确和安全。智能合约公共接口是使用特征定义的,并且对存储的访问是通过 ContractState 特征完成的。私有方法必须使用与公共接口不同的实现来定义。事件现在定义为枚举,其中每个变体都是同名的结构。 概要 Cairo 编译器的第 2 版对 Starknet 语法进行了更改,使代码更加明确和安全。智能合约公共接口是使用特征定义的,并且对存储的访问是通过 ContractState 特征完成的。私有方法必须使用与公共接口不同的实现来定义。事件现在定义为枚举,其中每个变体都是同名的结构。 免责声明:此处使用的术语指的是 Cairo 编译器的不同版本,其语法是临时的,因为 Starknet 社区仍在讨论哪些是最好用的术语。一旦确定,本文将进行相应更新。 The Compiler v2 就在上周,Cairo 编译器的新的主要版本 2.0.0-rc0 在 Github 上发布。新的编译器对 Starknet 插件进行了重大改进,使我们的代码更安全、更明确、更可重复使用。请注意,Starknet 测试网或主网尚不支持这个新版本的编译器,因为它仍在集成环境中进行。 本文的目标是向您展示如何将为 Cairo 编译器版本 1.x 创建的 Starknet 智能合约重写为与编译器版本 2.x 兼容的智能合约。我们的起点是上一篇文章中创建的 Ownable 智能合约,它与 Cario 编译器版本 1.x 兼容。 #[contract]mod Ownable {use starknet::ContractAddress;use starknet::get_caller_address; #[event]fn OwnershipTransferred(previous_owner: ContractAddress, new_owner: ContractAddress) {} struct Storage {owner: ContractAddress,} #[constructor]fn constructor() {let deployer = get_caller_address();owner::write(deployer);} #[view]fn get_owner() -> ContractAddress {owner::read()} #[external]fn transfer_ownership(new_owner: ContractAddress) {only_owner();let previous_owner = owner::read();owner::write(new_owner);OwnershipTransferred(previous_owner, new_owner);} fn only_owner() {let caller = get_caller_address();assert(caller == owner::read(), 'Caller is not the owner');}} 项目设置 由于 Protostar 尚不支持编译器 v2,因此本文将依赖支持它的 Scarb 预发行版本(版本 0.5.0-alpha.1)。要安装该特定版本的 Scarb,您可以使用以下命令。 $ curl --proto '=https' --tlsv1.2 -sSf | bash -s -- -v 0.5.0-alpha.1 安装完成后,验证您是否获得了正确的版本。 $ scarb --version>>>scarb 0.5.0-alpha.1 (546dad33d 2023-06-19)cairo:2.0.0-rc3() 现在可以创建一个 Scarb 项目。 $ scarb new cairo1_v2$cdcairo1_v2 您应该得到如下所示的文件夹结构。 $ tree .>>>.├── Scarb.toml└── src└──lib.cairo 为了让 Scarb 编译 Starknet 智能合约,需要启用 Starknet 插件作为依赖项。 // Scarb.toml...[dependencies]starknet="2.0.0-rc3" 设置完成后,我们可以前往 src/lib.cairo 开始编写智能合约。 存储与构造器 在 Cairo 编译器的版本 2 中,智能合约仍然由带有 contract 属性注释的模块定义,只是这次该属性以定义它的插件的名称命名,在本例中为 starknet。 #[starknet::contract]mod Ownable {} 内部存储仍然定义为一个必须称为 Storage 的结构,只是这次必须使用一个存储属性来注释它。 #[starknet::contract]mod Ownable {use super::ContractAddress; #[storage]struct Storage {owner: ContractAddress,}} 为了定义构造函数,我们使用构造函数属性来注释函数,就像在 v1 中所做的那样,优点是现在函数可以具有任何名称,不需要像 v1 中那样被称为“构造函数”。尽管这不是必需的,但出于习惯,我仍然会将该函数称为“构造函数”,但您可以以不同的方式调用它。 另一个重要的变化是,现在构造函数会自动传递对 ContractState 的引用,该引用充当存储变量的中介,在本例中为“所有者”。 #[starknet::contract]mod Ownable {use super::ContractAddress; #[storage]struct Storage {owner: ContractAddress,} #[constructor]fn constructor(ref self: ContractState) {let deployer = get_caller_address();self.owner.write(deployer);}} 请注意,写入和读取存储的语法自 v1 以来已发生变化。之前我们执行owner::write(),而现在执行self.owner.write()。这同样适用于从存储中读取。 顺便说一下,ContractState 这个类型不需要手动进入作用域,它已包含在前奏中。 公共方法 与 Cairo 编译器版本 1 的一个重要区别是,现在我们需要使用带有 starknet::interface 属性注释的特征来明确定义智能合约的公共接口。 use starknet::ContractAddress; #[starknet::interface]trait OwnableTrait { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress;} #[starknet::contract]mod Ownable { ...} 如果您还记得 v1 中的原始代码,我们的智能合约有两个「公共」方法(get_owner 和 transfer_ownership)和一个「私有」方法(only_owner)。这一特征仅处理公共方法,而不依赖于「外部」或「视图」属性来表示哪个方法可以修改合约的状态,哪个方法不允许。相反,现在通过参数 self 的类型来明确这一点。 如果一个方法需要引用 ContractStorage(一旦实现,通用 T 就是这样),该方法就能够修改智能合约的内部状态。这就是我们过去所说的“外部”方法。另一方面,如果一个方法需要 ContractStorage 的快照,那么它只能读取它,而不能修改。这就是我们过去所说的“视图”方法。 现在,我们可以使用关键字 impl 为刚刚定义的特征创建一个实现。请记住,Cairo 与 Rust 的不同之处在于,实现是具备名称的。 use starknet::ContractAddress; #[starknet::interface]trait OwnableTrait { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress;} #[starknet::contract]mod Ownable { ... #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { let prev_owner = self.owner.read(); self.owner.write(new_owner); } fn get_owner(self: @ContractState) -> ContractAddress { self.owner.read() } }} 我们在定义智能合约的模块内为我们的特征创建了一个实现,将类型 ContractState 作为通用类型 T 传递,这样就可以像构造函数那样访问存储。 我们的实现用属性 external(v0) 进行注释。属性中的版本 0 意味着选择器仅从方法名称派生,就像过去的情况一样。缺点是,如果您为您的智能合约定义了另一个不同特征的实现,并且两个特征碰巧对它其中一个方法使用相同的名称,则编译器会因为选择器的重复而抛出错误。 该属性的未来版本可能会添加一种新的方法来计算选择器,以防止冲突,但目前还不能使用。目前,我们只能使用外部属性的版本 0。 私有方法 我们还需要为智能合约定义另一种方法,only_owner。此方法检查调用它的人是否应该是智能合约的所有者。 因为这是一个不允许从外部调用的私有方法,所以不能将其定义为 OwnableTrait(智能合约的公共接口)的一部分。相反,我们将使用 generate_trait 属性创建自动生成特征的新实现。 ...#[starknet::contract]mod Ownable { ... #[generate_trait] impl PrivateMethods of PrivateMethodsTrait { fn only_owner(self: @ContractState) { let caller = get_caller_address(); assert(caller == self.owner.read(), 'Caller is not the owner'); } }} 现在可以通过在需要的地方调用 self.only_owner() 来使用 only_owner 方法。 #[starknet::contract]mod Ownable { ... #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { self.only_owner(); ... } ... } #[generate_trait] impl PrivateMethods of PrivateMethodsTrait { fn only_owner(self: @ContractState) { ... } }} 事件 在 Cairo v1 中,事件只是一个没有主体的函数,并用事件(event)属性进行注释,而在 v2 版本中,事件是一个用相同属性注释的枚举(enum),但现在使用派生(derive) 实现了一些附加特征。 ...#[starknet::contract]mod Ownable { ... #[event] #[derive(Drop, starknet::Event)] enum Event { OwnershipTransferred: OwnershipTransferred, } #[derive(Drop, starknet::Event)] struct OwnershipTransferred { #[key] prev_owner: ContractAddress, #[key] new_owner: ContractAddress, }} 事件枚举的每个变体都必须是同名的结构体。在该结构中,使用可选的 key 属性定义想要发出的所有值,来通知系统我们希望 Starknet 索引哪些值,以便索引器更快地搜索和检索。在本例中,我们希望对两个值(prev_owner 和 new_owner)建立索引。 ContractState 特征定义了一个发出方法,可以用来发出事件。 ...#[starknet::contract]mod Ownable { ... #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { ... self.emit(Event::OwnershipTransferred(OwnershipTransferred { prev_owner: prev_owner, new_owner: new_owner, })); } ... } ...} 通过这个最终功能,我们已经完成了 Ownable 智能合约从 v1 到 v2 的迁移。完整代码如下所示。 use starknet::ContractAddress; #[starknet::interface]trait OwnableTrait { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress;} #[starknet::contract]mod Ownable { use super::ContractAddress; use starknet::get_caller_address; #[event] #[derive(Drop, starknet::Event)] enum Event { OwnershipTransferred: OwnershipTransferred, } #[derive(Drop, starknet::Event)] struct OwnershipTransferred { #[key] prev_owner: ContractAddress, #[key] new_owner: ContractAddress, } #[storage] struct Storage { owner: ContractAddress, } #[constructor] fn constructor(ref self: ContractState) { let deployer = get_caller_address(); self.owner.write(deployer); } #[external(v0)] impl OwnableImpl of super::OwnableTrait { fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { self.only_owner(); let prev_owner = self.owner.read(); self.owner.write(new_owner); self.emit(Event::OwnershipTransferred(OwnershipTransferred { prev_owner: prev_owner, new_owner: new_owner, })); } fn get_owner(self: @ContractState) -> ContractAddress { self.owner.read() } } #[generate_trait] impl PrivateMethods of PrivateMethodsTrait { fn only_owner(self: @ContractState) { let caller = get_caller_address(); assert(caller == self.owner.read(), 'Caller is not the owner'); } }} 您也可以在 Github 上找到这段代码。 结论 Cairo 编译器第 2 版为 Starknet 带来了新的语法,使智能合约代码看起来与 Cairo 本身更加一致,并且在扩展上更类似于 Rust。即使牺牲了更多繁琐的代码,安全方面的优势也值得权衡。 在本文中,我们没有触及关于新语法的所有内容,特别是如何与其他智能合约交互,但您可以阅读编译器的变更日志、阅读论坛上的这篇文章或观看 StarkWare 的 YouTube 频道上的视频来了解更多信息。 这个新版本的编译器将在几周内提供给 Starknet 的测试网,在几周后提供给主网,所以暂时不要尝试部署此代码,它还不能运行。 Cairo 一直在变得更好。 资源 合约语法——迁移指南 Cairo 1:合约语法在不断发展 查看更多 —- 编译者/作者:StarkNet 中文 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
Starknet改进语法全解读
2023-07-11 StarkNet 中文 来源:区块链网络
LOADING...
相关阅读:
- 以太坊区块空间未来的机遇和思考2023-07-11
- 跨链桥接连出事用户如何自救?2023-07-09
- Multichain疑遭攻击,多个桥合约出现代币异常流出2023-07-07
- 深入解析ERC-6551底层原理2023-07-06
- 金色Web3.0日报|南华证券:已成为首批香港持牌虚拟资产金融机构之一2023-07-05