Structured Symbolic Storage Generation
Automated generation of structured symbolic storage
Overview
Structured symbolic storage generation is a new feature in Kontrol that automatically creates symbolic storage constants for your contracts based on their storage layout. This eliminates the need to manually specify storage slots, offsets, and widths, making symbolic testing more reliable and less error-prone.
This feature automates the manual approach described in the Symbolic Storage guide, providing a streamlined way to set up symbolic storage for your contracts without the complexity of manual slot management.
This approach has been successfully used in real-world formal verification engagements, including the Lido Dual Governance project, demonstrating its effectiveness in production environments.
How It Works
The structured symbolic storage generation feature:
Analyzes storage layout: Uses the Solidity compiler's storage layout information from the contract's JSON artifacts
Generates constants: Creates symbolic constants for each storage variable, including its type, offset, and width
Automates setup: Provides helper functions to initialize storage with symbolic values
Ensures correctness: Automatically handles slot calculations, offsets, and data widths
Prerequisites
To use structured symbolic storage generation, you need:
A Foundry project with contracts compiled with storage layout information (using
extra_output = ['storageLayout']in thefoundry.tomlfile)The auxiliary
KontrolTestcontract that is automatically inserted in the Foundry project bykontrol init
Basic Usage
Experimental Feature: Structured storage generation is an experimental feature, and dynamically-sized types like bytes of variable length, mappings, and arrays have limited support. The KontrolTest contract generated by kontrol init includes some auxiliary functions that allow setting up symbolic storage for these types manually.
Step 1: Initialize the project with KontrolTest
When you run kontrol init, it automatically generates a KontrolTest.sol contract that includes the necessary infrastructure, unless the --skip-kontrol-test option is provided. Additionally, when run in an existing Foundry project, you should provide the --skip-forge option:
This adds several auxiliary files, including the KontrolTest.sol file with helper functions like _loadData and _storeData for symbolic storage manipulation.
Step 2: Generate Storage Constants
Then, use the kontrol setup-storage command to automatically generate symbolic storage constants:
This command:
Analyzes the
src/SimpleStoragecontract's storage layoutGenerates symbolic constants for each storage variable and outputs them to the
test/kontrol/storage/SimpleStorageStorageConstants.solfile
Additionally, the output file path can be customized using the --output option, and the version of Solidity used in the generated file can be customized using the --solidity-version option.
The generated files will contain symbolic constants and helper functions. Here's an example of what gets generated:
Example Contract: SimpleStorage.sol
Generated Storage Constants: SimpleStorageStorageConstants.sol
Generated Setup Contract: SimpleStorageStorageSetup.sol
Key Features of the Generated Setup Contract:
Slot Clearing: Uses
_clearSlot()to ensure clean symbolic execution states for each slotStruct Support: Automatically decomposes structs into individual field parameters
Storage Packing: Properly handles packed storage slots (
walletandisActivefrom the example contract are stored in the same slot)Type Conversions: Includes proper type conversions in the data loading functions (e.g.,
uint160(_owner),boolToUint256())TODO Comments: Introduces comments to indicate unsupported types like dynamic arrays
Additionally, the user can introduce additional constraints to the generated setup contract, for example, to ensure that the totalSupply is always less than the maxSupply, using the vm.assume cheatcode.
Step 4: Use Generated Storage Setup in Tests
The generated setup contract can be used directly in your tests, for example, as follows:
For test_storage_setup, kontrol prove will output the counterexample with the following path condition:
indicating that the test failed because the symbolic value read from the storage slot corresponding to the totalSupply variable is not equal to the _anotherTotalSupply parameter.
The storage of the SimpleStorage contract looks like the following:
Best Practices
1. Use Meaningful Variable Names
The generator uses the actual variable names from your contract, so use descriptive names:
2. Add Appropriate Constraints
Add assumptions constraining the values of storage variables to reduce the search space for symbolic execution, if needed:
Related Features
Symbolic Storage - Manual symbolic storage management
Counterexample Generation - Finding failing test cases
References
Last updated
Was this helpful?