Storing custom structs in Cairo 1
Let's imagine you are writing a Starknet smart contract in Cairo 1 and you want to store some complex data as the value of a map in your contract's storage. You probably want to use a custom struct to save that complex data but you'll find a small surprise.
You might define your custom struct like this
and use it on the Storage struct like so
So far, so good. This makes perfect sense and it's the correct way of doing it. However, depending on your Cairo version, and at the time of writing, when you attempt to compile your contract, you might get the following error.
What's the problem here? What this means is simply the StorageAccess Trait has no implementation of the
read functions needed to access your CustomStruct in the Storage struct. For native types, this is automatically derived but since you defined your own CustomStruct, the compiler has no idea what to do.
Note: this is a temporary problem and in newer versions of Cairo we no longer need to manually derive the implementation. This PR adds that functionality for custom structs used in Storage and is already available if you are using the
main branch of the Cairo compiler.
So, while we wait for a proper release to include this functionality, how can we fix this? Let's take a look at a possible implementation.
We start by defining a new implementation of the
StorageAccess for our
Inside, we define two functions, a
write function and a
read function. As you can see from the signatures, the
write funtion receives, amongst other parameters, a
CustomStruct that we want to save in our Storage space, and the
read function returns a stored
CustomStruct wrapped on a
write function implementation could look something like this. We receive a
CustomStruct and we make use of the
storage_write_syscall function to write each individual value to our storage space.
One thing to take into consideration is the use of the
storage_address_from_base_and_offset function. This function is responsible for generating the address of our storage space where we are saving our element. Since our struct has three elements, the second parameter of the function goes from
2_u8, so that we have a unique spot for each element.
read function is what we need in order to retrieve the stored data of our
CustomStruct. This function makes use of the
storage_read_syscall function to retrieve each of our elements from the storage space. We also need to provide the same offset as we did for the
write function and we simply construct a
CustomStruct instance with the data retrieved. We wrap our struct in a
Result::Ok due to the function signature expected from the
One possible implementation would be something like this.
After adding this implementation, our contract should compile without any problem. For reference, here is the full implementation of a contract that uses a custom struct using this manual implementation approach.
Once again, this is only needed at the time of writing, and if we are using tagged release versions. On the
main branch, we no longer need to provide the
impl block to use this functionality, simply defining the struct is enough.
That's pretty much it. This was just a short post to show how we can manually implement the
write function for custom defined types.
Until next time!