Skip to content

Commit 55f76c6

Browse files
committed
Merge remote-tracking branch 'upstream/main' into ast-expose-types
2 parents 2b2792f + b124729 commit 55f76c6

21 files changed

Lines changed: 1760 additions & 90 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ rustdoc-json = { version = "0.9.5" }
150150
semver = { version = "1.0.26", features = ["serde"] }
151151
serde = { version = "1.0.219", features = ["derive", "rc"] }
152152
serde_json = { version = "1.0.140", features = ["preserve_order"] }
153+
sha3 = { version = "0.10.8" }
153154
similar-asserts = { version = "1.7.0" }
154155
smallvec = { version = "1.15.0", features = ["union"] }
155156
solar = { version = "0.1.3", package = "solar-compiler" }

crates/solidity/outputs/cargo/crate/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ categories = [
2727
[features]
2828
default = []
2929
__private_ariadne_errors = ["dep:ariadne"]
30-
__private_backend_api = ["dep:indexmap", "dep:paste"]
30+
__private_backend_api = ["dep:indexmap", "dep:paste", "dep:sha3"]
3131
__private_wasm_apis = []
3232
__private_testing_utils = ["metaslang_bindings/__private_testing_utils"]
3333

@@ -39,6 +39,7 @@ metaslang_cst = { workspace = true }
3939
paste = { workspace = true, optional = true }
4040
semver = { workspace = true }
4141
serde = { workspace = true }
42+
sha3 = { workspace = true, optional = true }
4243
strum = { workspace = true }
4344
strum_macros = { workspace = true }
4445
thiserror = { workspace = true }
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
use std::ops::Div;
2+
3+
use sha3::{Digest, Keccak256};
4+
5+
use super::binder::Definition;
6+
use super::ir::ast::{
7+
ContractDefinitionStruct, FunctionDefinitionStruct, FunctionKind, FunctionMutability,
8+
FunctionVisibility, ParametersStruct, StateVariableDefinitionStruct, StateVariableMutability,
9+
StateVariableVisibility,
10+
};
11+
use super::types::{Type, TypeId};
12+
use super::SemanticAnalysis;
13+
use crate::cst::NodeId;
14+
15+
pub struct ContractAbi {
16+
pub node_id: NodeId,
17+
pub name: String,
18+
pub file_id: String,
19+
pub functions: Vec<FunctionAbi>,
20+
pub storage_layout: Vec<StorageItem>,
21+
}
22+
23+
pub struct FunctionAbi {
24+
pub node_id: NodeId,
25+
pub name: Option<String>,
26+
pub kind: FunctionKind,
27+
pub inputs: Vec<FunctionParameter>,
28+
pub outputs: Vec<FunctionParameter>,
29+
pub state_mutability: FunctionMutability,
30+
}
31+
32+
pub struct FunctionParameter {
33+
pub node_id: Option<NodeId>, // will be `None` if the function is a generated getter
34+
pub name: Option<String>,
35+
pub r#type: String,
36+
}
37+
38+
pub struct StorageItem {
39+
pub node_id: NodeId,
40+
pub label: String,
41+
pub slot: usize,
42+
pub offset: usize,
43+
pub r#type: String,
44+
}
45+
46+
impl ContractDefinitionStruct {
47+
// TODO: ideally the user wouldn't need to provide the file_id and we should
48+
// be able to obtain it here, but for that we need bi-directional tree
49+
// navigation
50+
pub fn compute_abi_with_file_id(&self, file_id: String) -> Option<ContractAbi> {
51+
let name = self.ir_node.name.unparse();
52+
let functions = self.compute_abi_functions()?;
53+
let storage_layout = self.compute_storage_layout()?;
54+
Some(ContractAbi {
55+
node_id: self.ir_node.node_id,
56+
name,
57+
file_id,
58+
functions,
59+
storage_layout,
60+
})
61+
}
62+
63+
fn compute_abi_functions(&self) -> Option<Vec<FunctionAbi>> {
64+
let mut functions = Vec::new();
65+
if let Some(constructor) = self.constructor() {
66+
functions.push(constructor.compute_abi()?);
67+
}
68+
for function in &self.compute_linearised_functions() {
69+
if function.is_externally_visible() {
70+
functions.push(function.compute_abi()?);
71+
}
72+
}
73+
for state_variable in &self.compute_linearised_state_variables() {
74+
if state_variable.is_externally_visible() {
75+
functions.push(state_variable.compute_abi()?);
76+
}
77+
}
78+
79+
functions.sort_by(|a, b| a.name.cmp(&b.name));
80+
Some(functions)
81+
}
82+
83+
fn compute_storage_layout(&self) -> Option<Vec<StorageItem>> {
84+
let mut storage_layout = Vec::new();
85+
// TODO: if the contract has a specific storage layout specifier, we
86+
// need to compute its value and use it as the base
87+
let mut ptr: usize = 0;
88+
for state_variable in &self.compute_linearised_state_variables() {
89+
let node_id = state_variable.ir_node.node_id;
90+
// skip constants and immutable variables, since they are not placed in storage
91+
// TODO: also, transient storage is laid out separately and we need
92+
// to support that as well
93+
if !matches!(
94+
state_variable.mutability(),
95+
StateVariableMutability::Mutable
96+
) {
97+
continue;
98+
}
99+
100+
let variable_type_id = self.semantic.binder.node_typing(node_id).as_type_id()?;
101+
let variable_size = self.semantic.storage_size_of_type_id(variable_type_id)?;
102+
103+
// check if we can pack the variable in the previous slot
104+
let remaining_bytes = SemanticAnalysis::SLOT_SIZE - (ptr % SemanticAnalysis::SLOT_SIZE);
105+
if variable_size > remaining_bytes {
106+
ptr += remaining_bytes;
107+
}
108+
109+
let label = state_variable.ir_node.name.unparse();
110+
let slot = ptr.div(SemanticAnalysis::SLOT_SIZE);
111+
let offset = ptr % SemanticAnalysis::SLOT_SIZE;
112+
let r#type = self.semantic.type_canonical_name(variable_type_id);
113+
storage_layout.push(StorageItem {
114+
node_id,
115+
label,
116+
slot,
117+
offset,
118+
r#type,
119+
});
120+
121+
// ready pointer for the next variable
122+
ptr += variable_size;
123+
}
124+
Some(storage_layout)
125+
}
126+
}
127+
128+
impl FunctionDefinitionStruct {
129+
pub fn is_externally_visible(&self) -> bool {
130+
matches!(
131+
self.visibility(),
132+
FunctionVisibility::Public | FunctionVisibility::External
133+
)
134+
}
135+
136+
pub fn compute_abi(&self) -> Option<FunctionAbi> {
137+
if !self.is_externally_visible() {
138+
return None;
139+
}
140+
let inputs = self.parameters().compute_abi()?;
141+
let outputs = if let Some(returns) = self.returns() {
142+
returns.compute_abi()?
143+
} else {
144+
Vec::new()
145+
};
146+
147+
Some(FunctionAbi {
148+
node_id: self.ir_node.node_id,
149+
name: self.ir_node.name.as_ref().map(|name| name.unparse()),
150+
kind: self.kind(),
151+
inputs,
152+
outputs,
153+
state_mutability: self.mutability(),
154+
})
155+
}
156+
157+
pub fn compute_selector(&self) -> Option<u32> {
158+
if !self.is_externally_visible() {
159+
return None;
160+
}
161+
let name = self.ir_node.name.as_ref()?.unparse();
162+
let signature = format!(
163+
"{name}({parameters})",
164+
parameters = self.parameters().compute_canonical_signature()?,
165+
);
166+
167+
Some(selector_from_signature(&signature))
168+
}
169+
}
170+
171+
impl ParametersStruct {
172+
pub(crate) fn compute_abi(&self) -> Option<Vec<FunctionParameter>> {
173+
let mut result = Vec::new();
174+
for parameter in &self.ir_nodes {
175+
let node_id = parameter.node_id;
176+
let name = parameter.name.as_ref().map(|name| name.unparse());
177+
// Bail out with `None` if any of the parameters fails typing
178+
let type_id = self.semantic.binder.node_typing(node_id).as_type_id()?;
179+
let r#type = self.semantic.type_canonical_name(type_id);
180+
result.push(FunctionParameter {
181+
node_id: Some(node_id),
182+
name,
183+
r#type,
184+
});
185+
}
186+
Some(result)
187+
}
188+
189+
pub(crate) fn compute_canonical_signature(&self) -> Option<String> {
190+
let mut result = Vec::new();
191+
for parameter in &self.ir_nodes {
192+
let node_id = parameter.node_id;
193+
// Bail out with `None` if any of the parameters fails typing
194+
let type_id = self.semantic.binder.node_typing(node_id).as_type_id()?;
195+
result.push(self.semantic.type_canonical_name(type_id));
196+
}
197+
Some(result.join(","))
198+
}
199+
}
200+
201+
impl StateVariableDefinitionStruct {
202+
pub fn is_externally_visible(&self) -> bool {
203+
matches!(self.visibility(), StateVariableVisibility::Public)
204+
}
205+
206+
fn extract_getter_type_parameters_abi(
207+
&self,
208+
) -> Option<(Vec<FunctionParameter>, Vec<FunctionParameter>)> {
209+
let Definition::StateVariable(definition) = self
210+
.semantic
211+
.binder
212+
.find_definition_by_id(self.ir_node.node_id)?
213+
else {
214+
unreachable!("definition is not a state variable");
215+
};
216+
self.semantic
217+
.extract_function_type_parameters_abi(definition.getter_type_id?)
218+
}
219+
220+
pub fn compute_abi(&self) -> Option<FunctionAbi> {
221+
if !self.is_externally_visible() {
222+
return None;
223+
}
224+
let (inputs, outputs) = self.extract_getter_type_parameters_abi()?;
225+
226+
Some(FunctionAbi {
227+
node_id: self.ir_node.node_id,
228+
name: Some(self.ir_node.name.unparse()),
229+
kind: FunctionKind::Regular,
230+
inputs,
231+
outputs,
232+
state_mutability: FunctionMutability::View,
233+
})
234+
}
235+
236+
pub fn compute_selector(&self) -> Option<u32> {
237+
if !self.is_externally_visible() {
238+
return None;
239+
}
240+
let (inputs, _) = self.extract_getter_type_parameters_abi()?;
241+
242+
let signature = format!(
243+
"{name}({parameters})",
244+
name = self.ir_node.name.unparse(),
245+
parameters = inputs
246+
.into_iter()
247+
.map(|parameter| parameter.r#type)
248+
.collect::<Vec<_>>()
249+
.join(","),
250+
);
251+
252+
Some(selector_from_signature(&signature))
253+
}
254+
}
255+
256+
impl SemanticAnalysis {
257+
fn extract_function_type_parameters_abi(
258+
&self,
259+
type_id: TypeId,
260+
) -> Option<(Vec<FunctionParameter>, Vec<FunctionParameter>)> {
261+
let Type::Function(function_type) = self.types.get_type_by_id(type_id) else {
262+
return None;
263+
};
264+
let inputs = function_type
265+
.parameter_types
266+
.iter()
267+
.map(|parameter_type_id| FunctionParameter {
268+
node_id: None,
269+
name: None,
270+
r#type: self.type_canonical_name(*parameter_type_id),
271+
})
272+
.collect();
273+
let outputs = vec![FunctionParameter {
274+
node_id: None,
275+
name: None,
276+
r#type: self.type_canonical_name(function_type.return_type),
277+
}];
278+
Some((inputs, outputs))
279+
}
280+
}
281+
282+
fn selector_from_signature(signature: &str) -> u32 {
283+
let mut hasher = Keccak256::new();
284+
hasher.update(signature.as_bytes());
285+
let result = hasher.finalize();
286+
287+
let selector_bytes: [u8; 4] = result[0..4].try_into().unwrap();
288+
u32::from_be_bytes(selector_bytes)
289+
}

0 commit comments

Comments
 (0)