这个任务是关于合约状态变量存储的。需要用类似 Solidty 中的 sstore 直接向智能合约的存储中的指定位置写入特定值。
下载任务后,可以看到需要用 storage_write_syscall 按要求在特定 slot 位置写 felt252,2 个参数都是 felt252 类型。
fn write(ref self: ContractState, slot_index: felt252, value: felt252) {
let storageAddress = slot_index.try_into().unwrap();
storage_write_syscall(0, storageAddress, value);
}
StarkNet 官方文档 和 Cairo book 都有相关内容的解释。未指明的是哪个函数等同于 sn_keccak,在任务的 workthrough 里也有指引。
在 Cairo 中 slot 地址是变量名计算得到的,不像 Solidity 中是连续的,更方便合约逻辑升级,只需要保证属性命名相同,slot 地址就是相同的。
我采取的方式是本地写测试,通过测试后,print 出 slot 的地址和值,用 starkli invoke write [slot_index] [value]
来解答。这样直接调用合约函数更为简单,就不单独写 hack 合约了。
测试逻辑我使用的是 starknet-foundry,然后用如下代码就可以模拟合约部署,很方便。
use src::contracts::catacombs::{ICatacombsDispatcher, ICatacombsDispatcherTrait};
use snforge_std::{declare, ContractClassTrait};
#[test]
fn test_1() {
let contract = declare('Catacombs');
let contract_address = contract.deploy(@ArrayTrait::new()).unwrap();
let dispatcher = ICatacombsDispatcher { contract_address };
...
felt252#
第一个 entry_code 是个字符串表示的 felt252,在 write 里可直接写,唯一问题是需要打印出来显示成 hex 传入 starkli 命令才能发交易。
u256#
第二个子任务是写入值为 10^40 的 u256。涉及如何写乘方和如何存储 u256。
官方核心库里没看到相关操作符,三方库 alexandria 库里有实现。不过最简单的,10 的 40 次方可以直接写 10000000000000000000000000000000000000000_u256。
u256 分为 low 和 high 两部分存储为 1 个 struct。根据官方文档,struct 中的第一个元素存储在 sn_keccak,然后是 sn_keccak+1。所以这题算出 2 个位置后,提交 2 笔交易分别写入 u256.low 和 u256.high 即可。
map#
第三题是写入 LegacyMap::<u256, bool>,类似 Solidity 的 mapping,文档里有详细说明。如果是 Map,slot 的位置计算方式是 h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)
。
由于 key 是 u256,需要占 2 个位置,所以得 hash 两次。
hash 使用的函数是 core::pedersen::pedersen
。
传入的 bool 值对应 felt252 就是 0 和 1,找到 3 个索引位置写入 1 即可。
struct#
#[derive(Copy, Drop, Serde, starknet::Store)]
struct Chest {
is_open: bool,
owner: starknet::ContractAddress
}
第四题是写入 Chest 结构体。其实第二题已经知道了 struct 是怎么计算位置的了。这题的唯一问题在于如何把一个 StarkNet 合约地址转为 felt252,我在源码中找到了对应方法。
总结#
这题主要知识点是 StarkNet 的存储布局,不算难,弄懂文档很容易就能完成。