作者:Darington Nnam
原文:Journey through Cairo IV — A deep dive into Cairo’s Storage with Starklings
翻译:Louis Wang
校对:「StarkNet 中文社区」
欢迎来到「Cairo 之旅」第四讲,在上一讲中,我们学习了Cairo 中的 felt 数据类型和短字符串(Strings)。
像往常一样,如果你是中途加入的,建议从头开始看我们的文章。
P.S:教程中所有的语法代码都是在 Cairo v0.9.0 版本下执行
在这个系列的第二讲中,我们学习区分 Cairo 程序和 Cairo 合约,程序是无状态合约,而合约则运行在 Starknet VM 上,因此可访问持续(存储)状态。
合约存储是可书写,浏览和修改数据的持续存储空间。根据官方文档,它有 $2^{251}$ 个插槽,其中每个插槽都是初始化为 0 的 felt。
Cairo 中一个存储变量:
@storage_var
func id() -> (number: felt):
end
其中 @storage_var 被称为修饰器,用于指定存储变量。
不同于 solidity,Cairo 中所有的函数执行都由 func 关键字指定的,而且难以区分。为此 Cairo 使用修饰器区分这些函数。所有的修饰器都以 @ 开头。
以下是 Cairo 中常见的修饰器:
@storage_var - 指定状态变量
@constructor - 指定构造函数
@external - 指定书写状态变量的函数
@view - 指定从状态变量中读取的函数
@event - 用于指定事件
@l1_handler - 用于指定处理从 L1 合约信息桥所发送信息的函数
前文提及,向状态变量写入的函数,必须用 @external 修饰器来指定。列举 Cairo 函数的例子,它更新了前面的存储变量:
P.S:如果你不理解这里的所有内容,请不要担心,因为我们还没有完整学习过 Cairo 函数。
@external
func update_id{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr
}(_number: felt):
id.write(_number)
end
重点关注 id.write(_number),我们用变量名 .write()写入或者更新一个状态变量。
读取一个状态变量的值并不困难。如前文所述,必须用 @view 修饰器指定状态变量中读取的函数。从 id 状态变量读取的函数例子:
@view
func read_id{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr
}(_number: felt):
id.read()
end
注意关键词 id.read(_number),类似写入状态的方式,只要用变量名 .read() 即可。
不同于 Solidity 会映射其本身的特殊关键字,Cairo 使用存储变量进行映射。
在前文关于 id 的例子中,我们的状态变量只存储了一个值,但也可以创建更复杂的状态变量,即键 -> 值对:
@storage_var
func balance(address: felt) -> (amount: felt):
end
状态变量 balance 在这里是地址到所持数量的映射。
要写到这种类型的状态变量,需要同时提供键 (address) 和值 (balance)
@external
func update_balance{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr
}(_address: felt, _amount: felt):
balance.write(_address, _amount)
end
如你所见,我们在圆括号内提供了键和值,因为需指定被更新的值和键。
然后在状态变量中读取:
@view
func read_balance{
syscall_ptr : felt*,
pedersen_ptr : HashBuiltin*,
range_check_ptr
}(_address: felt, _amount: felt):
balance.read(_address)
end
在这里只使用了键 (address),因为我们只想获得该特定键的值,而不是更新或改变它。因此,使用 balance.read(_address),返回的是那个特定地址的数量或余额。
在理解了上述知识点后,直接进入 Starklings 的实操测试吧!
在这里,我们要创建名为 bool 的存储并存入单个 felt。
它类似第一个 id 的存储示例。因此,我们要创建 bool 状态变量的方法:
@storage_var
func bool () -> (value: felt):
end
看看是否通过测试…
成功!进入下一步!
这一步我们将探索如何存储结构 (Structs)
三个步骤:
创建一个名为 wallet 的存储,将一个 felt 映射到另一个。
类似于我们在 balance 状态变量(键到值的映射)中所做的:
@storage_var
func wallet (id: felt) -> (amount: felt):
end
创建一个名为 height_map 的存储,将两个 felt 映射到另一个。
同样类似 balance 状态变量时所做的,不同的是将两个键映射到一个值。
@storage_var
func height_map (length: felt, width: felt) -> (height: felt):
end
最后一步有点难度,需要将一个 felt,映射到一个 Id (struct)。用户值是一个 Id 类结构。
@storage_var
func id (address: felt) -> (user: Id):
end
成功运行!
这里要求实现 external 和 view 函数读取和写入 bool 状态变量。
上文所述,当一个函数写入一个存储变量时,作为外部函数应该用 @external 装饰器来指定,而当从存储变量中读取,它是视图函数,应该用 @view 修饰器来指定。
第一个需要修改的函数是 toggle 函数,它应该在调用时更新 bool 状态变量。
问题是一个布尔值 (Boolean Value) 只能为 0 或 1,所以需要通过以下方式实现,即每次调用 toggle 函数时,如果布尔值是 0,我们就将其更新为 1,反之亦然。所以建议我们使用 conditionals:
@external
func toggle{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
let (value) = bool.read()
if value == 0:
bool.write(1)
else:
bool.write(0)
end
return ()
end
接下来,我们需要修改 view_bool 函数在调用时返回 bool 状态变量的值。
@view
func view_bool{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}() -> (bool : felt):
let (value) = bool.read()
return (value)
end
完整代码如图:
成功通过!
恭喜你已经掌握了 Cairo 的存储!
如果你在实操练习中遇到了困难,可以在我的 repo 里找到练习的答案。
在下一课中,我们将研究隐式参数 (Implicit Arguments)。
如果觉得本教程对你有帮助,转发分享给其他人吧~