Starknet 改进语法全解读

Starknet 改进语法全解读

原文Improved Starknet Syntax 翻译及校对:「Starknet 中文社区」

概要

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 <https://docs.swmansion.com/scarb/install.sh> | 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 (<https://crates.io/crates/cairo-lang-compiler/2.0.0-rc3>)```  
$ scarb new cairo1_v2$ cd cairo1_v2```  

您应该得到如下所示的文件夹结构。

$ tree .
>>>.
├── Scarb.tom
l└── 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<T> { fn transfer_ownership(ref self: T, new_owner: ContractAddress); fn get_owner(self: @T) -> ContractAddress; }

[starknet::contract]

mod Ownable { ... }```

如果您还记得 v1 中的原始代码,我们的智能合约有两个「公共」方法(get_ownertransfer_ownership)和一个「私有」方法(only_owner)。这一特征仅处理公共方法,而不依赖于「外部」或「视图」属性来表示哪个方法可以修改合约的状态,哪个方法不允许。相反,现在通过参数 self 的类型来明确这一点。
如果一个方法需要引用 ContractStorage(一旦实现,通用 T 就是这样),该方法就能够修改智能合约的内部状态。这就是我们过去所说的“外部”方法。另一方面,如果一个方法需要 ContractStorage 的快照,那么它只能读取它,而不能修改。这就是我们过去所说的“视图”方法。
现在,我们可以使用关键字 impl 为刚刚定义的特征创建一个实现。请记住,Cairo 与 Rust 的不同之处在于,实现是具备名称的。

use starknet::ContractAddress;

#[starknet::interface]
trait OwnableTrait&lt;T> {
    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&lt;ContractState> {
        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&lt;ContractState> {
        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&lt;ContractState> {
        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&lt;T> {
    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&lt;ContractState> {
        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:合约语法在不断发展

原文地址:https://medium.com/starknet-edu/the-improved-starknet-syntax-6012f8e35a32
Mirror: https://mirror.xyz/starknet-zh.eth/Ed3FVoX6YwyuVvcydZghbDFAteV1uh01vqCdgC1BKdM

关于我们

Starknet 由 StarkWare 开发,采用 STARK 有效性证明,是为未来规模化应用而建的开放式以太坊 Layer2 网络。

「Starknet 中文」是 Starknet 社区项目,致力于 Starknet 在中文社区推广。

Twitter: https://twitter.com/StarkNet_ZH
Substack: https://starknetzh.substack.com/
Mirror: https://mirror.xyz/starknet-zh.eth
GitHub: https://github.com/StarkNet-ZH/Awesome-StarkNet-Chinese
Discord: https://discord.gg/R8A879b8x3
Telegram: https://t.me/starknet_zh

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

0 条评论

请先 登录 后评论
Starknet 中文
Starknet 中文
江湖只有他的大名,没有他的介绍。