rustsol is a tool designed to generate Rust storage bindings for Ethereum smart contracts.
These bindings can be used to retrieve storage slots, offsets, and sizes of all stored variables within a contract, including objects stored in mappings and arrays.
When initialized with a slots_getter, the generated structures can be used to conveniently access the values of these storage variables.
Get storage position of a contract variable:
let contract = generated_contract::UniswapV3Pool::new();
let (slot, offset, size_bytes) = contract.observations.at(42).tickCumulative.position();
println!("slot={}, offset={}, size_bytes={}", slot, offset, size_bytes);
// Output:
// slot=50, offset=4, size_bytes=7Get storage value of a contract variable using provided slots getter:
let contract = generated_contract::UniswapV3Pool::new();
contract.set_slots_getter(my_slots_getter);
let tick_value = contract.ticks.at(-92110).get_value().unwrap();
println!("{:?}", tick_value);
// Output (prettified):
// TickInfoValue {
// liquidityGross: 398290794261,
// liquidityNet: 398290794261,
// feeGrowthOutside0X128: 0x0000000000000000000000000000000000000b73d798604f1b0cd4f1d544c646_U256,
// feeGrowthOutside1X128: 0x000000000000000000000000000000f2a960acbe8891e526c025b819077f15ae_U256,
// tickCumulativeOutside: 622572156443,
// secondsPerLiquidityOutsideX128: 0x0000000000000000000000000000000000000001e576ee66a9d9f002e36fad4c_U256,
// secondsOutside: 1623419923,
// initialized: true
// }The generated structs from the bindings will look similar to the following:
pub struct UniswapV3Pool {
__slot: U256,
__slots_getter: Option<Arc<dyn SlotsGetter>>,
pub slot0: UniswapV3PoolSlot0,
pub feeGrowthGlobal0X128: Primitive<32, U256>,
pub feeGrowthGlobal1X128: Primitive<32, U256>,
pub protocolFees: UniswapV3PoolProtocolFees,
pub liquidity: Primitive<16, u128>,
pub ticks: Mapping<i32, TickInfo>,
pub tickBitmap: Mapping<i16, Primitive<32, U256>>,
pub positions: Mapping<U256, PositionInfo>,
pub observations: StaticArray<2097120, OracleObservation>,
}
pub struct UniswapV3PoolSlot0Value {
pub sqrtPriceX96: U256,
pub tick: i32,
pub observationIndex: u16,
pub observationCardinality: u16,
pub observationCardinalityNext: u16,
pub feeProtocol: u8,
pub unlocked: bool,
}From crates.io:
cargo install rustsolFrom source code:
git clone <this repo>
cd rustsol
cargo install --path rustsolTo generate storage bindings for a contract, you first need the contract's storage layout in JSON format.
For Solidity contracts, this can be generated using the solc compiler.
rustsol provides a simple Python script to assist with this process.
Set up the Python environment
python -m venv .venv
source .venv/bin/activate
pip install -r pytools/requirements.txtand run the script
cd pytools
python storage_layout.pyModify this script to generate the storage layout for the contracts you are interested in.
To generate bindings, you can use the following command:
rustsol generate_storage_bindings your_solc_output.json contract_path contract_name generated_contract.rsOr with cargo:
cargo run -- generate_storage_bindings your_solc_output.json contract_path contract_name generated_contract.rsTo understand what contract_path and contract_name mean, let's look at an example storage layout JSON:
{
"contracts": {
"UniswapV3Pool.sol": {
"UniswapV3Pool": {
"storageLayout": {
...
}
}
}
}
}Here, UniswapV3Pool.sol is the contract path, and UniswapV3Pool is the contract name.
For the provided example, it would be:
rustsol generate_storage_bindings example/solc_output_uniswap3pool.json UniswapV3Pool.sol UniswapV3Pool example/src/generated_contract_uniswap3pool.rsAfter running the above command, check the generated file generated_contract_uniswap3pool.rs in the example/src directory.
See example/src/run_*.rs examples for bindings usage.
Bindings can also be generated from Rust using rustsol as a library.
See the example/src/generate.rs script in the example directory:
cargo run -p example --bin generateSolidity variable types are mapped to rustsol binding types.
This mapping is shown in the table below.
Here, value_type corresponds to a (possibly nested) rustsol binding type, such as Primitive or Mapping.
The native_type is a type to which the actual slot values are converted, such as u64, bool and U256.
Note that the keys of Mappings are also native types.
| Solidity Type | Generated Rust Type |
|---|---|
| all integer types, bool, enum, address, contract, small bytes1..32 |
Primitive<byte_size, native_type> |
| string, bytes | Bytes<String>, Bytes<Vec<u8>> |
| static arrays | StaticArray<byte_size, value_type> |
| dynamic arrays | DynamicArray<value_type> |
| mapping | Mapping<key_native_type, value_type> |
| struct | CustomNamedStructWithCorrespondingFields |
Note that all enums are mapped to Primitive<byte_size, U256>.
It would be better to use native Rust enums as the native_type instead of the generic U256,
but this requires deeper Solidity contract analysis,
as information about enum field names is not available from the storage layout.
Variable values obtained with get_value() are converted to native types according to the table below.
rustsol Type |
Generated Rust Type |
|---|---|
Primitive<_, native_type> |
native_type |
Bytes<String> |
String |
Bytes<Vec<u8>> |
Vec<u8> |
StaticArray<value_type> |
Vec<recursive native type of value_type> |
DynamicArray<value_type> |
Vec<recursive native type of value_type> |
Mapping |
Mapping<key_native_type, value_type> getter |
SomeStruct |
SomeStructValue |
Note that Mapping types are actually mapped to the same Mapping.
This is because there is no way to get all elements of a Solidity mapping.
Therefore, we return a value getter instead of a concrete value.
This project is licensed under the MIT License.