rustsol
Description
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.
Bindings Example Usage
Get storage position of a contract variable:
let contract = new;
let = contract.observations.at.tickCumulative.position;
println!;
// Output:
// slot=50, offset=4, size_bytes=7
Get storage value of a contract variable using provided slots getter:
let contract = new;
contract.set_slots_getter;
let tick_value = contract.ticks.at.get_value.unwrap;
println!;
// Output (prettified):
// TickInfoValue {
// liquidityGross: 398290794261,
// liquidityNet: 398290794261,
// feeGrowthOutside0X128: 0x0000000000000000000000000000000000000b73d798604f1b0cd4f1d544c646_U256,
// feeGrowthOutside1X128: 0x000000000000000000000000000000f2a960acbe8891e526c025b819077f15ae_U256,
// tickCumulativeOutside: 622572156443,
// secondsPerLiquidityOutsideX128: 0x0000000000000000000000000000000000000001e576ee66a9d9f002e36fad4c_U256,
// secondsOutside: 1623419923,
// initialized: true
// }
Generated Structs
The generated structs from the bindings will look similar to the following:
Installation
Setup and Usage
1. Get Contract Storage Layout
To 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.
See the original repository for instructions.
2. Generate Bindings
To generate bindings, use the following command:
To understand what contract_path and contract_name mean, let's look at an example storage layout JSON:
Here, UniswapV3Pool.sol is the contract path, and UniswapV3Pool is the contract name.
For the provided example, it would be:
After running the above command, the generated structs will be in generated_contract.rs file.
See the original repository for an example.
Types Mapping
Solidity -> Rust Mapping
Solidity 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.
Value Types Mapping
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 |
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.
License
This project is licensed under the MIT License.