Skip to content

Commit

Permalink
Implement discovery of packed encodings (#46)
Browse files Browse the repository at this point in the history
This commit implements the following features into the analyzer:

- Discovery and lifting of multiplicative shifts of values that are
  commonly used by solc when constructing packed encodings.
- Discovery and lifting of packed encoding structures themselves, taking
  the form of bitwise disjunctions of masked and shifted values.
- Care is taken in the lifting pass to ensure that segments of packed
  encodings that are _not used_ (are direct reads from the slot into
  which the packed encoding is being written) are ignored, thus ensuring
  we do not infer types for segments of storage slots that are not
  really written to.
- Loads from storage are now wrapped in `SLoad` nodes, allowing for
  detailed tracking of when a value was loaded from storage in the value
  trees.
  • Loading branch information
iamrecursion authored Aug 2, 2023
1 parent 072897a commit 241f442
Show file tree
Hide file tree
Showing 20 changed files with 1,683 additions and 1,441 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

# Environment variables that will be set on all runners
env:
RUST_VERSION: 1.70.0 # Specify the Rust version to be used on CI.
RUST_VERSION: 1.71.0 # Specify the Rust version to be used on CI.
CARGO_TERM_COLOR: always # Always colour Cargo's output.
CARGO_INCREMENTAL: 0 # Always run without incremental builds on CI.
CARGO_PROFILE_DEV_DEBUG: 0 # Don't embed debug info even though the build is a dev build.
Expand Down
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ readme = "README.md"
# to let authors opt in to backwards-incompatible changes. Crates using one edition can interoperate with crates using
# another edition, thereby ensuring that there's no ecosystem split.
edition = "2021"
rust-version = "1.70.0"
rust-version = "1.71.0"

# Prevent publishing by accident.
publish = false
Expand All @@ -32,19 +32,19 @@ derivative = "2.2.0"
downcast-rs = "1.2.0"
ethnum = "1.3.2"
hex = "0.4.3"
itertools = "0.10.5"
serde = { version = "1.0.163", features = ["derive"] }
itertools = "0.11.0"
serde = { version = "1.0.180", features = ["derive"] }
sha3 = "0.10.8"
thiserror = "1.0.40"
uuid = { version = "1.3.2", features = ["v4", "fast-rng", "macro-diagnostics"] }
thiserror = "1.0.44"
uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics"] }

# Dev dependencies.
#
# These are the dependencies required purely for internal development of the library such as testing or benchmarking.
[dev-dependencies]
anyhow = { version = "1.0.71", features = ["backtrace"] }
anyhow = { version = "1.0.72", features = ["backtrace"] }
rand = "0.8.5"
serde_json = "1.0.96"
serde_json = "1.0.104"

# Profiles specify the build settings for different kinds of build.
#
Expand All @@ -63,7 +63,7 @@ incremental = true # Ensure that incremental builds are enabled for dev. Th
# Configuration for release builds
[profile.release]
opt-level = 3 # Compile with all of the optimisations in production builds. This is the default.
debug = 1 # Include just line tables in release builds.
debug = true # Include full debug information in release builds.
debug-assertions = false # Disable in release builds for speed. This is the default.
overflow-checks = true # Keep overflow checks in production builds.
lto = "thin" # Thin LTO performs cross-crate LTO while not bloating the build time too much.
Expand Down
870 changes: 105 additions & 765 deletions asset/PackedEncodings.json

Large diffs are not rendered by default.

25 changes: 1 addition & 24 deletions asset/PackedEncodings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,11 @@ contract BytecodeExample {
uint128 two;
}

// This one we use in a dynamic array as well
struct StructTwo {
address addr;
bool isEnabled;
}

// This struct is just used directly.
// This struct is just used directly.
StructOne internal data;

// Here we create a connection between a dynamic array and a storage slot.
// We should be able to infer a struct type for the slot.
StructTwo internal current;
StructTwo[] public history;

function set_data(uint64 one, uint128 two) public {
data.one = one;
data.two = two;
}

// Updates the current and adds the old one to the history
function new_current(address addr, bool isEnabled) public {
history.push(current);
current.addr = addr;
current.isEnabled = isEnabled;
}

// A getter for the current
function get_current() public view returns (StructTwo memory) {
return current;
}
}
733 changes: 210 additions & 523 deletions asset/ReplaceMeForTesting.json

Large diffs are not rendered by default.

42 changes: 33 additions & 9 deletions asset/ReplaceMeForTesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,42 @@
pragma solidity ^0.8.13;

contract BytecodeExample {
mapping(uint128 => mapping(uint128 => uint256)) internal mappings;
uint64[] internal numbers;

function add(uint128 key, uint256 value) public {
mappings[key][key] = value;
// This one is only used in a storage slot, so should be indistinguishable
// from a packed encoding (done so we force it)
struct StructOne {
uint64 one;
uint128 two;
}

function append(uint64 number) public {
numbers.push(number);
}
// This one we use in a dynamic array as well
// struct StructTwo {
// address addr;
// bool isEnabled;
// }

// This struct is just used directly.
StructOne internal data;

// Here we create a connection between a dynamic array and a storage slot.
// We should be able to infer a struct type for the slot.
// StructTwo internal current;
// StructTwo[] public history;

function read(uint256 index) public view returns (uint64) {
return numbers[index];
function set_data(uint64 one, uint128 two) public {
data.one = one;
data.two = two;
}

// Updates the current and adds the old one to the history
// function new_current(address addr, bool isEnabled) public {
// history.push(current);
// current.addr = addr;
// current.isEnabled = isEnabled;
// }

// A getter for the current
// function get_current() public view returns (StructTwo memory) {
// return current;
// }
}
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Getting set up with this project is pretty simple.
git clone https://github.com/smlxlio/storage-layout-analyzer.git
```

If you _do_ want to contribute, we recommend cloning over SSH:
If you _do_ want to contribute directly to the tree, we recommend cloning over SSH:

```shell
git clone [email protected]:smlxlio/storage-layout-analyzer.git
Expand Down
6 changes: 6 additions & 0 deletions src/inference/lift/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

pub mod dynamic_array_access;
pub mod mapping_access;
pub mod mul_shifted;
pub mod packed_encoding;
pub mod recognise_hashed_slots;
pub mod storage_slots;
pub mod sub_word;
Expand All @@ -21,6 +23,8 @@ use crate::{
lift::{
dynamic_array_access::DynamicArrayAccess,
mapping_access::MappingAccess,
mul_shifted::MulShiftedValue,
packed_encoding::PackedEncoding,
recognise_hashed_slots::StorageSlotHashes,
storage_slots::StorageSlots,
sub_word::SubWordValue,
Expand Down Expand Up @@ -121,6 +125,8 @@ impl Default for LiftingPasses {
passes: vec![
StorageSlotHashes::new(),
SubWordValue::new(),
MulShiftedValue::new(),
PackedEncoding::new(),
MappingAccess::new(),
DynamicArrayAccess::new(),
StorageSlots::new(),
Expand Down
Loading

0 comments on commit 241f442

Please sign in to comment.