Skip to content

Commit a5c5be5

Browse files
authored
chore(bind-json): replace solang with solar (#9616)
1 parent 2e9d849 commit a5c5be5

File tree

2 files changed

+157
-74
lines changed

2 files changed

+157
-74
lines changed

crates/forge/bin/cmd/bind_json.rs

Lines changed: 86 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ use foundry_compilers::{
1515
};
1616
use foundry_config::Config;
1717
use itertools::Itertools;
18-
use rayon::prelude::*;
19-
use solang_parser::pt as solang_ast;
18+
use solar_ast::{
19+
ast::{self, Arena, FunctionKind, Span, VarMut},
20+
interface::source_map::FileName,
21+
visit::Visit,
22+
};
23+
use solar_parse::{interface::Session, Parser as SolarParser};
2024
use std::{
2125
collections::{BTreeMap, BTreeSet},
22-
fmt,
23-
fmt::Write,
26+
fmt::{self, Write},
2427
path::PathBuf,
2528
sync::Arc,
2629
};
@@ -85,85 +88,94 @@ impl BindJsonArgs {
8588
.unwrap()
8689
.1;
8790

88-
// Insert empty bindings file
91+
let sess = Session::builder().with_stderr_emitter().build();
92+
let result = sess.enter(|| -> solar_parse::interface::Result<()> {
93+
// TODO: Switch back to par_iter_mut and `enter_parallel` after solar update.
94+
sources.0.iter_mut().try_for_each(|(path, source)| {
95+
let mut content = Arc::try_unwrap(std::mem::take(&mut source.content)).unwrap();
96+
97+
let arena = Arena::new();
98+
let mut parser = SolarParser::from_source_code(
99+
&sess,
100+
&arena,
101+
FileName::Real(path.clone()),
102+
content.to_string(),
103+
)?;
104+
let ast = parser.parse_file().map_err(|e| e.emit())?;
105+
106+
let mut visitor = PreprocessorVisitor::new();
107+
visitor.visit_source_unit(&ast);
108+
visitor.update(&sess, &mut content);
109+
110+
source.content = Arc::new(content);
111+
Ok(())
112+
})
113+
});
114+
eyre::ensure!(result.is_ok(), "failed parsing");
115+
116+
// Insert empty bindings file.
89117
sources.insert(target_path.clone(), Source::new("library JsonBindings {}"));
90118

91-
let sources = Sources(
92-
sources
93-
.0
94-
.into_par_iter()
95-
.map(|(path, source)| {
96-
let mut locs_to_update = Vec::new();
97-
let mut content = Arc::unwrap_or_clone(source.content);
98-
let (parsed, _) = solang_parser::parse(&content, 0)
99-
.map_err(|errors| eyre::eyre!("Parser failed: {errors:?}"))?;
100-
101-
// All function definitions in the file
102-
let mut functions = Vec::new();
103-
104-
for part in &parsed.0 {
105-
if let solang_ast::SourceUnitPart::FunctionDefinition(def) = part {
106-
functions.push(def);
107-
}
108-
if let solang_ast::SourceUnitPart::ContractDefinition(contract) = part {
109-
for part in &contract.parts {
110-
match part {
111-
solang_ast::ContractPart::FunctionDefinition(def) => {
112-
functions.push(def);
113-
}
114-
// Remove `immutable` attributes
115-
solang_ast::ContractPart::VariableDefinition(def) => {
116-
for attr in &def.attrs {
117-
if let solang_ast::VariableAttribute::Immutable(loc) =
118-
attr
119-
{
120-
locs_to_update.push((
121-
loc.start(),
122-
loc.end(),
123-
String::new(),
124-
));
125-
}
126-
}
127-
}
128-
_ => {}
129-
}
130-
}
131-
};
132-
}
119+
Ok(PreprocessedState { sources, target_path, project, config })
120+
}
121+
}
133122

134-
for def in functions {
135-
// If there's no body block, keep the function as is
136-
let Some(solang_ast::Statement::Block { loc, .. }) = def.body else {
137-
continue;
138-
};
139-
let new_body = match def.ty {
140-
solang_ast::FunctionTy::Modifier => "{ _; }",
141-
_ => "{ revert(); }",
142-
};
143-
let start = loc.start();
144-
let end = loc.end();
145-
locs_to_update.push((start, end + 1, new_body.to_string()));
146-
}
123+
struct PreprocessorVisitor {
124+
updates: Vec<(Span, &'static str)>,
125+
}
126+
127+
impl PreprocessorVisitor {
128+
fn new() -> Self {
129+
Self { updates: Vec::new() }
130+
}
147131

148-
locs_to_update.sort_by_key(|(start, _, _)| *start);
132+
fn update(mut self, sess: &Session, content: &mut String) {
133+
if self.updates.is_empty() {
134+
return;
135+
}
149136

150-
let mut shift = 0_i64;
137+
let sf = sess.source_map().lookup_source_file(self.updates[0].0.lo());
138+
let base = sf.start_pos.0;
151139

152-
for (start, end, new) in locs_to_update {
153-
let start = ((start as i64) - shift) as usize;
154-
let end = ((end as i64) - shift) as usize;
140+
self.updates.sort_by_key(|(span, _)| span.lo());
141+
let mut shift = 0_i64;
142+
for (span, new) in self.updates {
143+
let lo = span.lo() - base;
144+
let hi = span.hi() - base;
145+
let start = ((lo.0 as i64) - shift) as usize;
146+
let end = ((hi.0 as i64) - shift) as usize;
155147

156-
content.replace_range(start..end, new.as_str());
157-
shift += (end - start) as i64;
158-
shift -= new.len() as i64;
159-
}
148+
content.replace_range(start..end, new);
149+
shift += (end - start) as i64;
150+
shift -= new.len() as i64;
151+
}
152+
}
153+
}
160154

161-
Ok((path, Source::new(content)))
162-
})
163-
.collect::<Result<BTreeMap<_, _>>>()?,
164-
);
155+
impl<'ast> Visit<'ast> for PreprocessorVisitor {
156+
fn visit_item_function(&mut self, func: &'ast ast::ItemFunction<'ast>) {
157+
// Replace function bodies with a noop statement.
158+
if let Some(block) = &func.body {
159+
if !block.is_empty() {
160+
let span = block.first().unwrap().span.to(block.last().unwrap().span);
161+
let new_body = match func.kind {
162+
FunctionKind::Modifier => "_;",
163+
_ => "revert();",
164+
};
165+
self.updates.push((span, new_body));
166+
}
167+
}
165168

166-
Ok(PreprocessedState { sources, target_path, project, config })
169+
self.walk_item_function(func)
170+
}
171+
172+
fn visit_variable_definition(&mut self, var: &'ast ast::VariableDefinition<'ast>) {
173+
// Remove `immutable` attributes.
174+
if let Some(VarMut::Immutable) = var.mutability {
175+
self.updates.push((var.span, ""));
176+
}
177+
178+
self.walk_variable_definition(var)
167179
}
168180
}
169181

crates/forge/tests/cli/bind_json.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use foundry_test_utils::snapbox;
2+
13
// tests complete bind-json workflow
24
// ensures that we can run forge-bind even if files are depending on yet non-existent bindings and
35
// that generated bindings are correct
@@ -50,5 +52,74 @@ contract BindJsonTest is Test {
5052
.unwrap();
5153

5254
cmd.arg("bind-json").assert_success();
55+
56+
snapbox::assert_data_eq!(
57+
snapbox::Data::read_from(&prj.root().join("utils/JsonBindings.sol"), None),
58+
snapbox::str![[r#"
59+
// Automatically generated by forge bind-json.
60+
61+
pragma solidity >=0.6.2 <0.9.0;
62+
pragma experimental ABIEncoderV2;
63+
64+
import {BindJsonTest, TopLevelStruct} from "test/JsonBindings.sol";
65+
66+
interface Vm {
67+
function parseJsonTypeArray(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);
68+
function parseJsonType(string calldata json, string calldata typeDescription) external pure returns (bytes memory);
69+
function parseJsonType(string calldata json, string calldata key, string calldata typeDescription) external pure returns (bytes memory);
70+
function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json);
71+
function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes memory value) external returns (string memory json);
72+
}
73+
74+
library JsonBindings {
75+
Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
76+
77+
string constant schema_TopLevelStruct = "TopLevelStruct(uint256 param1,int8 param2)";
78+
string constant schema_ContractLevelStruct = "ContractLevelStruct(address[][] param1,address addrParam)";
79+
80+
function serialize(TopLevelStruct memory value) internal pure returns (string memory) {
81+
return vm.serializeJsonType(schema_TopLevelStruct, abi.encode(value));
82+
}
83+
84+
function serialize(TopLevelStruct memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
85+
return vm.serializeJsonType(objectKey, valueKey, schema_TopLevelStruct, abi.encode(value));
86+
}
87+
88+
function deserializeTopLevelStruct(string memory json) public pure returns (TopLevelStruct memory) {
89+
return abi.decode(vm.parseJsonType(json, schema_TopLevelStruct), (TopLevelStruct));
90+
}
91+
92+
function deserializeTopLevelStruct(string memory json, string memory path) public pure returns (TopLevelStruct memory) {
93+
return abi.decode(vm.parseJsonType(json, path, schema_TopLevelStruct), (TopLevelStruct));
94+
}
95+
96+
function deserializeTopLevelStructArray(string memory json, string memory path) public pure returns (TopLevelStruct[] memory) {
97+
return abi.decode(vm.parseJsonTypeArray(json, path, schema_TopLevelStruct), (TopLevelStruct[]));
98+
}
99+
100+
function serialize(BindJsonTest.ContractLevelStruct memory value) internal pure returns (string memory) {
101+
return vm.serializeJsonType(schema_ContractLevelStruct, abi.encode(value));
102+
}
103+
104+
function serialize(BindJsonTest.ContractLevelStruct memory value, string memory objectKey, string memory valueKey) internal returns (string memory) {
105+
return vm.serializeJsonType(objectKey, valueKey, schema_ContractLevelStruct, abi.encode(value));
106+
}
107+
108+
function deserializeContractLevelStruct(string memory json) public pure returns (BindJsonTest.ContractLevelStruct memory) {
109+
return abi.decode(vm.parseJsonType(json, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct));
110+
}
111+
112+
function deserializeContractLevelStruct(string memory json, string memory path) public pure returns (BindJsonTest.ContractLevelStruct memory) {
113+
return abi.decode(vm.parseJsonType(json, path, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct));
114+
}
115+
116+
function deserializeContractLevelStructArray(string memory json, string memory path) public pure returns (BindJsonTest.ContractLevelStruct[] memory) {
117+
return abi.decode(vm.parseJsonTypeArray(json, path, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct[]));
118+
}
119+
}
120+
121+
"#]],
122+
);
123+
53124
cmd.forge_fuse().args(["test"]).assert_success();
54125
});

0 commit comments

Comments
 (0)