Starknet 训练营-课程笔记3

为什么在合约中表示金额时更推荐使用 u256 而不是 felt252?

在 Starknet 的 Cairo 开发中,选择 u256 而不是 felt252 来表示金额是一个非常关键的工程实践。这不仅关乎安全性,也关乎与区块链生态(尤其是以太坊)的兼容性。

针对你提到的几个观点,我们来做一下事实核查和深度解析:

1. 核心差异对比

特性 felt252 (Field Element) u256 (Unsigned 256-bit Integer)
数值范围 (其中 是一个约 252 位的质数)
溢出行为 模运算(不会 Panic) 触发 Panic(默认检测溢出)
语义化 密码学原语,不直观 标准整数,符合金融直觉
兼容性 仅限 Starknet 内部 完美适配 ERC-20 等以太坊标准

2. 深度解析:为什么选 u256

防止隐式溢出(关键安全点)

你说 u256 会在溢出时触发 panic,这是完全正确的。

  • felt252 的陷阱:当你对 felt252 进行加法运算并超过其质数模 时,它会发生“回绕”(Wrap around),且不会报错。这在处理金额时是致命的,可能导致黑客通过溢出手段盗取资金。
  • u256 的保护:Cairo 中的 u256 实际上是由两个 u128 组成的结构体。在进行算术运算时,标准库会自动检查溢出。如果结果超出 ,合约会立即终止,保护资产安全。

数值精度的匹配

以太坊上的 ERC-20 代币标准规定金额通常为 256 位。

  • felt252 的最大值略小于 。
  • 如果你用 felt252 来接收一个从以太坊跨链过来的 大小的金额,felt252 根本存不下,会导致数据丢失或截断。

纠正两个误区

  • 关于字符串:其实 u256 并不直接“支持字符串”,它本质上还是数字。但在 Cairo 中,你可以方便地将短字符串(Short strings)转换为数值存储。
  • 关于性能:实际上,felt252 的性能反而更好。因为它是 Cairo 的底层原生类型,所有的计算都是 的 CPU 指令。u256 作为结构体,其加减乘除需要多步 felt252 运算组合而成。我们选择 u256 是为了安全牺牲了少许性能。

3. 你的“排除错误选项”逻辑

正如你之前提到的:“当我排除了所有错误选项,那么剩下的就是正确选项。

在金融合约设计中:

  1. 排除 felt252:因为它会隐式溢出(错误选项)。
  2. 排除 felt252:因为它无法覆盖 的完整范围(错误选项)。
  3. **剩下 u256**:虽然它更“重”,但它在逻辑上是严密的、安全的、符合行业标准的。

4. 总结

在 Starknet 上写书或开发合约时,记住这个原则:底层的密码学逻辑(如签名验证)用 felt252,业务上的资产金额、计数器一律使用 u256 这不仅是对权威(底层架构)的理解,更是对自己代码逻辑负责的“预判”。

既然你正在筹备写书,需要我帮你用 Cairo 语言写一个简单的金额溢出对比案例吗?我可以演示 u256 是如何通过 Panic 保护资金安全的。

Cairo 中的变量默认是?可变的;不可变的

你想知道Cairo语言中变量的默认可变性,核心结论先明确:

正确答案

Cairo 中的变量默认是【不可变的(immutable)】

详细解析

1. 核心规则:默认不可变,显式声明可变

Cairo 借鉴了 Rust 等语言的设计理念,所有变量默认具备不可变性,目的是提升代码的安全性和可预测性(尤其是在处理资产的智能合约场景中,避免意外修改变量导致的逻辑错误)。

  • 若要让变量可修改,必须显式添加 mut 关键字;
  • 未加 mut 的变量,赋值后无法二次修改,编译时会直接报错。

2. 代码示例(直观理解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 示例1:默认不可变变量(无法修改)
fn immutable_var() {
let x = 10; // 默认不可变
// x = 20; // 编译报错:Cannot assign to immutable variable `x`
}

// 示例2:显式声明可变变量(可修改)
fn mutable_var() {
let mut y = 10; // 加mut关键字,变为可变
y = 20; // 编译通过,可正常修改
y += 5; // 支持自增等修改操作
}

// 示例3:合约中金额变量(实战场景)
#[starknet::contract]
mod Token {
#[storage]
struct Storage {
// 存储变量的可变性由storage规则决定,但局部变量仍遵循默认不可变
balance: u256,
}

#[external(v0)]
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
let mut sender_balance = self.balance.read(); // 局部变量默认不可变,加mut才能修改
sender_balance = sender_balance - amount; // 编译通过
self.balance.write(sender_balance);
}
}

3. 设计初衷(为什么默认不可变?)

Cairo 作为面向 ZK-Rollup 智能合约的语言,安全性是核心:

  • 减少意外修改:默认不可变可避免开发者误改关键变量(如金额、权限参数),降低资产安全漏洞;
  • 提升可证明性:不可变变量的执行轨迹更稳定,有助于 CairoVM 生成准确的 STARK 有效性证明;
  • 符合区块链特性:区块链数据强调“确定性”,不可变变量的逻辑更易审计和验证。

补充:特殊场景的可变性

  • 存储变量(Storage):合约的存储变量(如示例3中的balance)本身没有“默认不可变”的限制,需通过read()/write()方法修改,但其读取到局部变量后,仍遵循“局部变量默认不可变”的规则;
  • 函数参数:函数参数默认也不可变,若需在函数内修改参数值,需添加mut(如fn update(mut num: u256))。

总结

  1. Cairo 局部变量、函数参数默认不可变,必须加 mut 关键字才能修改;
  2. 该设计是为了适配智能合约的安全需求,减少意外修改导致的逻辑错误;
  3. 存储变量的修改需通过read()/write(),但局部变量仍遵循默认不可变规则。

需要我整理一份Cairo变量可变性的核心规则速记表,包含局部变量、函数参数、存储变量的可变性差异吗?

快照@和引用ref哪一项说法正确?

二者都会修改变量;

快照可写,引用只读;

快照不转移所有权且只读,引用不转移所有权且可写;

二者都会转移所有权


在 Cairo 编程语言(用于 Starknet 合约开发)中,你给出的选项中,正确的说法是:

“快照(Snapshot)不转移所有权且只读,引用(Reference)不转移所有权且可写。”


详细对比:快照 @ vs 引用 ref

为了帮你更清晰地排除“错误选项”,我们可以通过下表看透二者的本质:

特性 快照 @ (Snapshot) 引用 ref (Reference)
符号/关键字 @variable ref variable
可变性 只读(不可修改原变量) 可写(可以修改原变量)
所有权 不转移(原函数保留所有权) 不转移(隐式返回所有权)
底层逻辑 该时间点的“不可变视图” 相当于自动返回新值的“语法糖”
常见场景 只需读取数据(如计算 Array 总和) 需要在函数内修改状态(如 increment

为什么其他选项是错误的?

  1. “二者都会修改变量”:错误。快照 @ 是绝对只读的,编译器会阻止任何尝试修改快照内容的操作。
  2. “快照可写,引用只读”:错误。这完全记反了。在 Cairo 中,ref 专门用于需要修改(Mutate)传入参数的情况。
  3. “二者都会转移所有权”:错误。
    • 快照 @ 只是给函数看了一眼数据,所有权还在调用者手里。
    • 引用 ref 稍微复杂点:它在进入函数时拿走所有权,但在函数结束时自动(隐式)归还所有权。所以从开发者的体感来看,它也没有“失去”所有权。

你的“预判”应用

  • 当你看到一个函数只需要读取你的 Array(比如检查余额),你应该预判到:不应该转移所有权(否则后面没法用了),也不需要修改
  • 根据这个预判,你唯一正确的选择就是使用 快照 @

你想让我为你写一段对比代码,直观地展示在什么情况下编译器会报错(例如尝试修改快照)吗?

Cairo Tutorial Series Part 11: References and Snapshots

这个视频详细演示了如何在 Cairo 中使用 ref 和 @ 来管理变量的可变性与所有权,能帮你更直观地理解二者的区别。

Span 和 Array 的主要区别是?

在 Cairo 编程语言中,关于 SpanArray 的区别,正确选项是:

“Span 是只读的数组视图。”


核心区别:Array vs Span

在 Starknet 开发中,理解这两者的关系非常重要。你可以把 Array 看作是真实的存储实体,而 Span 是观察这些实体的**“快照窗口”**。

特性 Array (数组) Span (快照/视图)
本质 数据的持有者(可动态增加) 数据的引用/快照(不可变视图)
可变性 有限可写(只能追加 append 绝对只读
内存成本 较高(涉及所有权转移) 极低(几乎免费的轻量级结构)
典型用途 构造、收集数据 遍历数据、作为函数参数(防止修改)

为什么其他选项是错误的?

  1. “Span 可以修改元素,Array 不可以”
    • 错误。恰恰相反,Span 设计出来的初衷就是为了提供只读访问。而 Array 虽然在 Cairo 的不可变内存模型中无法直接通过索引修改旧值,但它是允许通过 append() 增加新元素的。
  2. “Span 只能存 felt252”
    • 错误Span<T>泛型的。它可以是 Span<u256>Span<felt252>,甚至是 Span<MyStruct>。它的灵活性非常强。
  3. “Array 不能被遍历”
    • 错误。虽然 Array 的底层是类似队列的结构,但你完全可以通过递归或循环配合 get() 方法来遍历它。不过,在 Cairo 的最佳实践中,我们通常会先用 .span() 将数组转为 Span 再进行遍历,因为 Span 专门为这种“只读查看”优化过。

你的“排除法”与工程直觉

  • 如果一个函数只是想看看数据(比如计算总和),为什么要冒着被修改的风险把整个 Array 传进去?
  • 预判: 应该传一个无法被修改的“快照”。
  • 结论: 于是 Span 成了这个场景下的唯一正确选项。

既然你提到要在书中使用“排除法”逻辑,需要我为你提供一段对比 ArraySpan 在函数传递中所有权变化的 Cairo 代码示例吗?

Understanding Arrays in Cairo

这个视频详细讲解了 Cairo 中数组的操作及其与内存的关系,能帮你更深入地理解为什么 Span 作为“只读视图”在高性能合约开发中如此重要。

command not found

┌──(root㉿kali)-[/home/kali]
└─# scarb –version
snforge –version
sncast –version
starknet-devnet –version
scarb: command not found
snforge: command not found
Command ‘sncast’ not found, did you mean:
command ‘sbcast’ from deb slurm-client
Try: apt install
starknet-devnet: command not found


你现在遇到的问题是在Kali Linux中执行scarbsnforgesncaststarknet-devnet这些StarkNet开发工具命令时,系统提示“command not found”,核心原因是这些工具尚未安装,我会一步步教你完整的安装流程,确保能正常使用。

一、先明确工具的作用(帮你理解安装的意义)

  • scarb:StarkNet/Cairo的包管理器和构建工具(类似Solidity的Hardhat/Truffle);
  • snforge:Cairo合约的测试框架;
  • sncast:StarkNet合约的部署/交互工具;
  • starknet-devnet:本地StarkNet测试节点(类似以太坊的Ganache)。

二、安装前置条件

Kali Linux默认可能缺少依赖,先安装基础环境:

1
2
3
4
5
# 更新系统包
apt update && apt upgrade -y

# 安装必要依赖(Python3、pip、curl、build-essential等)
apt install -y python3 python3-pip curl build-essential libssl-dev pkg-config git

三、分步安装所有工具

1. 安装Scarb(核心工具,snforge/sncast依赖它)

Scarb的官方安装方式是通过脚本,适配Linux/macOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 下载并执行官方安装脚本
curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh

# 配置环境变量(让系统识别scarb命令)
# 若用bash
echo 'export PATH="$HOME/.local/share/scarb/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# 若用zsh(Kali默认可能是zsh)
echo 'export PATH="$HOME/.local/share/scarb/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

# 验证安装
scarb --version

✅ 正常输出示例:scarb 2.6.1 (f123456 2025-01-01)

2. 安装snforge和sncast(Scarb插件)

snforge/sncast是Scarb的官方插件,安装Scarb后直接通过scarb命令安装:

1
2
3
4
5
6
7
8
9
# 安装snforge(测试框架)
scarb install snforge

# 安装sncast(交互工具)
scarb install sncast

# 验证安装
snforge --version
sncast --version

✅ 正常输出示例:snforge 0.12.0 / sncast 0.12.0

3. 安装starknet-devnet(本地测试节点)

starknet-devnet基于Python,通过pip安装:

1
2
3
4
5
# 安装starknet-devnet(指定稳定版本,避免最新版兼容问题)
pip3 install starknet-devnet[rocksdb] --upgrade

# 验证安装
starknet-devnet --version

✅ 正常输出示例:starknet-devnet 0.7.1

四、常见问题排查

如果安装后仍提示“command not found”,按以下步骤排查:

  1. 环境变量未生效
    • 重新执行source ~/.bashrcsource ~/.zshrc
    • 手动检查PATH:echo $PATH,确认包含$HOME/.local/share/scarb/bin
  2. pip安装的starknet-devnet不在PATH
    • 找到pip安装路径:pip3 show starknet-devnet | grep Location
    • 将该路径下的bin目录加入PATH,例如:
      1
      2
      echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
      source ~/.bashrc
  3. 权限问题
    • 避免用root用户安装(建议切换到kali普通用户),若已用root,执行chown -R kali:kali ~/.local修复权限。

五、验证所有工具是否可用

执行以下命令,若都输出版本号则安装成功:

1
2
3
4
scarb --version
snforge --version
sncast --version
starknet-devnet --version

总结

  1. 核心问题是工具未安装+环境变量未配置,先装Scarb(snforge/sncast依赖),再装starknet-devnet;
  2. 安装后必须配置PATH环境变量,否则系统识别不到命令;
  3. Kali默认可能用zsh,需注意环境变量文件是.zshrc而非.bashrc

如果安装过程中遇到具体报错(比如依赖缺失、网络问题),可以把报错信息贴出来,我帮你针对性解决。