這個任務是關於合約狀態變數存儲的。需要用類似 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 的存儲佈局,不算難,弄懂文檔很容易就能完成。