Storage Variable Clashing in StarkNet

How storage clashing can occur with StarkNet smart contracts and best practices for prevention

Andrew Fleming
Coinmonks
Published in
4 min readApr 26, 2022

--

🗄️ How is Storage Handled?

Contract storage on StarkNet is handled with simple key/value pairs. According to the StarkNet documentation:

Storage Layout

Contract storage is a persistent storage space where you can read, write, modify, and persist data. Storage is a map with 2²⁵¹ slots, where each slot is a felt and is initialized to 0.

Storage Basic Functions

The basic function for reading storage returns value stored in key

let (value) = storage_read(key)

The basic function for writing to storage writes value to key

storage_write(key, value)

Storage variables, decorated in contracts with @storage_var, have a bit more complexity. The StarkNet compiler maps their name and values (in Cairo code) to an address generated by StarkNet’s own sn_keccak method (as-is or through a hash chain for nested mappings). The important takeaway here, however, is that storage variables are simply treated as hashed key/value pairs.

Contract Extensibility in StarkNet

OpenZeppelin pioneered the Extensibility Pattern which consists of guidelines for contracts to safely import functionality and state from battle-tested libraries. The basic idea is for contracts to import and expose (with @external and @view decorators) the methods they want to use from libraries. For example, consider the popular ERC20 library. All of the methods and state management to deploy an ERC20 token already exist in the library. The user just needs to expose the requisite methods in a contract and voila! Your contract is ready to deploy.

The reason libraries do not expose their methods is because Cairo will automatically export them regardless if they’re imported or not. This can be dangerous.

The interesting question with this pattern is: if libraries set their own state with storage variables, what happens when a contract imports from multiple libraries that share the same name for those storage variables?

💥 Storage Clashing from Separate Libraries

Let’s look at these two libraries:

While the example methods share the same name, they belong to their respective namespace i.e. LIBRARY_A.increase_balance and LIBRARY_B_increase_balance. The storage variable balance, however, is not encompassed by either namespace. This is important to remember.

Now, let’s look at a contract that will import from these libraries and expose their methods.

Notice that neither library’s storage is explicitly imported into the contract—just the namespace. Now, let’s test the exposed methods of contract_c and discover what happens to the libraries’s respective storage.

The result:

Wait, what happened? The StarkNet compiler did not differentiate between the two balance storage variables even though they seemed to “privately” belong to their respective libraries. In other words, the compiler sees both balance storage variables as references to the same variable.

The StarkNet compiler will fail, however, if the same-named storage variables differ in any way. Such differences include variable names, return value names, and the number of keys. Here’s a simple example to illustrate:

If we change the return variable name in library_b as above and try to compile contract_c, the compilation will fail. Any such difference will return this AssertionError:

Main takeaway: if a contract imports from multiple libraries and these libraries happen to share a storage variable name (i.e. balance), these variables will likely clash if the compiler doesn’t catch it.

🛡 ️Prevent Storage Variable Clashing

As of this writing, the best solution consists of prefixing storage variable names with the library’s name or namespace. For example: ERC20_balances, ReentrancyGuard_start, and Ownable_owner.

For a proof of concept, let’s change the storage variables of the example libraries to LIBRARY_A_balance and LIBRARY_B_balance.

After running the exact same test, here is the result:

Conclusion

Given how new and cutting-edge the StarkNet network and Cairo programming language are, best practices will inevitably change as existing patterns and conventions evolve and new ones take their place. In the mean time, prefix your storage variables!

Special thanks to Martín Triay, Julissa Dantes, and OpenZeppelin for inspiring this research. Check out the original discussion here.

Interested in learning more about StarkNet and Cairo? Check out these excellent resources:

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read

--

--

Andrew Fleming
Coinmonks

Writer, programmer, boating accident enthusiast