這個任務涉及到合約狀態變量存儲。需要使用類似 Solidity 中的 sstore 直接將特定值寫入智能合約存儲的指定位置。
下載任務後,可以看到需要使用 storage_write_syscall 按要求在特定的 slot 位置寫入 felt252,這兩個參數都是 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 函數中可以直接寫入,唯一的問題是需要打印出來以十六進制形式傳入 starkli 命令才能發送交易。
u256#
第二個子任務是寫入值為 10^40 的 u256。涉及到如何計算乘方和如何存儲 u256。
官方核心庫中沒有看到相關的操作符,三方庫 alexandria 中有實現。不過最簡單的方法是直接寫入 10000000000000000000000000000000000000000_u256。
u256 分為 low 和 high 兩部分存儲,作為一個 struct。根據官方文檔,struct 中的第一個元素存儲在 sn_keccak,然後是 sn_keccak+1。所以,計算出兩個位置後,提交兩筆交易分別寫入 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,需要佔用兩個位置,所以需要對其進行兩次哈希。
哈希使用的函數是 core::pedersen::pedersen
。
傳入的 bool 值對應 felt252 就是 0 和 1,找到三個索引位置並寫入 1 即可。
struct#
#[derive(Copy, Drop, Serde, starknet::Store)]
struct Chest {
is_open: bool,
owner: starknet::ContractAddress
}
第四個任務是寫入 Chest 結構體。其實,第二個任務已經知道了 struct 是如何計算位置的。這個問題的唯一問題在於如何將一個 StarkNet 合約地址轉為 felt252,我在源碼中找到了相應的方法。
總結#
這個問題的主要知識點是 StarkNet 的存儲布局,不算難,只要弄懂文檔就能輕鬆完成。