This task is about storing contract state variables. It requires writing specific values directly to the storage of the smart contract at specified positions, similar to the "sstore" function in Solidity.
After downloading the task, you can see that you need to use the "storage_write_syscall" to write "felt252" at a specific slot position. Both parameters are of type "felt252".
fn write(ref self: ContractState, slot_index: felt252, value: felt252) {
let storageAddress = slot_index.try_into().unwrap();
storage_write_syscall(0, storageAddress, value);
}
The StarkNet official documentation and the Cairo book provide explanations related to this topic. It is not specified which function is equivalent to "sn_keccak", but there are instructions in the task's workthrough.
In Cairo, the slot address is calculated based on the variable name, unlike in Solidity where it is continuous. This makes it easier to upgrade contract logic, as long as the attribute names remain the same, the slot addresses will remain the same.
The approach I took was to write local tests. After passing the tests, I printed out the slot address and value, and used starkli invoke write [slot_index] [value]
to provide the answers. This way, calling the contract function directly is simpler, so I didn't write a separate hack contract.
For testing logic, I used starknet-foundry, and with the following code, I could simulate contract deployment easily.
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#
The first entry code is a felt252 represented as a string. It can be directly written in the write
function. The only issue is that it needs to be printed and displayed as hex to be passed as an argument in the starkli
command.
u256#
The second subtask is to write a value of 10^40 as a u256. This involves understanding how to write exponentiation and how to store a u256.
I didn't find the relevant operators in the official core library, but the third-party library alexandria has an implementation. However, the simplest way is to directly write 10000000000000000000000000000000000000000_u256 for 10^40.
A u256 is stored in two parts: low and high. According to the official documentation, the first element of the struct is stored at "sn_keccak", and the second element is stored at "sn_keccak+1". So, after calculating the two positions, submit two transactions to write to u256.low and u256.high respectively.
map#
The third task is to write a LegacyMap::<u256, bool>, similar to Solidity's mapping. The documentation provides detailed explanations. If it were a Map, the position calculation would be h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)
.
Since the key is a u256 and occupies two positions, it needs to be hashed twice.
The hash function used is core::pedersen::pedersen
.
The corresponding felt252 values for the bool values are 0 and 1. Find three index positions and write 1 to them.
struct#
#[derive(Copy, Drop, Serde, starknet::Store)]
struct Chest {
is_open: bool,
owner: starknet::ContractAddress
}
The fourth task is to write a Chest struct. We already know how to calculate the position from the second task. The only issue here is how to convert a StarkNet contract address to felt252. I found the corresponding method in the source code.
Summary#
The main knowledge point of this task is the storage layout in StarkNet, which is not difficult. Understanding the documentation makes it easy to complete.