Skip to content

Commit e324f3d

Browse files
committed
init
0 parents  commit e324f3d

File tree

12 files changed

+1500
-0
lines changed

12 files changed

+1500
-0
lines changed

.github/workflows/test.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
env:
9+
FOUNDRY_PROFILE: ci
10+
11+
jobs:
12+
check:
13+
name: Foundry project
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
submodules: recursive
19+
20+
- name: Install Foundry
21+
uses: foundry-rs/foundry-toolchain@v1
22+
23+
- name: Show Forge version
24+
run: |
25+
forge --version
26+
27+
- name: Run Forge fmt
28+
run: |
29+
forge fmt --check
30+
id: fmt
31+
32+
- name: Run Forge build
33+
run: |
34+
forge build --sizes
35+
id: build
36+
37+
- name: Run Forge tests
38+
run: |
39+
forge test -vvv
40+
id: test

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Docs
11+
docs/
12+
13+
# Dotenv file
14+
.env

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/forge-std"]
2+
path = lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std

README.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# RedBlackTree KV Demo
2+
3+
A demonstration of a gas-efficient key-value store implementation using Red-Black Tree data structure in Solidity. This project showcases how Red-Black Trees can be more gas-efficient than traditional mapping-based storage when frequently adding and removing values.
4+
5+
## Overview
6+
7+
This project implements a Red-Black Tree based key-value store (`RedBlockTreeKV`) that stores complex value structures efficiently. The implementation is particularly beneficial for use cases involving frequent insertions and deletions, as it reuses storage slots and minimizes gas consumption.
8+
9+
The Red-Black Tree implementation is based on [Solady's RedBlackTreeLib](https://github.com/vectorized/solady/blob/main/src/utils/RedBlackTreeLib.sol). In this example, we modified the `remove` function to prevent deleting storage when removing nodes, enabling storage slot reuse for subsequent insertions.
10+
11+
### Key Features
12+
13+
- **Gas-Efficient Storage**: Reuses storage slots when items are deleted, leading to significant gas savings on subsequent insertions
14+
- **Complex Value Support**: Stores `ValueLib.Value` structs containing any size of data. In this example, it shows 6 slots.
15+
- **Red-Black Tree Properties**: Maintains balanced tree structure ensuring O(log n) operations
16+
- **Zero Value Protection**: Prevents insertion of zero keys to maintain tree integrity
17+
18+
### Gas Performance
19+
20+
Based on benchmark tests with 1,000 operations:
21+
22+
**RedBlackTreeKV:**
23+
24+
- Cold Insert: ~188,984 gas
25+
- Hot Insert (reusing deleted slot): ~13,250 gas ⚡
26+
- Delete: ~12,876 gas
27+
28+
**Traditional Mapping:**
29+
30+
- Insert: ~135,350 gas
31+
- Delete: ~1,529 gas
32+
33+
The Red-Black Tree implementation shows **90% gas reduction** for hot insertions compared to cold insertions, making it highly efficient for applications with frequent add/remove patterns.
34+
35+
> **Note for MegaETH**: On MegaETH where SSTORE operations cost significantly more than standard EVM, the gas efficiency advantage of RedBlackTreeKV becomes even more pronounced due to its storage slot reuse optimization.
36+
37+
## Project Structure
38+
39+
```
40+
src/
41+
├── RedBlockTreeKV.sol # Main KV store implementation
42+
├── MappingKV.sol # Traditional mapping implementation for comparison
43+
└── lib/
44+
├── RedBlackTreeLib.sol # Red-Black Tree data structure library
45+
└── Value.sol # Value struct definition
46+
47+
test/
48+
├── RedBlockTreeKV.t.sol # Comprehensive unit tests
49+
└── RedBlockTreeKVGas.t.sol # Gas benchmark tests
50+
```
51+
52+
## Value Structure
53+
54+
The KV store works with `ValueLib.Value` structs:
55+
56+
```solidity
57+
struct Value {
58+
bytes32 slot1; // 32 bytes
59+
uint256 slot2; // 32 bytes
60+
int256 slot3; // 32 bytes
61+
bytes32 slot4; // 32 bytes
62+
uint256 slot5; // 32 bytes
63+
address slot6; // 20 bytes
64+
}
65+
// Total: 192 bytes (6 storage slots)
66+
```
67+
68+
## Usage
69+
70+
### Basic Operations
71+
72+
```solidity
73+
RedBlockTreeKV kv = new RedBlockTreeKV();
74+
75+
// Create a value
76+
ValueLib.Value memory value = ValueLib.Value({
77+
slot1: bytes32(uint256(100)),
78+
slot2: 100,
79+
slot3: int256(100),
80+
slot4: bytes32(uint256(100)),
81+
slot5: 100,
82+
slot6: address(uint160(100))
83+
});
84+
85+
// Set value
86+
kv.setValue(1, value);
87+
88+
// Get value
89+
ValueLib.Value memory retrieved = kv.getValue(1);
90+
91+
// Delete value
92+
kv.deleteValue(1);
93+
94+
// Get all keys (sorted)
95+
uint256[] memory keys = kv.values();
96+
```
97+
98+
### Constraints
99+
100+
- **No Zero Keys**: Keys cannot be 0 (will revert with `ValueIsEmpty()`)
101+
- **No Duplicate Keys**: Attempting to set an existing key will revert with `ValueAlreadyExists()`
102+
- **Key Must Exist for Deletion**: Deleting non-existent keys will revert with `ValueDoesNotExist()`
103+
104+
## Development
105+
106+
### Build
107+
108+
```shell
109+
forge build
110+
```
111+
112+
### Test
113+
114+
```shell
115+
# Run all tests
116+
forge test
117+
118+
# Run unit tests only
119+
forge test --match-contract RedBlockTreeKVTest
120+
121+
# Run gas benchmarks
122+
forge test --match-contract MappingGasTest -vv
123+
```
124+
125+
### Test Coverage
126+
127+
The test suite includes:
128+
129+
- ✅ Basic CRUD operations
130+
- ✅ Edge cases (zero keys, max values)
131+
- ✅ Multiple value operations
132+
- ✅ Delete and reuse scenarios
133+
- ✅ Large dataset handling (100+ items)
134+
- ✅ Fuzz testing with random inputs
135+
- ✅ Gas consumption benchmarks
136+
137+
## Use Cases
138+
139+
This implementation is ideal for:
140+
141+
- **Order Books**: Frequent order insertions and cancellations
142+
- **Gaming**: Player inventory systems with item trading
143+
- **DeFi Protocols**: Position management with frequent updates
144+
- **NFT Marketplaces**: Listing and delisting operations
145+
146+
## License
147+
148+
MIT

foundry.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
optimizer = true
6+
optimizer_runs = 200
7+
solc = "0.8.30"
8+
9+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

lib/forge-std

Submodule forge-std added at 8bbcf6e

src/MappingKV.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import {ValueLib} from "./lib/Value.sol";
5+
6+
// A simple kv example. It is gas-consuming if frequently add & remove.
7+
contract MappingKV {
8+
// a mapping kv
9+
mapping(uint256 => ValueLib.Value) public kv;
10+
11+
function setValue(uint256 key, ValueLib.Value memory value) public {
12+
kv[key] = value;
13+
}
14+
15+
function getValue(uint256 key) public view returns (ValueLib.Value memory) {
16+
return kv[key];
17+
}
18+
19+
function deleteValue(uint256 key) public {
20+
delete kv[key];
21+
}
22+
}

src/RedBlockTreeKV.sol

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import {RedBlackTreeLib} from "./lib/RedBlackTreeLib.sol";
5+
import {ValueLib} from "./lib/Value.sol";
6+
7+
// A simple RBT kv example. It is gas-efficient if frequently add & remove.
8+
contract RedBlockTreeKV {
9+
uint256 private constant _DATA_SLOT_SEED = 0xdeadbeef; // Arbitrary unique seed
10+
uint256 private constant _SLOTS_PER_POSITION = ValueLib.SLOTS_PER_POSITION; // Dense slots per value
11+
12+
RedBlackTreeLib.Tree public tree;
13+
14+
using RedBlackTreeLib for RedBlackTreeLib.Tree;
15+
16+
function setValue(uint256 key, ValueLib.Value memory value) public {
17+
tree.insert(key);
18+
bytes32 ptr = tree.find(key);
19+
(, uint256 index) = _unpack(ptr); // Extract dense node index
20+
uint256 dataBase = _dataBase();
21+
uint256 valueSlot = dataBase + index * _SLOTS_PER_POSITION;
22+
/// @solidity memory-safe-assembly
23+
assembly {
24+
sstore(valueSlot, mload(value))
25+
sstore(add(valueSlot, 1), mload(add(value, 0x20)))
26+
sstore(add(valueSlot, 2), mload(add(value, 0x40)))
27+
sstore(add(valueSlot, 3), mload(add(value, 0x60)))
28+
sstore(add(valueSlot, 4), mload(add(value, 0x80)))
29+
sstore(add(valueSlot, 5), mload(add(value, 0xa0)))
30+
}
31+
}
32+
33+
function getValue(uint256 key) public view returns (ValueLib.Value memory) {
34+
bytes32 ptr = tree.find(key);
35+
(, uint256 index) = _unpack(ptr); // Extract dense node index
36+
uint256 dataBase = _dataBase();
37+
uint256 valueSlot = dataBase + index * _SLOTS_PER_POSITION;
38+
ValueLib.Value memory value;
39+
/// @solidity memory-safe-assembly
40+
assembly {
41+
mstore(value, sload(valueSlot))
42+
mstore(add(value, 0x20), sload(add(valueSlot, 1)))
43+
mstore(add(value, 0x40), sload(add(valueSlot, 2)))
44+
mstore(add(value, 0x60), sload(add(valueSlot, 3)))
45+
mstore(add(value, 0x80), sload(add(valueSlot, 4)))
46+
mstore(add(value, 0xa0), sload(add(valueSlot, 5)))
47+
}
48+
return value;
49+
}
50+
51+
function deleteValue(uint256 key) public {
52+
bytes32 ptr = tree.find(key);
53+
(, uint256 deletedIndex) = _unpack(ptr); // Extract dense node index
54+
uint256 dataBase = _dataBase();
55+
uint256 deletedSlot = dataBase + deletedIndex * _SLOTS_PER_POSITION;
56+
57+
uint256 treeSize = tree.size();
58+
uint256 lastIndex = treeSize;
59+
bool needsCopy = (deletedIndex != lastIndex);
60+
uint256 lastSlot = dataBase + lastIndex * _SLOTS_PER_POSITION;
61+
62+
tree.remove(key);
63+
64+
if (needsCopy) {
65+
/// @solidity memory-safe-assembly
66+
assembly {
67+
for { let i := 0 } lt(i, 6) { i := add(i, 1) } { sstore(add(deletedSlot, i), sload(add(lastSlot, i))) }
68+
}
69+
}
70+
}
71+
72+
// Helper: Compute data base slot (keccak-based, like tree nodes but unique seed)
73+
function _dataBase() internal pure returns (uint256 slotBase) {
74+
/// @solidity memory-safe-assembly
75+
assembly {
76+
mstore(0x20, tree.slot)
77+
mstore(0x00, _DATA_SLOT_SEED)
78+
slotBase := keccak256(0x00, 0x40)
79+
}
80+
}
81+
82+
// Helper: Unpack pointer (copied from lib private _unpack for convenience)
83+
function _unpack(bytes32 ptr) internal pure returns (uint256 nodes, uint256 key) {
84+
/// @solidity memory-safe-assembly
85+
assembly {
86+
nodes := shl(32, shr(32, ptr)) // _NODES_SLOT_SHIFT == 32
87+
key := and(0x7fffffff, ptr) // _BITMASK_KEY == (1 << 31) - 1
88+
}
89+
}
90+
91+
function values() public view returns (uint256[] memory) {
92+
return tree.values();
93+
}
94+
}

0 commit comments

Comments
 (0)