Skip to content

Commit 23b6b76

Browse files
iajoinerclaude
andauthored
feat: add new Yul preprocessor (#1033)
🤖 Generated with [Claude Code](https://claude.com/claude-code) Add a new Yul preprocessor Co-authored-by: Claude <[email protected]>
1 parent 28ca5c6 commit 23b6b76

File tree

59 files changed

+2477
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2477
-0
lines changed

.github/workflows/lint-and-test.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,22 @@ jobs:
256256
run: cargo test --package proof-of-sql --lib --all-features -- evm_tests --ignored --test-threads=1
257257
- name: Run Planner Rust EVM tests
258258
run: cargo test --package proof-of-sql-planner --all-features --test evm_tests -- --ignored --test-threads=1
259+
260+
check-yul-preprocessor:
261+
name: Check Yul Preprocessor
262+
runs-on: ubuntu-latest
263+
steps:
264+
- name: Checkout repository
265+
uses: actions/checkout@v4
266+
- name: Set up Python
267+
uses: actions/setup-python@v5
268+
with:
269+
python-version: '3.x'
270+
- name: Install dependencies
271+
run: |
272+
python -m pip install --upgrade pip
273+
pip install black pytest
274+
- name: Run black
275+
run: black --check solidity/preprocessor/
276+
- name: Run pytest
277+
run: pytest solidity/preprocessor/

solidity/preprocessor/README.md

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# Yul Preprocessor
2+
3+
A preprocessor for Solidity files that resolves Yul function imports within assembly blocks, enabling better code organization and reusability for inline assembly code.
4+
5+
## Features
6+
7+
- **Import Yul functions** from other `.presl` files into your assembly blocks
8+
- **Multiple imports per line**: Import several functions in a single statement
9+
- **Self imports**: Reference functions from different assembly blocks in the same file
10+
- **Relative path support**: Import from files using relative paths
11+
- **Circular dependencies allowed**: Files can import from each other - circular dependency groups are automatically resolved
12+
- **Transitive dependency resolution**: Importing from a file automatically includes all its dependencies
13+
- **Function deduplication**: Automatically deduplicates identical function imports
14+
- **Caching**: Efficiently processes files with intelligent caching
15+
- **Automatic formatting**: Runs `forge fmt` on output files for clean, consistent formatting
16+
17+
## Installation
18+
19+
No installation required! Just use Python 3.6+:
20+
21+
```bash
22+
python3 yul_preprocessor.py <directory>
23+
```
24+
25+
**Optional**: Install [Foundry](https://book.getfoundry.sh/getting-started/installation) for automatic formatting of output files with `forge fmt`.
26+
27+
## Usage
28+
29+
### Basic Usage
30+
31+
Process all `.presl` and `.t.presl` files in a directory:
32+
```bash
33+
python3 yul_preprocessor.py ./contracts
34+
```
35+
36+
The preprocessor processes both `.presl` and `.t.presl` files (for test files), generating corresponding `.post.sol` and `.t.post.sol` output files respectively.
37+
38+
The preprocessor automatically runs `forge fmt` on all generated `.post.sol` files to ensure clean, consistent formatting. If `forge` is not available in your PATH, the preprocessor will skip formatting with a warning.
39+
40+
### Import Syntax
41+
42+
The preprocessor supports three import patterns:
43+
44+
#### 1. Single Function Import
45+
```solidity
46+
// import <function_name> from <file_path>
47+
```
48+
49+
Example:
50+
```solidity
51+
assembly {
52+
// import add5 from utils.presl
53+
let result := add5(10)
54+
}
55+
```
56+
57+
#### 2. Multiple Functions Per Line
58+
```solidity
59+
// import <func1>, <func2>, <func3> from <file_path>
60+
```
61+
62+
Example:
63+
```solidity
64+
assembly {
65+
// import add, multiply, divide from math.presl
66+
let sum := add(5, 10)
67+
let product := multiply(3, 7)
68+
}
69+
```
70+
71+
#### 3. Self Import
72+
```solidity
73+
// import <function_name> from self
74+
```
75+
76+
Import functions from a different assembly block in the same file:
77+
```solidity
78+
contract Example {
79+
function defineHelpers() external pure {
80+
assembly {
81+
function helper(x) -> result {
82+
result := mul(x, 2)
83+
}
84+
}
85+
}
86+
87+
function useHelpers() external pure {
88+
assembly {
89+
// import helper from self
90+
let doubled := helper(5)
91+
}
92+
}
93+
}
94+
```
95+
96+
### Relative Paths
97+
98+
Import from subdirectories or parent directories:
99+
100+
```solidity
101+
// import compute_fold from ../base/MathUtil.presl
102+
// import err from ./errors/Errors.sol
103+
// import safe_add from lib/SafeMath.presl
104+
```
105+
106+
## Complete Example
107+
108+
### Source File: `utils.presl`
109+
```solidity
110+
// SPDX-License-Identifier: MIT
111+
pragma solidity ^0.8.0;
112+
113+
contract Utils {
114+
function process() external pure returns (uint256) {
115+
assembly {
116+
function add5(x) -> result {
117+
result := add(x, 5)
118+
}
119+
120+
function multiply2(x) -> result {
121+
result := mul(x, 2)
122+
}
123+
}
124+
}
125+
}
126+
```
127+
128+
### Target File: `main.presl`
129+
```solidity
130+
// SPDX-License-Identifier: MIT
131+
pragma solidity ^0.8.0;
132+
133+
contract Main {
134+
function compute() external pure returns (uint256) {
135+
assembly {
136+
// import add5, multiply2 from utils.presl
137+
let a := add5(10) // a = 15
138+
let b := multiply2(a) // b = 30
139+
}
140+
}
141+
}
142+
```
143+
144+
### Output File: `main.post.sol`
145+
```solidity
146+
// SPDX-License-Identifier: MIT
147+
pragma solidity ^0.8.0;
148+
149+
contract Main {
150+
function compute() external pure returns (uint256) {
151+
assembly {
152+
function add5(x) -> result {
153+
result := add(x, 5)
154+
}
155+
function multiply2(x) -> result {
156+
result := mul(x, 2)
157+
}
158+
159+
let a := add5(10) // a = 15
160+
let b := multiply2(a) // b = 30
161+
}
162+
}
163+
}
164+
```
165+
166+
## File Naming Convention
167+
168+
- **Input files**:
169+
- `*.presl` - Standard files with import statements
170+
- `*.t.presl` - Test files with import statements
171+
- **Output files**:
172+
- `*.post.sol` - Processed standard files with imports resolved
173+
- `*.t.post.sol` - Processed test files with imports resolved
174+
175+
The preprocessor automatically generates `.post.sol` files from `.presl` files and `.t.post.sol` files from `.t.presl` files.
176+
177+
## Circular Dependencies
178+
179+
Circular dependencies are **fully supported**! When files A and B import from each other, they are processed as a unified dependency group.
180+
181+
### How It Works
182+
183+
When a circular dependency is detected (e.g., A imports from B, B imports from A), the preprocessor:
184+
185+
1. Identifies all files in the circular group
186+
2. Collects all Yul functions from all files in the group
187+
3. Makes the complete set of functions available to every file in the group
188+
4. Ensures all assembly blocks in the cycle have identical function sets
189+
190+
### Example
191+
192+
**file_a.presl:**
193+
```solidity
194+
assembly {
195+
// import funcB from file_b.presl
196+
function funcA() -> result { result := 1 }
197+
}
198+
```
199+
200+
**file_b.presl:**
201+
```solidity
202+
assembly {
203+
// import funcA from file_a.presl
204+
function funcB() -> result { result := 2 }
205+
}
206+
```
207+
208+
After processing, both files will have both `funcA` and `funcB` in their assembly blocks.
209+
210+
### Nested Circular Dependencies
211+
212+
The preprocessor handles complex scenarios where:
213+
- Group C imports from Group B (B0, B1) which are mutually dependent
214+
- Group B imports from Group A (A0, A1) which are mutually dependent
215+
- Group A imports from standalone files
216+
217+
All transitive dependencies are correctly resolved and propagated.
218+
219+
## Error Handling
220+
221+
The preprocessor detects and reports several types of errors:
222+
223+
### Missing Functions
224+
```
225+
ValueError: Function 'nonExistent' not found in utils.presl
226+
Available functions: add5, multiply2
227+
```
228+
229+
### Function Signature Mismatch
230+
```
231+
ValueError: Function signature mismatch for 'add':
232+
Existing: function add(a, b) -> result
233+
New: function add(x) -> result
234+
```
235+
236+
## Architecture
237+
238+
### Key Components
239+
240+
1. **YulFunction**: Represents a parsed Yul function with name, signature, body, and full text
241+
2. **YulPreprocessor**: Main processor class that handles:
242+
- File parsing and processing
243+
- Import resolution
244+
- Function extraction
245+
- Caching
246+
247+
### Processing Flow
248+
249+
```
250+
Input .presl file
251+
252+
Find assembly blocks
253+
254+
For each assembly block:
255+
- Parse import statements
256+
- Resolve imported functions
257+
- Process dependencies recursively
258+
- Deduplicate functions
259+
- Insert functions at block start
260+
261+
Generate .post.sol file
262+
```
263+
264+
## Testing
265+
266+
Run the test suite:
267+
268+
```bash
269+
python3 -m pytest test_yul_preprocessor.py -v
270+
```
271+
272+
Test coverage includes:
273+
- Basic imports
274+
- Multiple imports per line
275+
- Self imports
276+
- Relative path imports
277+
- Function deduplication
278+
- Circular dependency detection
279+
- Missing function errors
280+
- Complex function signatures
281+
- Multiple assembly blocks
282+
- Caching
283+
284+
## Advanced Features
285+
286+
### Function Deduplication
287+
288+
If the same function is imported multiple times, only one copy is included:
289+
290+
```solidity
291+
assembly {
292+
// import add from math.presl
293+
// import add from math.presl // Deduplicated
294+
let x := add(1, 2)
295+
}
296+
```
297+
298+
### Multiple Assembly Blocks
299+
300+
The preprocessor handles multiple assembly blocks within a single contract:
301+
302+
```solidity
303+
contract Multi {
304+
function first() external pure {
305+
assembly {
306+
// import func1 from lib.presl
307+
}
308+
}
309+
310+
function second() external pure {
311+
assembly {
312+
// import func2 from lib.presl
313+
}
314+
}
315+
}
316+
```
317+
318+
### Caching
319+
320+
Processed files are cached to improve performance when the same file is imported multiple times in a dependency tree.
321+
322+
## Behavior Notes
323+
324+
- **Transitive Dependencies**: When you import a function from a file, you automatically get all functions from that file's assembly block. This ensures the complete dependency closure is available.
325+
- **Import statements** must be on a single line
326+
- **Import syntax** must follow: `// import <names> from <path>`
327+
- Only **Yul functions** within `assembly {}` blocks are extracted and imported
328+
329+
## Contributing
330+
331+
To add new features or fix bugs:
332+
333+
1. Add test cases in `test_files/` directory
334+
2. Update `test_yul_preprocessor.py` with corresponding tests
335+
3. Implement changes in `yul_preprocessor.py`
336+
4. Run tests to ensure everything passes
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
contract Main {
5+
function compute() external pure returns (uint256 output) {
6+
assembly {
7+
// import add5 from utils.presl
8+
let result := add5(10)
9+
output := result
10+
}
11+
}
12+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
contract Utils {
5+
function process() external pure returns (uint256 output) {
6+
assembly {
7+
function add5(x) -> result {
8+
result := add(x, 5)
9+
}
10+
output := add5(7)
11+
}
12+
}
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pragma solidity ^0.8.24;
2+
contract Cached {
3+
function test() external pure {
4+
assembly {
5+
function cachedFunc() -> result { result := 42 }
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)