NG#6 - 存储布局

这个任务是关于合约状态变量存储的。需要用类似 Solidty 中的 sstore 直接向智能合约的存储中的指定位置写入特定值。

下载任务后，可以看到需要用 storage_write_syscall 按要求在特定 slot 位置写 felt252，2 个参数都是 felt252 类型。

Copy
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，然后用如下代码就可以模拟合约部署，很方便。

Copy
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

Copy
#[derive(Copy, Drop, Serde, starknet::Store)]
struct Chest {
is_open: bool,
owner: starknet::ContractAddress
}

第四题是写入 Chest 结构体。其实第二题已经知道了 struct 是怎么计算位置的了。这题的唯一问题在于如何把一个 StarkNet 合约地址转为 felt252，我在源码中找到了对应方法。

总结

这题主要知识点是 StarkNet 的存储布局，不算难，弄懂文档很容易就能完成。