diff --git a/ethers-solc/src/artifact_output/configurable.rs b/ethers-solc/src/artifact_output/configurable.rs index 7d4fb06c..3d3d0bae 100644 --- a/ethers-solc/src/artifact_output/configurable.rs +++ b/ethers-solc/src/artifact_output/configurable.rs @@ -16,9 +16,9 @@ use crate::{ BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection, }, - Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates, - GeneratedSource, LosslessAbi, LosslessMetadata, Metadata, Offsets, Settings, StorageLayout, - UserDoc, + CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates, + GeneratedSource, LosslessAbi, LosslessMetadata, Metadata, Offsets, Settings, SourceUnit, + StorageLayout, UserDoc, }, sources::VersionedSourceFile, ArtifactOutput, SolcConfig, SolcError, SourceFile, @@ -68,7 +68,7 @@ pub struct ConfigurableContractArtifact { #[serde(default, skip_serializing_if = "Option::is_none")] pub ewasm: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub ast: Option, + pub ast: Option, /// The identifier of the source file #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option, diff --git a/ethers-solc/src/artifacts/ast/macros.rs b/ethers-solc/src/artifacts/ast/macros.rs new file mode 100644 index 00000000..ebad35c7 --- /dev/null +++ b/ethers-solc/src/artifacts/ast/macros.rs @@ -0,0 +1,56 @@ +/// Macro that expands to a struct with common AST node fields. +macro_rules! ast_node { + ( + $(#[$struct_meta:meta])* + struct $name:ident { + $( + $(#[$field_meta:meta])* + $field:ident: $ty:ty + ),* $(,)* + } + ) => { + $(#[$struct_meta])* + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct $name { + pub id: usize, + #[serde(with = "serde_helpers::display_from_str")] + pub src: SourceLocation, + $( + $(#[$field_meta])* + pub $field: $ty + ),* + } + }; +} + +/// A macro that expands to a struct with common expression node fields. +macro_rules! expr_node { + ( + $(#[$struct_meta:meta])* + struct $name:ident { + $( + $(#[$field_meta:meta])* + $field:ident: $ty:ty + ),* $(,)* + } + ) => { + ast_node!( + $(#[$struct_meta])* + struct $name { + argument_types: Vec, + is_constant: bool, + is_l_value: bool, + is_pure: bool, + l_value_requested: bool, + type_descriptions: TypeDescriptions, + $( + $(#[$field_meta])* + $field: $ty + ),* + } + ); + } +} + +pub(crate) use ast_node; +pub(crate) use expr_node; diff --git a/ethers-solc/src/artifacts/ast.rs b/ethers-solc/src/artifacts/ast/misc.rs similarity index 52% rename from ethers-solc/src/artifacts/ast.rs rename to ethers-solc/src/artifacts/ast/misc.rs index 122054f8..09976d46 100644 --- a/ethers-solc/src/artifacts/ast.rs +++ b/ethers-solc/src/artifacts/ast/misc.rs @@ -1,63 +1,5 @@ -//! Bindings for solc's `ast` output field - -use crate::artifacts::serde_helpers; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt, fmt::Write, str::FromStr}; - -/// Represents the AST field in the solc output -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Ast { - #[serde(rename = "absolutePath")] - pub absolute_path: String, - pub id: usize, - #[serde(default, rename = "exportedSymbols")] - pub exported_symbols: BTreeMap>, - #[serde(rename = "nodeType")] - pub node_type: NodeType, - #[serde(with = "serde_helpers::display_from_str")] - pub src: SourceLocation, - #[serde(default)] - pub nodes: Vec, - - /// Node attributes that were not deserialized. - #[serde(flatten)] - pub other: BTreeMap, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Node { - /// The node ID. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub id: Option, - - /// The node type. - #[serde(rename = "nodeType")] - pub node_type: NodeType, - - /// The location of the node in the source file. - #[serde(with = "serde_helpers::display_from_str")] - pub src: SourceLocation, - - /// Child nodes for some node types. - #[serde(default)] - pub nodes: Vec, - - /// Body node for some node types. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub body: Option>, - - /// Node attributes that were not deserialized. - #[serde(flatten)] - pub other: BTreeMap, -} - -impl Node { - /// Deserialize a serialized node attribute. - pub fn attribute(&self, key: impl AsRef) -> Option { - // TODO: Can we avoid this clone? - self.other.get(key.as_ref()).and_then(|v| serde_json::from_value(v.clone()).ok()) - } -} +use serde::{Deserialize, Serialize}; +use std::{fmt, fmt::Write, str::FromStr}; /// Represents the source location of a node: `::`. /// @@ -119,8 +61,7 @@ impl fmt::Display for SourceLocation { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum NodeType { - // Expressions +pub enum Expression { Assignment, BinaryOperation, Conditional, @@ -135,8 +76,50 @@ pub enum NodeType { NewExpression, TupleExpression, UnaryOperation, +} - // Statements +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum StateMutability { + Payable, + Pure, + Nonpayable, + View, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum TypeName { + ArrayTypeName, + ElementaryTypeName, + FunctionTypeName, + Mapping, + UserDefinedTypeName, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Mutability { + Mutable, + Immutable, + Constant, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum StorageLocation { + Calldata, + Default, + Memory, + Storage, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Visibility { + External, + Public, + Internal, + Private, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Statement { Block, Break, Continue, @@ -152,10 +135,11 @@ pub enum NodeType { TryStatement, UncheckedBlock, VariableDeclarationStatement, - VariableDeclaration, WhileStatement, +} - // Yul statements +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum YulStatement { YulAssignment, YulBlock, YulBreak, @@ -167,51 +151,17 @@ pub enum NodeType { YulIf, YulSwitch, YulVariableDeclaration, +} - // Yul expressions +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum YulExpression { YulFunctionCall, YulIdentifier, YulLiteral, +} - // Yul literals +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum YulLiteral { YulLiteralValue, - YulHexValue, - - // Definitions - ContractDefinition, - FunctionDefinition, - EventDefinition, - ErrorDefinition, - ModifierDefinition, - StructDefinition, - EnumDefinition, - UserDefinedValueTypeDefinition, - - // Directives - PragmaDirective, - ImportDirective, - UsingForDirective, - - // Misc - SourceUnit, - InheritanceSpecifier, - ElementaryTypeName, - FunctionTypeName, - ParameterList, - TryCatchClause, - ModifierInvocation, - - /// An unknown AST node type. - Other(String), -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_parse_ast() { - let ast = include_str!("../../test-data/ast/ast-erc4626.json"); - let _ast: Ast = serde_json::from_str(ast).unwrap(); - } + YulLiteralHexValue, } diff --git a/ethers-solc/src/artifacts/ast/mod.rs b/ethers-solc/src/artifacts/ast/mod.rs new file mode 100644 index 00000000..b257a95f --- /dev/null +++ b/ethers-solc/src/artifacts/ast/mod.rs @@ -0,0 +1,248 @@ +//! Bindings for solc's `ast` output field +mod macros; +mod misc; + +pub use misc::*; +pub mod util; +pub mod visitor; + +use crate::artifacts::serde_helpers; +use macros::{ast_node, expr_node}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +// TODO: Node ID + +ast_node!( + /// The root node of a Solidity AST. + struct SourceUnit { + #[serde(rename = "absolutePath")] + absolute_path: String, + #[serde(default, rename = "exportedSymbols")] + exported_symbols: BTreeMap>, + license: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + nodes: Vec, + } +); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum SourceUnitPart { + PragmaDirective, + ImportDirective, + UsingForDirective, + VariableDeclaration, + EnumDefinition, + ErrorDefinition, + FunctionDefinition, + StructDefinition, + UserDefinedValueTypeDefinition, + ContractDefinition, +} + +ast_node!( + struct ContractDefinition { + name: String, + name_location: Option, + is_abstract: bool, + base_contracts: Vec, + canonical_name: Option, + contract_dependencies: Vec, + kind: ContractKind, + documentation: Option, + fully_implemented: bool, + linearized_base_contracts: Vec, + nodes: Vec, + scope: usize, + used_errors: Vec, + } +); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ContractKind { + Contract, + Interface, + Library, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum ContractDefinitionPart { + EnumDefinition, + ErrorDefinition, + EventDefinition, + FunctionDefinition, + ModifierDefinition, + StructDefinition, + UserDefinedValueTypeDefinition, + UsingForDirective, + VariableDeclaration, +} + +ast_node!( + struct InheritanceSpecifier { + arguments: Vec, + base_name: UserDefinedTypeNameOrIdentifierPath, + } +); + +// TODO: Better name +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum UserDefinedTypeNameOrIdentifierPath { + UserDefinedTypeName, + IdentifierPath, +} + +expr_node!( + struct Assignment { + lhs: Expression, + operator: AssignmentOperator, + rhs: Expression, + } +); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum AssignmentOperator { + /// = + Assign, + /// += + AddAssign, + /// -= + SubAssign, + /// *= + MulAssign, + /// /= + DivAssign, + /// %= + ModAssign, + /// |= + OrAssign, + /// &= + AndAssign, + /// ^= + XorAssign, + /// >>= + ShrAssign, + /// <<= + ShlAssign, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TypeDescriptions { + pub type_identifier: Option, + pub type_string: Option, +} + +ast_node!( + struct BinaryOperation { + common_type: TypeDescriptions, + lhs: Expression, + operator: BinaryOperator, + rhs: Expression, + } +); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum BinaryOperator { + /// + + Add, + /// - + Sub, + /// * + Mul, + /// / + Div, + /// % + Mod, + /// ** + Pow, + /// && + And, + /// || + Or, + /// != + NotEqual, + /// == + Equal, + /// < + LessThan, + /// <= + LessThanOrEqual, + /// > + GreaterThan, + /// >= + GreaterThanOrEqual, + /// ^ + Xor, + /// & + BitAnd, + /// | + BitOr, + /// << + Shl, + /// >> + Shr, +} + +expr_node!( + struct Conditional { + condition: Expression, + false_expression: Expression, + true_expression: Expression, + } +); + +expr_node!( + struct ElementaryTypeNameExpression { + type_name: ElementaryTypeName, + } +); + +ast_node!( + struct ElementaryTypeName { + type_descriptions: TypeDescriptions, + name: String, + state_mutability: Option, + } +); + +expr_node!( + struct FunctionCall { + arguments: Vec, + expression: Expression, + kind: FunctionCallKind, + names: Vec, + try_call: bool, + } +); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum FunctionCallKind { + FunctionCall, + TypeConversion, + StructConstructorCall, +} + +expr_node!( + struct FunctionCallOptions { + expression: Expression, + names: Vec, + options: Vec, + } +); + +ast_node!( + struct StructuredDocumentation { + text: String, + } +); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_parse_ast() { + let ast = include_str!("../../test-data/ast/ast-erc4626.json"); + let _ast: SourceUnit = serde_json::from_str(ast).unwrap(); + } +} diff --git a/ethers-solc/src/artifacts/ast/util.rs b/ethers-solc/src/artifacts/ast/util.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ethers-solc/src/artifacts/ast/util.rs @@ -0,0 +1 @@ + diff --git a/ethers-solc/src/artifacts/ast/visitor.rs b/ethers-solc/src/artifacts/ast/visitor.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ethers-solc/src/artifacts/ast/visitor.rs @@ -0,0 +1 @@ + diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index 0a0d1dd0..7b42276b 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -1835,7 +1835,7 @@ pub struct SecondarySourceLocation { pub struct SourceFile { pub id: u32, #[serde(default, with = "serde_helpers::empty_json_object_opt")] - pub ast: Option, + pub ast: Option, } // === impl SourceFile === @@ -1846,7 +1846,7 @@ impl SourceFile { pub fn contains_contract_definition(&self) -> bool { if let Some(ref ast) = self.ast { // contract definitions are only allowed at the source-unit level - return ast.nodes.iter().any(|node| node.node_type == NodeType::ContractDefinition) + return ast.nodes.iter().any(|node| matches!(node, SourceUnitPart::ContractDefinition)) // abstract contract, interfaces: ContractDefinition }