开始使用 Cairo 1.0

通过一人了解 关于Cairo 1.0 新特性和改进的所有信息

Getting Started With Cairo 1.0

去年9月,StarkWare 发布了两个重要的公告,这将大大改善网络上的开发者体验。

它们是:

  1. Cairo 1.0: 对Starknet的原生编程语言Cairo进行了巨大的升级,承诺为Starknet开发者提供更安全、更简单和更好的编程体验。
  2. Sierra: 一个中间表示层,将为Cairo程序提供更大的长期稳定性。

许多开发人员在使用旧版本的Cairo编写代码时面临的繁琐体验将成为过去,因为Cairo 1.0使编写代码明显变得更容易、更整洁,因为该语言变得与Rust相似。

随着Cairo的这一变化,它还有一个额外的好处,就是让熟悉Rust的新开发者进入Starknet生态系统。

这就是为什么Cairo 1.0,随着Sierra的引入,让Starknet开发者社区兴奋,而且是有理由的。

在这篇文章中,我们将教你如何编写和编译你的第一个Cairo 1.0合约,并探索所有随着改进而来的新功能。现在,让我们一起开始吧。

设置开发环境

在我们开始写代码之前,我们需要建立开发环境。不幸的是,你可能熟悉的Starknet工具,如Protostar、Nile或官方Starknet CLI开发,目前不支持Cairo 1.0。这意味着我们需要使用一个替代的解决方案...

我们已经解决了这个问题,通过克隆和修改官方 repo,创建了一个Cairo 1.0的自定义开发环境。你将需要克隆这个 repo 来编写和编译你的 Cairo 1.0 合约。

设置你的Cairo 0.1开发者环境

我们所有的合约都将被放入src文件夹。我们已经创建了一个模板合约,我们将在本文的下一节将其编译为sierracasm

一旦我们克隆了 repo,我们就可以继续激活 Cairo 1.0 VS代码扩展来实现语法高亮。

要做到这一点:

  1. 安装Node.js 18 LTS

  2. 导航到vscode-cairo文件夹中

在终端中运行以下命令:

sudo npm install --global @vscode/vsce

npm install

vsce package

code --install-extension cairo1*.vsix
  1. 构建语言服务器:
cargo build --bin cairo-language-server --release
  1. 在你的当前目录中打开Vscode

运行这些命令:

npm install

npm run compile
  1. 最后,重新加载VScode并按F5键

PS: 你应该在VScode中的settings.json文件中添加这个额外的代码片段,以确保事情正确运行。

"cairo1.languageServerPath": "/Some/Path/target/release/cairo-language-server",

成功地设置你的Cairo 0.1开发环境

如果你的安装成功了,前往src/hello.cairo,你应该激活了语法高亮,如上图所示。

编译你的第一个Cairo 1.0合约

在建立了开发环境之后,现在可以开始编译我们的Cairo 1.0合约。

我们将使用位于src/hello.cairo的一个简单的合约:

#[contract]

mod HelloStarknet {

   struct Storage {

       balance: felt,

   }

   #[external]

   fn increase_balance(amount: felt) {

       balance::write(balance::read() + amount);

   }

   #[view]

   fn get_balance() -> felt {

       balance::read()

   }

}

这个合约实现了简单的函数来增加和获得余额变量。#[contract]标志着我们正在编写一个Starknet合约,而不是一个Cairo程序。

与版本0.x合约不同的是,0.x 直接编译成Cairo汇编(casm),在Cairo 1.0中,编译我们的合约将分两个阶段进行。

  1. Cairo > Sierra
  2. Sierra > Casm

Sierra代表安全中间表示法,旨在构成Cairo程序和其字节码之间的表示层。Sierra开启证明每个Cairo运行的可能性,从而允许强大的拒绝服务攻击(DoS)保护。

要编译到Sierra,请运行下面的命令:

cargo run --bin starknet-compile -- src/hello.cairo src/hello.sierra --replace-ids

如果编译成功,你应该在你的 src/hello.sierra中看到Sierra的输出。

要进一步从Sierra编译到Casm,请运行下面的命令:

cargo run --bin starknet-sierra-compile -- src/hello.sierra src/hello.casm

如果编译成功,你应该在你的 src/hello.casm中看到casm的输出。

Starknet Alpha v0.11.0还没有发布,所以我们不能把我们编译的合约部署到testnet或mainnet上。然而,这个网络升级预计将在2023年2月底发生。

你需要了解的Cairo 1.0关键功能

要想了解Cairo 1.0的所有新功能,一篇文章是不够的,但我们将在本节中探讨一些关键功能:

一个新的语法

如前所述,Cairo 1.0带有一个更干净、更好的语法,其灵感来自Rust。这包括采用类似Rust的特性,如traits和它们的实现,模式匹配,panic等。

语言指令和导入

通过Cairo 1.0,要开始编写Starknet合约,你必须指定宏:

#[contract]

虽然每个Cairo程序都必须实现一个主函数,但导入一个函数也变得更加容易。

你可以像这样做:

use starknet::get_caller_address;

另外,你也可以导入整个 "包":

use starknet;

并像这样使用它:

starknet::get_caller_address() 

数据类型

Starknet生态系统的标志是felt数据类型,当然,它们也不会很快消失!

除了felt数据类型外,还支持其他Integer类型,如u256、u128、u64、u32和u8。

虽然这些Integer类型是在幕后使用felts实现的,但它们被认为使用起来更安全,现在可以支持算术运算,而不需要自定义库。例如,你现在可以执行如下操作。

let sum = 1_u128 + 2_u128;

let product = 5_u256 * 10_u256;

这些运算符的使用是有溢出保护的,如果检测到溢出,会引起panic。

还支持类似于Solidity地址的contractAddress数据类型,该类型最近才在Cairo 0.1中实现。

Type 常量

数字常量,如1,2,3,默认为felt。然而,你可以通过附加数据类型为它们指定一个不同的数据类型,就像这样:

let num_u256 = 1_u256

统一使用 Let 关键字

在Cairo 1.0中,我们最终消除了多种变量声明模式(tempvar、local等),而只接受使用let关键字。

这非常有帮助,因为我们终于告别了撤销引用(revoked references) 的编译器错误。

现在你也可以使用mut关键字来创建可变的变量。

结构和存储变量

Cairo 1.0没有使用@storage_var修饰器,而是将一个合约分为三个主要部分:一个存储结构、合约接口(traits)和其实现。

所有的存储变量都被打包到一个名为Storage的结构中,尽管这可能会被改变。

在写这篇文章的时候,它看起来是这样的:

struct Storage {

   name: felt,

   symbol: felt,

   decimals: u8,

   total_supply: u256,

   balances: LegacyMap::<felt, u256>,

   allowances: LegacyMap::<(felt, felt), u256>,

}

并可以用这种方式读或写:

let name = name::read()

let name = name::write(_name)

可以使用LegacyMap关键字创建映射,其中映射变量的数据类型被插入到<>之间。你也可以映射元组(tuples)。

事件

事件允许合约在其执行过程中发出信息,这些信息可以在Starknet之外使用。

一个事件可以像这样创建:

#[event]

fn Transfer(from_: felt, to: felt, value: u256) {}

并随后以这种方式发出:

Transfer(sender, recipient, amount);

函数

Cairo 1.0中的函数与0.x版本相似,只是省略了隐含参数,并按照Rust的惯例将func关键字改为fn。下面是一个Cairo 1.0中基本函数签名的例子:

fn balance_of(account: felt) -> u256 {

   ...

}

你还会注意到,返回变量不再包含变量名,而只是其数据类型。

返回语句

就像Rust一样,Cairo 1.0中的函数既可以通过省略结尾的分号来隐含地返回值,就像这样:

#[view]

fn get_name() -> felt {

   name::read()

}

在此案例中,函数的最终表达式会自动返回,或者通过使用return关键字显式返回,像这样:

#[view]

fn get_name() -> felt {

   return name::read();

}

修饰器

修饰器在Cairo 1.0中仍然存在,但有一个类似于Rust的宏的新语法。

你可以像这样声明一个修饰器:

#[external]

#[view]

枚举和模式匹配

枚举(Enum)是一种特殊的数据类型,由你定义的一组固定的常数组成。

你可以在Cairo 1.0中创建一个Enum,像这样:

enum Animals { Goat: felt, Dog: felt, Cat: felt }

Cairo 1.0 也使我们能够使用 match 关键字创建类似于 Rust 的模式匹配。当与Enum结合时,模式匹配可以让我们根据遇到的数据变量来调整函数的行为:

enum Colors { Red: felt, Green: felt, Blue: felt }

func get_favorite_color() -> Colors {

   Colors::Green(0)

}

func main() -> felt {

   let my_color = get_favorite_color();

   let result = match my_color {

       Colors::Red(_) => {

           1

       },

       Colors::Green(_) => {

           2

       },

       Colors::Blue(_) => {

           3

       },

   };

   result // returns 2

}

数组

Cairo 1.0使数组操作变得更加容易,因为核心库除了导出一个数组类型外,还有一些相关的函数,如append, array_at, 和array_len。一些基本的数组操作包括向现有的数组添加新元素,获得元素索引,获得数组长度等。这些都可以通过ArrayTrait访问。

fn fib(n: usize) -> (Array::&lt;felt>, felt, usize) {

   let mut arr = ArrayTrait::new();

   arr.append(1);

   arr.append(1);

   let mut arr = fib_inner(:n, :arr);

   let len = arr.len();

   let last = arr.at(len - 1_usize);

   return (arr, last, len);

}

错误信息和访问控制

你可以在Cairo中创建自定义错误,在执行失败时输出给用户。这对于实现检查和适当的访问控制机制非常有用。

在Cairo 1.0中,你可以很容易地使用assert语句来做到这一点,其模式与Solidity中的require语句相似。

assert(sender != 0, 'ERC20: transfer from 0');

其中错误信息必须小于31个字符。

Traits 及其实现

Cairo 1.0引入了traits和实现。把traits看作是一种特殊的合约接口,因为它们定义了一个特定类型的功能,并且可以与其他类型共享。它们是用trait关键字定义的:

trait IContract{

   fn set_something(num: felt, pair: felt);

   fn get_something(num: felt) -> felt;

}

另一方面,一个实现了特定的trait行为的实现中,需要包含 trait中的所有定义函数。

它们是用impl关键字定义的,像这样:

impl Contract of IContract {

   fn set_something(num: felt, pair: felt) {

       number_pair::write(num, pair)

   }

   fn get_something(num: felt) -> felt {

       number_pair::read(num)

   }

}

泛型

Cairo 1.0与Rust类似,也支持泛型,这使你能够编写灵活的代码,可以与多种类型一起工作,而不是被束缚在一个特定的类型上。

一个类型参数通过使用角括号<>来指定为泛型,像这样:

fn foo&lt;T>(arg: T) -> T {
    …
 }

Replace_class sysall

在Cairo 1.0和Starknet v0.11中,将增加一个新的syscall,使你能够在不改变合约的入口点的情况下交换底层的合约实现(class hash)。想想看,一个默认的代理!

最重要的是,你只需用一行代码就可以做到这一点:

#[contract]

mod upgradeable {
    use starknet::replace_class;

.   ….

    #[external]
    fn upgradeable(cls_hash: felt) {
        replace_class(cls_hash);
    }
}

结论

恭喜你,你刚刚编写并编译了你的第一个Cairo 1.0合约,并了解了Cairo 1.0的关键特性

再次强调,Cairo 1.0仍然是一项正在进行中的工作,而且发展很快。新的功能每天都在增加,所以要经常查看官方版本Discord讨论频道,以保持最新的信息!

这里有一些额外的资源,我们认为会对你的Cairo 0.10之旅有所帮助。

  1. Cairo1.0初探: 一个更安全、更强大、更简单的可验证编程语言

  2. Cairo 1.0--变化、特性、发布日期

  3. https://github.com/starkware-libs/cairo/blob/main/docs/reference/src/SUMMARY.adoc

  4. https://github.com/argentlabs/starknet-build/tree/main/cairo1.0/examples

  5. [Cairo 1.0 by StarkWare](https://medium.com/starkware/cairo-1-0-aa96eefb19a0#:~:text=Introducing Sierra%3A ensuring every Cairo run can be proven)

如果你对此有任何问题,请联系我@0xdarlington,我很乐意帮助你用Argent X建立Starknet。

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

0 条评论

请先 登录 后评论
翻译小组
翻译小组
0x9e64...7c84
大家看到好的文章可以在 GitHub 提 Issue: https://github.com/lbc-team/Pioneer/issues 欢迎关注我的 Twitter: https://twitter.com/UpchainDAO