├── .cargo └── config ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── eth-lang-utils ├── Cargo.toml └── src │ ├── ast │ └── mod.rs │ └── lib.rs ├── solast ├── Cargo.toml └── src │ ├── analysis │ ├── abi_encoding.rs │ ├── abstract_contracts.rs │ ├── address_balance.rs │ ├── address_zero.rs │ ├── array_assignment.rs │ ├── assert_usage.rs │ ├── assignment_comparisons.rs │ ├── check_effects_interactions.rs │ ├── comparison_utilization.rs │ ├── divide_before_multiply.rs │ ├── explicit_variable_return.rs │ ├── external_calls_in_loop.rs │ ├── floating_solidity_version.rs │ ├── ineffectual_statements.rs │ ├── inline_assembly.rs │ ├── invalid_using_for_directives.rs │ ├── large_literals.rs │ ├── manipulatable_balance_usage.rs │ ├── missing_return.rs │ ├── mod.rs │ ├── no_spdx_identifier.rs │ ├── node_modules_imports.rs │ ├── redundant_assignments.rs │ ├── redundant_comparisons.rs │ ├── redundant_getter_function.rs │ ├── redundant_imports.rs │ ├── redundant_state_variable_access.rs │ ├── require_without_message.rs │ ├── safe_erc20_functions.rs │ ├── secure_ether_transfer.rs │ ├── selfdestruct_usage.rs │ ├── state_variable_mutability.rs │ ├── state_variable_shadowing.rs │ ├── storage_array_loop.rs │ ├── tight_variable_packing.rs │ ├── unchecked_casting.rs │ ├── unchecked_erc20_transfer.rs │ ├── unnecessary_pragmas.rs │ ├── unpaid_payable_functions.rs │ ├── unreferenced_state_variables.rs │ ├── unrestricted_setter_functions.rs │ └── unused_return.rs │ ├── brownie.rs │ ├── foundry.rs │ ├── hardhat.rs │ ├── main.rs │ ├── report.rs │ ├── todo_list.rs │ └── truffle.rs ├── solidity ├── Cargo.toml └── src │ ├── ast │ ├── blocks.rs │ ├── builder.rs │ ├── contracts.rs │ ├── documentation.rs │ ├── enumerations.rs │ ├── errors.rs │ ├── events.rs │ ├── expressions.rs │ ├── functions.rs │ ├── identifiers.rs │ ├── import_directives.rs │ ├── literals.rs │ ├── mod.rs │ ├── modifiers.rs │ ├── pragma_directives.rs │ ├── source_units.rs │ ├── statements.rs │ ├── structures.rs │ ├── types.rs │ ├── user_defined_value_types.rs │ ├── using_for_directives.rs │ ├── variables.rs │ └── visitor.rs │ └── lib.rs └── yul ├── Cargo.toml └── src ├── ast └── mod.rs └── lib.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "target-cpu=native"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["eth-lang-utils", "solast", "solidity", "yul"] 3 | 4 | [profile.release.package.solast] 5 | opt-level = "s" 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 camden-smallwood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SolAST 2 | 3 | Solidity 0.8.X AST parsing and analysis in Rust. 4 | 5 | Some legacy versions of Solidity are inherently supported (0.5.X-0.7.X), but the focus is primarily on Solidity 0.8.X and above. 6 | 7 | * [Usage](#usage) 8 | * [Analyzers](#analyzers) 9 | 10 | ## Usage 11 | 12 | ``` 13 | cargo run --release -- \ 14 | [--output_format=] \ 15 | [--analyzer_name1] \ 16 | [--analyzer_nameN] \ 17 | [--todo_list] \ 18 | [--contract_path=] \ 19 | [--contract=] \ 20 | 21 | ``` 22 | 23 | | Option | Description | 24 | |-:|-| 25 | | `--output_format=` | Specifies the report output format. Can be `plain_text` or `json`. | 26 | | `--analyzer_name` | Specifies an analyzer to enable (see below). All analyzers are enabled by default. | 27 | | `--todo_list` | Generates a TODO list for each contract's contents in Markdown format. | 28 | | `--contract_path=` | Specifies a Solidity source file to include in analysis. | 29 | | `--contract=` | Specifies a specific contract to include in analysis. | 30 | | `` | Specifies a precompiled project directory to include in analysis. | 31 | 32 | SolAST can parse and analyze the AST of individual Solidity contract files and precompiled projects. 33 | 34 | For project-based analysis, SolAST requires utilization of one of the following build systems: 35 | * [Brownie](https://eth-brownie.readthedocs.io/en/stable/) 36 | * [Hardhat](https://hardhat.org/) 37 | * [Truffle](https://www.trufflesuite.com/) 38 | 39 | If you only have `.sol` files, you can create a quick truffle project by performing the following: 40 | 41 | 1. Open a terminal. 42 | 2. Create a directory for your project to be contained in with `mkdir solidity-project` 43 | 3. Move into the newly-created project directory with `cd solidity-project`. 44 | 4. Initialize a node module for the project with `npm init -y`. 45 | 5. Initialize the truffle project with `truffle init`. 46 | 6. Copy all of your `.sol` files into `contracts/`. 47 | 48 | ```Shell 49 | mkdir solidity-project 50 | cd solidity-project 51 | npm init -y 52 | truffle init 53 | cp ~/Downloads/awesome-contracts/*.sol contracts/ 54 | ``` 55 | 56 | Use your favorite text editor to change the `solc` version in `truffle-config.js` to `0.8.13` (or the relevant `0.8.X`). 57 | 58 | ```js 59 | module.exports = { 60 | networks: {}, 61 | mocha: {}, 62 | compilers: { 63 | solc: { 64 | version: "0.8.13", 65 | } 66 | } 67 | }; 68 | ``` 69 | 70 | Compile your truffle project with `npm i && rm -rf build && truffle compile`. 71 | You should have a `build/contracts/` folder with `*.json` files inside of it afterwards. 72 | 73 | Now you can supply the path to the truffle project directory to SolAST with the following: 74 | ```Shell 75 | cargo run --release -- /path/to/project/ 76 | ``` 77 | 78 | If you would like to save text output to an `out.txt` file instead of printing to the terminal, use the following: 79 | ```Shell 80 | cargo run --release -- /path/to/project/ > out.txt 81 | ``` 82 | 83 | On the first run it may take a few minutes to optimize and compile, but subsequent runs will be quite fast in release mode. 84 | 85 | ## Analyzers 86 | 87 | *WARNING:* Any analyzer marked (WIP) may not display output or may provide false positives. This is to be expected, as the code has not been fully implemented yet. Please file an issue if you come across a false positive from an analyzer which is not marked (WIP). 88 | 89 | | Name | Description | 90 | |-|-| 91 | | `no_spdx_identifier` | Determines if a source file was compiled without a `SPDX` identifier specified. | 92 | | `floating_solidity_version` | Determines if a pragma directive specifies a floating/unlocked Sollidity version. | 93 | | `node_modules_imports` | Determines if an import directive attempts to locally import from the `node_modules` directory. | 94 | | `redundant_imports` | Determines if any import directives are redundant due to the specified path being already previously imported. | 95 | | `abstract_contracts` | Determines if a contract specifies an internal constructor without declaring the contract `abstract`. | 96 | | `large_literals` | Determines if an expression contains a large literal value, which may be difficult to read or interpretted incorrectly. | 97 | | ~~`tight_variable_packing`~~ (WIP) | Determines if a contract or structure contains loose variable packing which can be more efficiently packed in order to decrease the number of required storage slots. | 98 | | `redundant_getter_function` | Determines if a contract contains a function which returns a state variable instead of providing outside access to the state variable. | 99 | | `require_without_message` | Determines if a `require` statement does not contain a message string. | 100 | | `state_variable_shadowing` | Determines if a contract declares a local or state variable which shadows another state variable in the contract's inheritance hierarchy. | 101 | | `explicit_variable_return` | Determines if a function returns local variables explicitly over declaring and utilizing named return variables. | 102 | | `unused_return` | Determines if the values returned from a function call go unused. | 103 | | `storage_array_loop` | Determines if a loop's condition relies on the `length` member of an array state variable. | 104 | | `external_calls_in_loop` | Determines if any functions or modifiers contain any loops which performs calls to external functions. | 105 | | `check_effects_interactions` | Determines if any functions or modifiers ignore the [Check Effects Interactions](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html) pattern. | 106 | | `secure_ether_transfer` | Determines if any functions or modifiers ignores the [Secure Ether Transfer](https://fravoll.github.io/solidity-patterns/secure_ether_transfer.html) pattern. 107 | | `safe_erc20_functions` | Determines if any functions or modifiers utilize unsafe ERC-20 functionality. | 108 | | `unchecked_erc20_transfer` | Determines if any functions or modifiers perform ERC-20 transfers without checking the value being transferred, which can revert if zero. | 109 | | `unpaid_payable_functions` | Determines if any functions or modifiers perform calls to `payable` functions without paying. | 110 | | ~~`divide_before_multiply`~~ (WIP) | Determines if any functions or modifiers perform multiplication on the result of a division, which can truncate. | 111 | | ~~`comparison_utilization`~~ (WIP) | Determines if an `if` statement's condition contains a comparison without utilizing either compared value in its `true` or `false` branches. | 112 | | `assignment_comparisons` | Determines if any conditional expressions contain assignments, i.e: `require(owner = msg.sender);`, `if (releaseTime = block.timestamp)`, etc. | 113 | | `state_variable_mutability` | Determines if any state variables can be made `constant` or `immutable`. | 114 | | `unused_state_variables` | Determines if any state variables are unused within a contract. | 115 | | `ineffectual_statements` | Determines if any statements are ineffectual, i.e: `balance[msg.sender];` | 116 | | `inline_assembly` | Determines if any functions or modifiers contain inline Yul assembly usage and checks for arbitrary data passing. | 117 | | ~~`unchecked_casting`~~ (WIP) | Determines if a value expression is cast without checking its value beforehand, which can result it invalid values. | 118 | | ~~`unnecessary_pragmas`~~ (WIP) | Determines if any pragma directives are unnecessary for a specific Solidity version. | 119 | | `missing_return` | Determines if a function is missing an explicity return statement without assigning to a named return variable. | 120 | | ~~`redundant_state_variable_access`~~ (WIP) | Determines if any functions or modifiers access state variables multiple times without updating their value between each access. | 121 | | ~~`redundant_comparisons`~~ (WIP) | Determines if any comparisons are redundant, i.e: `true != false`, `uint16(uint8(x)) < 256`, etc. | 122 | | `assert_usage` | Determines if any functions or modifiers utilize `assert(...)`, which should not be used in production. | 123 | | `selfdestruct_usage` | Determines if any functions or modifiers perform a `selfdestruct`. | 124 | | ~~`unrestricted_setter_functions`~~ (WIP) | Determines if any functions allow setting of state variable values without any access restriction or requirements. | 125 | | ~~`manipulatable_balance_usage`~~ (WIP) | Determines if any functions or modifiers contain `balance` usage which can potentially be manipulated, i.e: `address(this).balance`, `IERC20(token).balance()`, etc. | 126 | | ~~`redundant_assignments`~~ (WIP) | Determines if any functions or modifiers perform assignments which are redundant, i.e: `(x, x) = getValues();` | 127 | | `invalid_using_for_directives` | Determines if any using-for directives specify types which do not have functions provided by the specified library. | 128 | | `abi_encoding` | Determines if any functions or modifiers attempt to use `abi.encodePacked` on multiple arguments when any of are variably-sized arrays, which can result in hash collisions. | 129 | -------------------------------------------------------------------------------- /eth-lang-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "eth-lang-utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1.0", features = ["derive"] } 8 | -------------------------------------------------------------------------------- /eth-lang-utils/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub type NodeID = i64; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 7 | pub enum NodeType { 8 | SourceUnit, 9 | PragmaDirective, 10 | ImportDirective, 11 | UsingForDirective, 12 | ContractDefinition, 13 | InheritanceSpecifier, 14 | OverrideSpecifier, 15 | IdentifierPath, 16 | StructuredDocumentation, 17 | VariableDeclaration, 18 | Mapping, 19 | ElementaryTypeName, 20 | ElementaryTypeNameExpression, 21 | ArrayTypeName, 22 | TupleExpression, 23 | FunctionDefinition, 24 | ParameterList, 25 | Block, 26 | UncheckedBlock, 27 | Continue, 28 | Break, 29 | Return, 30 | Throw, 31 | Literal, 32 | Conditional, 33 | Identifier, 34 | IndexAccess, 35 | IndexRangeAccess, 36 | MemberAccess, 37 | Assignment, 38 | FunctionCall, 39 | FunctionCallOptions, 40 | FunctionTypeName, 41 | NewExpression, 42 | ExpressionStatement, 43 | VariableDeclarationStatement, 44 | IfStatement, 45 | TryCatchClause, 46 | UnaryOperation, 47 | BinaryOperation, 48 | EventDefinition, 49 | ErrorDefiniton, 50 | EmitStatement, 51 | PlaceholderStatement, 52 | TryStatement, 53 | RevertStatement, 54 | ForStatement, 55 | WhileStatement, 56 | DoWhileStatement, 57 | ModifierDefinition, 58 | ModifierInvocation, 59 | EnumDefinition, 60 | EnumValue, 61 | StructDefinition, 62 | UserDefinedTypeName, 63 | InlineAssembly, 64 | YulLiteral, 65 | YulTypedName, 66 | YulIf, 67 | YulSwitch, 68 | YulCase, 69 | YulForLoop, 70 | YulBreak, 71 | YulContinue, 72 | YulLeave, 73 | YulFunctionDefinition, 74 | YulFunctionCall, 75 | YulExpressionStatement, 76 | YulAssignment, 77 | YulIdentifier, 78 | YulVariableDeclaration, 79 | YulBlock, 80 | } 81 | -------------------------------------------------------------------------------- /eth-lang-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | -------------------------------------------------------------------------------- /solast/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solast" 3 | version = "0.1.0" 4 | authors = ["Camden Smallwood "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { version = "1.0", features = ["derive"] } 9 | serde_json = "1.0" 10 | simd-json = "0.7" 11 | eth-lang-utils = { path = "../eth-lang-utils" } 12 | solang-parser = "0.3.0" 13 | solidity = { path = "../solidity" } 14 | yul = { path = "../yul" } 15 | primitive-types = "0.10.1" 16 | -------------------------------------------------------------------------------- /solast/src/analysis/abi_encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 5 | 6 | pub struct AbiEncodingVisitor { 7 | report: Rc>, 8 | declaration_type_names: HashMap 9 | } 10 | 11 | impl AbiEncodingVisitor { 12 | pub fn new(report: Rc>) -> Self { 13 | Self { 14 | report, 15 | declaration_type_names: HashMap::new(), 16 | } 17 | } 18 | 19 | fn add_report_entry( 20 | &mut self, 21 | source_unit_path: String, 22 | contract_definition: &ContractDefinition, 23 | definition_node: &ContractDefinitionNode, 24 | source_line: usize, 25 | expression: &dyn std::fmt::Display, 26 | ) { 27 | self.report.borrow_mut().add_entry( 28 | source_unit_path, 29 | Some(source_line), 30 | format!( 31 | "{} contains the potential for hash collisions: `{}`", 32 | contract_definition.definition_node_location(definition_node), 33 | expression, 34 | ), 35 | ); 36 | } 37 | } 38 | 39 | impl AstVisitor for AbiEncodingVisitor { 40 | fn visit_variable_declaration<'a, 'b>(&mut self, context: &mut VariableDeclarationContext<'a, 'b>) -> std::io::Result<()> { 41 | // 42 | // Store the type of any variable declarations 43 | // 44 | 45 | if let Some(type_name) = context.variable_declaration.type_name.as_ref() { 46 | if self.declaration_type_names.contains_key(&context.variable_declaration.id) { 47 | return Ok(()) 48 | } 49 | 50 | self.declaration_type_names.insert(context.variable_declaration.id, type_name.clone()); 51 | } 52 | 53 | Ok(()) 54 | } 55 | 56 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> std::io::Result<()> { 57 | // 58 | // Only check for calls to abi.encodePacked(...) 59 | // 60 | 61 | if let Expression::MemberAccess(MemberAccess { expression, member_name, .. }) = context.function_call.expression.as_ref() { 62 | if let Expression::Identifier(Identifier { name, .. }) = expression.as_ref() { 63 | if name != "abi" || member_name != "encodePacked" { 64 | return Ok(()) 65 | } 66 | } else { 67 | return Ok(()) 68 | } 69 | } else { 70 | return Ok(()) 71 | } 72 | 73 | // 74 | // Only check if multiple arguments are supplied: abi.encodePacked(as, bs, ...) 75 | // 76 | 77 | if context.function_call.arguments.len() < 2 { 78 | return Ok(()) 79 | } 80 | 81 | // 82 | // Determine if any parameters contain any variably-sized arrays 83 | // 84 | 85 | let mut any_arguments_variably_sized = false; 86 | 87 | for expression in context.function_call.arguments.iter() { 88 | if any_arguments_variably_sized { 89 | break; 90 | } 91 | 92 | for referenced_declaration in expression.referenced_declarations() { 93 | if let Some(TypeName::ArrayTypeName(ArrayTypeName { length: None, .. })) = self.declaration_type_names.get(&referenced_declaration) { 94 | any_arguments_variably_sized = true; 95 | break; 96 | } 97 | } 98 | } 99 | 100 | // 101 | // If so, print a message warning about potential hash collisions 102 | // 103 | 104 | if any_arguments_variably_sized { 105 | self.add_report_entry( 106 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 107 | context.contract_definition, 108 | context.definition_node, 109 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 110 | context.function_call, 111 | ); 112 | } 113 | 114 | Ok(()) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /solast/src/analysis/abstract_contracts.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub struct AbstractContractsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl AbstractContractsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for AbstractContractsVisitor { 16 | fn visit_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> std::io::Result<()> { 17 | // 18 | // Only check function definitions associated with constructors 19 | // 20 | 21 | if context.function_definition.kind != FunctionKind::Constructor { 22 | return Ok(()) 23 | } 24 | 25 | // 26 | // Only check function definitions with internal visibility 27 | // 28 | 29 | if context.function_definition.visibility != Visibility::Internal { 30 | return Ok(()) 31 | } 32 | 33 | // 34 | // If the constructor is marked internal and the contract is not abstract, print a message 35 | // 36 | 37 | if let None | Some(false) = context.contract_definition.is_abstract { 38 | self.report.borrow_mut().add_entry( 39 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 40 | Some(context.current_source_unit.source_line(context.function_definition.src.as_str())?), 41 | format!( 42 | "{} is marked {} instead of marking `{}` as abstract", 43 | context.contract_definition.definition_node_location(context.definition_node), 44 | context.function_definition.visibility, 45 | context.contract_definition.name, 46 | ), 47 | ); 48 | } 49 | 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /solast/src/analysis/address_balance.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub struct AddressBalanceVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl AddressBalanceVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | expression: &dyn std::fmt::Display, 21 | external: bool, 22 | ) { 23 | self.report.borrow_mut().add_entry( 24 | source_unit_path, 25 | Some(source_line), 26 | format!( 27 | "{} contains `{}` usage, which can be optimized with assembly: `{}`", 28 | contract_definition.definition_node_location(definition_node), 29 | expression, 30 | if external { 31 | "assembly { bal := balance(addr); }" 32 | } else { 33 | "assembly { bal := selfbalance(); }" 34 | } 35 | ), 36 | ); 37 | } 38 | } 39 | 40 | impl AstVisitor for AddressBalanceVisitor { 41 | fn visit_member_access<'a, 'b>(&mut self, context: &mut MemberAccessContext<'a, 'b>) -> std::io::Result<()> { 42 | if context.member_access.member_name != "balance" { 43 | return Ok(()) 44 | } 45 | 46 | let (expression, arguments) = match context.member_access.expression.as_ref() { 47 | Expression::FunctionCall(FunctionCall { 48 | expression, 49 | arguments, 50 | .. 51 | }) if arguments.len() == 1 => (expression, arguments), 52 | 53 | _ => return Ok(()) 54 | }; 55 | 56 | match expression.as_ref() { 57 | Expression::ElementaryTypeNameExpression(ElementaryTypeNameExpression { 58 | type_name: TypeName::ElementaryTypeName(ElementaryTypeName { 59 | name, 60 | .. 61 | }), 62 | .. 63 | }) if name == "address" => {} 64 | 65 | _ => return Ok(()) 66 | } 67 | 68 | if let Some(Expression::Identifier(Identifier { 69 | name, 70 | .. 71 | })) = arguments.first() { 72 | self.add_report_entry( 73 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 74 | context.contract_definition, 75 | context.definition_node, 76 | context.current_source_unit.source_line(context.member_access.src.as_str())?, 77 | context.member_access, 78 | name != "this" 79 | ); 80 | } 81 | 82 | Ok(()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /solast/src/analysis/address_zero.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub struct AddressZeroVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl AddressZeroVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | expression: &dyn std::fmt::Display, 21 | ) { 22 | self.report.borrow_mut().add_entry( 23 | source_unit_path, 24 | Some(source_line), 25 | format!( 26 | "{} contains `{}` usage, which can be optimized with assembly: `{}`", 27 | contract_definition.definition_node_location(definition_node), 28 | expression, 29 | "assembly { if iszero(addr) { ... } }", 30 | ), 31 | ); 32 | } 33 | } 34 | 35 | impl AstVisitor for AddressZeroVisitor { 36 | fn visit_binary_operation<'a, 'b>( 37 | &mut self, 38 | context: &mut BinaryOperationContext<'a, 'b>, 39 | ) -> std::io::Result<()> { 40 | if !matches!(context.binary_operation.operator.as_str(), "==" | "!=") { 41 | return Ok(()); 42 | } 43 | 44 | let check_expression = |expression: &Expression| -> bool { 45 | match expression { 46 | Expression::FunctionCall(FunctionCall { 47 | kind: FunctionCallKind::TypeConversion, 48 | arguments, 49 | expression, 50 | .. 51 | }) if arguments.len() == 1 => match expression.as_ref() { 52 | Expression::ElementaryTypeNameExpression(ElementaryTypeNameExpression { 53 | type_name: 54 | TypeName::ElementaryTypeName(ElementaryTypeName { 55 | name: type_name, .. 56 | }), 57 | .. 58 | }) if type_name == "address" => match &arguments[0] { 59 | Expression::Literal(Literal { 60 | value: Some(value), .. 61 | }) if value == "0" => true, 62 | 63 | _ => false, 64 | }, 65 | 66 | _ => false, 67 | }, 68 | 69 | _ => false, 70 | } 71 | }; 72 | 73 | if check_expression(context.binary_operation.left_expression.as_ref()) 74 | || check_expression(context.binary_operation.right_expression.as_ref()) 75 | { 76 | self.add_report_entry( 77 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 78 | context.contract_definition, 79 | context.definition_node, 80 | context.current_source_unit.source_line(context.binary_operation.src.as_str())?, 81 | context.binary_operation, 82 | ); 83 | } 84 | 85 | Ok(()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /solast/src/analysis/array_assignment.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub struct ArrayAssignmentVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl ArrayAssignmentVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | index_access: &IndexAccess, 21 | operator: &str, 22 | expression: &Expression, 23 | ) { 24 | self.report.borrow_mut().add_entry( 25 | source_unit_path, 26 | Some(source_line), 27 | format!( 28 | "{} contains an inefficient array assignment which can be optimized to `{} {}= {};`", 29 | contract_definition.definition_node_location(definition_node), 30 | index_access, 31 | operator, 32 | expression, 33 | ), 34 | ); 35 | } 36 | } 37 | 38 | impl AstVisitor for ArrayAssignmentVisitor { 39 | fn visit_assignment<'a, 'b>(&mut self, context: &mut AssignmentContext<'a, 'b>) -> std::io::Result<()> { 40 | if context.assignment.operator != "=" { 41 | return Ok(()); 42 | } 43 | 44 | let index_access = match context.assignment.left_hand_side.as_ref() { 45 | Expression::IndexAccess(index_access) => index_access, 46 | _ => return Ok(()), 47 | }; 48 | 49 | let binary_operation = match context.assignment.right_hand_side.as_ref() { 50 | Expression::BinaryOperation(binary_operation) => binary_operation, 51 | _ => return Ok(()), 52 | }; 53 | 54 | if !matches!(binary_operation.operator.as_str(), "+" | "-" | "*" | "/" | "%" | "<<" | ">>" | "&" | "|" | "^") { 55 | return Ok(()); 56 | } 57 | 58 | let index_access2 = match binary_operation.left_expression.as_ref() { 59 | Expression::IndexAccess(index_access2) => index_access2, 60 | _ => return Ok(()), 61 | }; 62 | 63 | if index_access.base_expression == index_access2.base_expression { 64 | self.add_report_entry( 65 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 66 | context.contract_definition, 67 | context.definition_node, 68 | context.current_source_unit.source_line(context.assignment.src.as_str())?, 69 | index_access, 70 | binary_operation.operator.as_str(), 71 | binary_operation.right_expression.as_ref(), 72 | ); 73 | } 74 | 75 | Ok(()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /solast/src/analysis/assert_usage.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::HashSet, rc::Rc}; 5 | 6 | pub struct AssertUsageVisitor { 7 | report: Rc>, 8 | reported_definitions: HashSet, 9 | } 10 | 11 | impl AssertUsageVisitor { 12 | pub fn new(report: Rc>) -> Self { 13 | Self { 14 | report, 15 | reported_definitions: HashSet::new(), 16 | } 17 | } 18 | 19 | fn add_report_entry( 20 | &mut self, 21 | source_unit_path: String, 22 | contract_definition: &ContractDefinition, 23 | definition_node: &ContractDefinitionNode, 24 | source_line: usize, 25 | expression: &dyn std::fmt::Display, 26 | ) { 27 | self.report.borrow_mut().add_entry( 28 | source_unit_path, 29 | Some(source_line), 30 | format!( 31 | "{} contains assert usage: `{}`", 32 | contract_definition.definition_node_location(definition_node), 33 | expression, 34 | ), 35 | ); 36 | } 37 | } 38 | 39 | impl AstVisitor for AssertUsageVisitor { 40 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> std::io::Result<()> { 41 | // 42 | // Get the identifier associated with the function or modifier containing the function call 43 | // 44 | 45 | let definition_id = match context.definition_node { 46 | ContractDefinitionNode::FunctionDefinition(FunctionDefinition { id, .. }) | 47 | ContractDefinitionNode::ModifierDefinition(ModifierDefinition { id, .. }) => *id, 48 | 49 | _ => return Ok(()) 50 | }; 51 | 52 | // 53 | // Check if the expression of function call is the "assert" identifier 54 | // 55 | 56 | let is_assert = match context.function_call.expression.as_ref() { 57 | Expression::Identifier(Identifier { name, .. }) => name == "assert", 58 | _ => false 59 | }; 60 | 61 | if !is_assert { 62 | return Ok(()) 63 | } 64 | 65 | // 66 | // Don't display multiple messages for the same function or modifier 67 | // 68 | 69 | if self.reported_definitions.contains(&definition_id) { 70 | return Ok(()) 71 | } 72 | 73 | self.reported_definitions.insert(definition_id); 74 | 75 | // 76 | // Print a message about the assert usage 77 | // 78 | 79 | self.add_report_entry( 80 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 81 | context.contract_definition, 82 | context.definition_node, 83 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 84 | context.function_call, 85 | ); 86 | 87 | Ok(()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /solast/src/analysis/assignment_comparisons.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct AssignmentComparisonsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl AssignmentComparisonsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | message: String, 21 | expression: &dyn std::fmt::Display 22 | ) { 23 | self.report.borrow_mut().add_entry( 24 | source_unit_path, 25 | Some(source_line), 26 | format!( 27 | "{} contains {} that performs an assignment: `{}`", 28 | contract_definition.definition_node_location(definition_node), 29 | message, 30 | expression 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | impl AstVisitor for AssignmentComparisonsVisitor { 37 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 38 | let called_function_name = match context.function_call.expression.as_ref() { 39 | Expression::Identifier(Identifier { name, .. }) if name == "require" || name == "assert" => name, 40 | _ => return Ok(()) 41 | }; 42 | 43 | if context.function_call.arguments.first().unwrap().contains_operation("=") { 44 | self.add_report_entry( 45 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 46 | context.contract_definition, 47 | context.definition_node, 48 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 49 | format!("a call to `{}`", called_function_name), 50 | context.function_call 51 | ); 52 | } 53 | 54 | Ok(()) 55 | } 56 | 57 | fn visit_if_statement<'a, 'b>(&mut self, context: &mut IfStatementContext<'a, 'b>) -> io::Result<()> { 58 | if context.if_statement.condition.contains_operation("=") { 59 | self.add_report_entry( 60 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 61 | context.contract_definition, 62 | context.definition_node, 63 | context.current_source_unit.source_line(context.if_statement.src.as_str())?, 64 | "an if statement".to_string(), 65 | &context.if_statement.condition 66 | ); 67 | } 68 | 69 | Ok(()) 70 | } 71 | 72 | fn visit_for_statement<'a, 'b>(&mut self, context: &mut ForStatementContext<'a, 'b>) -> io::Result<()> { 73 | if let Some(condition) = context.for_statement.condition.as_ref() { 74 | if condition.contains_operation("=") { 75 | self.add_report_entry( 76 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 77 | context.contract_definition, 78 | context.definition_node, 79 | context.current_source_unit.source_line(context.for_statement.src.as_str())?, 80 | "a for statement".to_string(), 81 | condition 82 | ); 83 | } 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | fn visit_while_statement<'a, 'b>(&mut self, context: &mut WhileStatementContext<'a, 'b>) -> io::Result<()> { 90 | if context.while_statement.condition.contains_operation("=") { 91 | self.add_report_entry( 92 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 93 | context.contract_definition, 94 | context.definition_node, 95 | context.current_source_unit.source_line(context.while_statement.src.as_str())?, 96 | "a while statement".to_string(), 97 | &context.while_statement.condition 98 | ); 99 | } 100 | 101 | Ok(()) 102 | } 103 | 104 | fn visit_conditional<'a, 'b>(&mut self, context: &mut ConditionalContext<'a, 'b>) -> io::Result<()> { 105 | if context.conditional.condition.contains_operation("=") { 106 | self.add_report_entry( 107 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 108 | context.contract_definition, 109 | context.definition_node, 110 | context.current_source_unit.source_line(context.conditional.src.as_str())?, 111 | "a conditional expression".to_string(), 112 | context.conditional 113 | ); 114 | } 115 | 116 | Ok(()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /solast/src/analysis/comparison_utilization.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct ComparisonUtilizationVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl ComparisonUtilizationVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for ComparisonUtilizationVisitor { 16 | fn visit_if_statement<'a, 'b>(&mut self, _context: &mut IfStatementContext<'a, 'b>) -> io::Result<()> { 17 | // 18 | // TODO: 19 | // 20 | // Scenario: 21 | // if (x > 0) { 22 | // doSomething(y); 23 | // } else { 24 | // doSomething(z); 25 | // } 26 | // 27 | // Description: 28 | // Verify `x` is utilized within the `true` or `false` blocks. 29 | // Unless `y` or `z` is bound to `x`, then `x` goes unutilized, which can be unintentional. 30 | // 31 | 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /solast/src/analysis/divide_before_multiply.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct DivideBeforeMultiplyVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl DivideBeforeMultiplyVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | // 16 | // TODO: 17 | // 1. track variable assignments, transfering all operations that occurred 18 | // 2. retrieve operations from function calls 19 | // 20 | 21 | impl AstVisitor for DivideBeforeMultiplyVisitor { 22 | fn visit_binary_operation<'a, 'b>(&mut self, context: &mut BinaryOperationContext<'a, 'b>) -> io::Result<()> { 23 | if context.binary_operation.operator != "*" { 24 | return Ok(()) 25 | } 26 | 27 | if let Expression::BinaryOperation(left_operation) = context.binary_operation.left_expression.as_ref() { 28 | if left_operation.contains_operation("/") { 29 | self.report.borrow_mut().add_entry( 30 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 31 | Some(context.current_source_unit.source_line(context.binary_operation.src.as_str())?), 32 | format!( 33 | "{} performs a multiplication on the result of a division", 34 | context.contract_definition.definition_node_location(context.definition_node), 35 | ), 36 | ); 37 | } 38 | } 39 | 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /solast/src/analysis/explicit_variable_return.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::HashSet, io, rc::Rc}; 5 | 6 | pub struct ExplicitVariableReturnVisitor{ 7 | report: Rc>, 8 | local_variable_ids: HashSet, 9 | } 10 | 11 | impl ExplicitVariableReturnVisitor { 12 | pub fn new(report: Rc>) -> Self { 13 | Self { 14 | report, 15 | local_variable_ids: HashSet::new(), 16 | } 17 | } 18 | } 19 | 20 | impl AstVisitor for ExplicitVariableReturnVisitor { 21 | fn visit_variable_declaration_statement<'a, 'b>(&mut self, context: &mut VariableDeclarationStatementContext<'a, 'b>) -> io::Result<()> { 22 | for declaration in context.variable_declaration_statement.declarations.iter().flatten() { 23 | if !self.local_variable_ids.contains(&declaration.id) { 24 | self.local_variable_ids.insert(declaration.id); 25 | } 26 | } 27 | 28 | Ok(()) 29 | } 30 | 31 | fn visit_return<'a, 'b>(&mut self, context: &mut ReturnContext<'a, 'b>) -> io::Result<()> { 32 | let description = match context.return_statement.expression.as_ref() { 33 | Some(Expression::Identifier(identifier)) => { 34 | if self.local_variable_ids.contains(&identifier.referenced_declaration) { 35 | Some("a local variable") 36 | } else { 37 | None 38 | } 39 | } 40 | 41 | Some(Expression::TupleExpression(tuple_expression)) => { 42 | let mut all_local_variables = true; 43 | 44 | for component in tuple_expression.components.iter().flatten() { 45 | if let Expression::Identifier(identifier) = component { 46 | if !self.local_variable_ids.contains(&identifier.referenced_declaration) { 47 | all_local_variables = false; 48 | break; 49 | } 50 | } else { 51 | all_local_variables = false; 52 | break; 53 | } 54 | } 55 | 56 | if all_local_variables { 57 | Some("local variables") 58 | } else { 59 | None 60 | } 61 | } 62 | 63 | _ => None, 64 | }; 65 | 66 | if let Some(description) = description { 67 | self.report.borrow_mut().add_entry( 68 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 69 | Some(context.current_source_unit.source_line(context.return_statement.src.as_str())?), 70 | format!( 71 | "{} returns {} explicitly: `{}`", 72 | context.contract_definition.definition_node_location(context.definition_node), 73 | description, 74 | context.return_statement 75 | ), 76 | ); 77 | } 78 | 79 | Ok(()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /solast/src/analysis/external_calls_in_loop.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, io, rc::Rc}; 5 | 6 | pub struct ExternalCallsInLoopVisitor { 7 | report: Rc>, 8 | loop_ids: Vec, 9 | function_calls: Vec, 10 | } 11 | 12 | impl ExternalCallsInLoopVisitor { 13 | pub fn new(report: Rc>) -> Self { 14 | Self { 15 | report, 16 | loop_ids: vec![], 17 | function_calls: vec![], 18 | } 19 | } 20 | 21 | fn add_report_entry( 22 | &mut self, 23 | source_unit_path: String, 24 | contract_definition: &ContractDefinition, 25 | definition_node: &ContractDefinitionNode, 26 | source_line: usize, 27 | expression: &dyn std::fmt::Display 28 | ) { 29 | self.report.borrow_mut().add_entry( 30 | source_unit_path, 31 | Some(source_line), 32 | format!( 33 | "{} makes an external call inside a loop: `{}`", 34 | contract_definition.definition_node_location(definition_node), 35 | expression 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | impl AstVisitor for ExternalCallsInLoopVisitor { 42 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 43 | self.function_calls.push(context.function_call.clone()); 44 | 45 | Ok(()) 46 | } 47 | 48 | fn leave_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 49 | match self.function_calls.pop() { 50 | Some(function_call) => { 51 | if function_call.id != context.function_call.id { 52 | return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid function call id stack")); 53 | } 54 | }, 55 | 56 | None => return Err(io::Error::new(io::ErrorKind::InvalidData, "Not currently in a function call")) 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | fn visit_for_statement<'a, 'b>(&mut self, context: &mut ForStatementContext<'a, 'b>) -> io::Result<()> { 63 | self.loop_ids.push(context.for_statement.id); 64 | 65 | Ok(()) 66 | } 67 | 68 | fn leave_for_statement<'a, 'b>(&mut self, context: &mut ForStatementContext<'a, 'b>) -> io::Result<()> { 69 | match self.loop_ids.pop() { 70 | Some(loop_id) => { 71 | if loop_id != context.for_statement.id { 72 | return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid loop id stack")); 73 | } 74 | } 75 | 76 | None => return Err(io::Error::new(io::ErrorKind::InvalidData, "Not currently in a loop")) 77 | } 78 | 79 | Ok(()) 80 | } 81 | 82 | fn visit_while_statement<'a, 'b>(&mut self, context: &mut WhileStatementContext<'a, 'b>) -> io::Result<()> { 83 | self.loop_ids.push(context.while_statement.id); 84 | 85 | Ok(()) 86 | } 87 | 88 | fn leave_while_statement<'a, 'b>(&mut self, context: &mut WhileStatementContext<'a, 'b>) -> io::Result<()> { 89 | match self.loop_ids.pop() { 90 | Some(loop_id) => if loop_id != context.while_statement.id { 91 | return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid loop id stack")); 92 | } 93 | 94 | None => return Err(io::Error::new(io::ErrorKind::InvalidData, "Not currently in a loop")) 95 | } 96 | 97 | Ok(()) 98 | } 99 | 100 | fn visit_identifier<'a, 'b>(&mut self, context: &mut IdentifierContext<'a, 'b>) -> io::Result<()> { 101 | match context.definition_node { 102 | ContractDefinitionNode::FunctionDefinition(_) | 103 | ContractDefinitionNode::ModifierDefinition(_) if !self.loop_ids.is_empty() => (), 104 | _ => return Ok(()) 105 | } 106 | 107 | for source_unit in context.source_units.iter() { 108 | let called_function_definition = match source_unit.function_definition(context.identifier.referenced_declaration) { 109 | Some(function_definition) => function_definition, 110 | None => continue, 111 | }; 112 | 113 | if let Visibility::External = called_function_definition.visibility { 114 | self.add_report_entry( 115 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 116 | context.contract_definition, 117 | context.definition_node, 118 | context.current_source_unit.source_line(context.identifier.src.as_str())?, 119 | &self.function_calls.last().unwrap().clone(), 120 | ); 121 | break; 122 | } 123 | } 124 | 125 | Ok(()) 126 | } 127 | 128 | fn visit_member_access<'a, 'b>(&mut self, context: &mut MemberAccessContext<'a, 'b>) -> io::Result<()> { 129 | match context.definition_node { 130 | ContractDefinitionNode::FunctionDefinition(_) | 131 | ContractDefinitionNode::ModifierDefinition(_) if !self.loop_ids.is_empty() => (), 132 | _ => return Ok(()) 133 | } 134 | 135 | if let Some(referenced_declaration) = context.member_access.referenced_declaration { 136 | for source_unit in context.source_units.iter() { 137 | if let Some(function_definition) = 138 | source_unit.function_definition(referenced_declaration) 139 | { 140 | if let Visibility::External = function_definition.visibility { 141 | self.add_report_entry( 142 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 143 | context.contract_definition, 144 | context.definition_node, 145 | context.current_source_unit.source_line(context.member_access.src.as_str())?, 146 | &self.function_calls.last().unwrap().clone(), 147 | ); 148 | break; 149 | } 150 | } 151 | } 152 | } 153 | 154 | Ok(()) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /solast/src/analysis/floating_solidity_version.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct FloatingSolidityVersionVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl FloatingSolidityVersionVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for FloatingSolidityVersionVisitor { 16 | fn visit_pragma_directive<'a>( 17 | &mut self, 18 | context: &mut PragmaDirectiveContext<'a>, 19 | ) -> io::Result<()> { 20 | if let Some(literal) = context.pragma_directive.literals.first() { 21 | if literal == "solidity" { 22 | let mut pragma_string = String::new(); 23 | let mut floating = false; 24 | 25 | for literal in context.pragma_directive.literals.iter().skip(1) { 26 | if let "^" | ">" | ">=" | "<" | "<=" = literal.as_str() { 27 | if !pragma_string.is_empty() { 28 | pragma_string.push(' '); 29 | } 30 | 31 | floating = true; 32 | } 33 | 34 | pragma_string.push_str(literal); 35 | } 36 | 37 | if floating { 38 | self.report.borrow_mut().add_entry( 39 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 40 | Some(context.current_source_unit.source_line(context.pragma_directive.src.as_str())?), 41 | format!("Floating solidity version: {pragma_string}; Consider locking before deployment"), 42 | ); 43 | } 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /solast/src/analysis/ineffectual_statements.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct IneffectualStatementsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl IneffectualStatementsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | description: &str, 21 | expression: &dyn std::fmt::Display 22 | ) { 23 | self.report.borrow_mut().add_entry( 24 | source_unit_path, 25 | Some(source_line), 26 | format!( 27 | "{} contains an ineffectual {} statement: `{}`", 28 | contract_definition.definition_node_location(definition_node), 29 | description, 30 | expression 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | impl AstVisitor for IneffectualStatementsVisitor { 37 | fn visit_statement<'a, 'b>(&mut self, context: &mut StatementContext<'a, 'b>) -> io::Result<()> { 38 | let expression = match context.statement { 39 | Statement::ExpressionStatement(ExpressionStatement { expression }) => expression, 40 | _ => return Ok(()) 41 | }; 42 | 43 | let (source_line, description, expression): (usize, &str, &dyn std::fmt::Display) = match expression { 44 | Expression::Literal(literal) => ( 45 | context.current_source_unit.source_line(literal.src.as_str())?, 46 | "literal", 47 | literal 48 | ), 49 | Expression::Identifier(identifier) => ( 50 | context.current_source_unit.source_line(identifier.src.as_str())?, 51 | "identifier", 52 | identifier 53 | ), 54 | Expression::IndexAccess(index_access) => ( 55 | context.current_source_unit.source_line(index_access.src.as_str())?, 56 | "index access", 57 | index_access 58 | ), 59 | Expression::IndexRangeAccess(index_range_access) => ( 60 | context.current_source_unit.source_line(index_range_access.src.as_str())?, 61 | "index range access", 62 | index_range_access 63 | ), 64 | Expression::MemberAccess(member_access) => ( 65 | context.current_source_unit.source_line(member_access.src.as_str())?, 66 | "member access", 67 | member_access 68 | ), 69 | Expression::BinaryOperation(binary_operation) => ( 70 | context.current_source_unit.source_line(binary_operation.src.as_str())?, 71 | "binary operation", 72 | binary_operation 73 | ), 74 | Expression::Conditional(conditional) => ( 75 | context.current_source_unit.source_line(conditional.src.as_str())?, 76 | "conditional", 77 | conditional 78 | ), 79 | Expression::TupleExpression(tuple_expression) => ( 80 | context.current_source_unit.source_line(tuple_expression.src.as_str())?, 81 | "tuple expression", 82 | tuple_expression 83 | ), 84 | Expression::FunctionCallOptions(function_call_options) => ( 85 | context.current_source_unit.source_line(function_call_options.src.as_str())?, 86 | "function call options", 87 | function_call_options 88 | ), 89 | _ => return Ok(()), 90 | }; 91 | 92 | self.add_report_entry( 93 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 94 | context.contract_definition, 95 | context.definition_node, 96 | source_line, 97 | description, 98 | expression, 99 | ); 100 | 101 | Ok(()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /solast/src/analysis/inline_assembly.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | use yul::ast::*; 5 | 6 | pub struct InlineAssemblyVisitor { 7 | report: Rc>, 8 | } 9 | 10 | impl InlineAssemblyVisitor { 11 | pub fn new(report: Rc>) -> Self { 12 | Self { report } 13 | } 14 | 15 | fn add_report_entry( 16 | &mut self, 17 | source_unit_path: String, 18 | contract_definition: &ContractDefinition, 19 | definition_node: &ContractDefinitionNode, 20 | source_line: usize, 21 | description: &str 22 | ) { 23 | self.report.borrow_mut().add_entry( 24 | source_unit_path, 25 | Some(source_line), 26 | format!( 27 | "{} contains {}", 28 | contract_definition.definition_node_location(definition_node), 29 | description 30 | ), 31 | ); 32 | } 33 | } 34 | 35 | impl AstVisitor for InlineAssemblyVisitor { 36 | fn visit_inline_assembly<'a, 'b>(&mut self, context: &mut InlineAssemblyContext<'a, 'b>) -> io::Result<()> { 37 | self.add_report_entry( 38 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 39 | context.contract_definition, 40 | context.definition_node, 41 | context.current_source_unit.source_line(context.inline_assembly.src.as_str())?, 42 | "inline assembly usage" 43 | ); 44 | 45 | Ok(()) 46 | } 47 | 48 | fn visit_yul_function_call<'a, 'b, 'c>(&mut self, context: &mut YulFunctionCallContext<'a, 'b, 'c>) -> io::Result<()> { 49 | match context.yul_function_call.function_name.name.as_str() { 50 | "mload" => { 51 | let value = match context.yul_function_call.arguments.first() { 52 | Some( 53 | YulExpression::YulLiteral(YulLiteral { 54 | value: Some(value), 55 | .. 56 | }) 57 | | YulExpression::YulLiteral(YulLiteral { 58 | hex_value: Some(value), 59 | .. 60 | }) 61 | ) => value, 62 | 63 | _ => return Ok(()) 64 | }; 65 | 66 | if let Ok(0x40) = if value.starts_with("0x") { 67 | i64::from_str_radix(value.trim_start_matches("0x"), 16) 68 | } else { 69 | value.parse() 70 | } { 71 | self.add_report_entry( 72 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 73 | context.contract_definition, 74 | context.definition_node, 75 | context.current_source_unit.source_line(context.inline_assembly.src.as_str())?, 76 | "inline assembly which loads the free memory pointer" 77 | ); 78 | } 79 | } 80 | 81 | "calldatacopy" => { 82 | let arguments = match context.yul_function_call.arguments.get(2) { 83 | Some(YulExpression::YulFunctionCall(YulFunctionCall { 84 | function_name: YulIdentifier { name }, 85 | arguments, 86 | })) if name == "sub" => arguments, 87 | 88 | _ => return Ok(()) 89 | }; 90 | 91 | match arguments.get(0) { 92 | Some(YulExpression::YulFunctionCall(YulFunctionCall { 93 | function_name: YulIdentifier { name }, 94 | .. 95 | })) if name == "calldatasize" => {} 96 | 97 | _ => return Ok(()) 98 | } 99 | 100 | let value = match arguments.get(1) { 101 | Some( 102 | YulExpression::YulLiteral(YulLiteral { 103 | value: Some(value), 104 | .. 105 | }) 106 | | YulExpression::YulLiteral(YulLiteral { 107 | hex_value: Some(value), 108 | .. 109 | }) 110 | ) => value, 111 | 112 | _ => return Ok(()) 113 | }; 114 | 115 | if let Ok(0x4) = if value.starts_with("0x") { 116 | i64::from_str_radix(value.trim_start_matches("0x"), 16) 117 | } else { 118 | value.parse() 119 | } { 120 | self.add_report_entry( 121 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 122 | context.contract_definition, 123 | context.definition_node, 124 | context.current_source_unit.source_line(context.inline_assembly.src.as_str())?, 125 | "inline assembly which copies arbitrary function arguments" 126 | ); 127 | } 128 | } 129 | 130 | _ => {} 131 | } 132 | 133 | Ok(()) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /solast/src/analysis/invalid_using_for_directives.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct InvalidUsingForDirectivesVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl InvalidUsingForDirectivesVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | using_for_directive: &UsingForDirective 21 | ) { 22 | self.report.borrow_mut().add_entry( 23 | source_unit_path, 24 | Some(source_line), 25 | format!( 26 | "{} contains an invalid using-for directive: `{}`", 27 | contract_definition.definition_node_location(definition_node), 28 | using_for_directive, 29 | ), 30 | ); 31 | } 32 | } 33 | 34 | impl AstVisitor for InvalidUsingForDirectivesVisitor { 35 | fn visit_using_for_directive<'a>(&mut self, context: &mut UsingForDirectiveContext<'a>) -> io::Result<()> { 36 | // 37 | // Get the identifier of the contract definition associated with the used library 38 | // 39 | 40 | let using_contract_id = match context.using_for_directive.library_name.referenced_declaration.as_ref() { 41 | Some(&id) => id, 42 | None => return Ok(()) 43 | }; 44 | 45 | // 46 | // Attempt to retrieve the contract definition associated with the used library 47 | // 48 | 49 | let using_contract_definition = { 50 | let mut using_contract_definition = None; 51 | 52 | for source_unit in context.source_units.iter() { 53 | if let Some(contract_definition) = source_unit.contract_definition(using_contract_id) { 54 | using_contract_definition = Some(contract_definition); 55 | break; 56 | } 57 | } 58 | 59 | if using_contract_definition.is_none() { 60 | return Ok(()) 61 | } 62 | 63 | using_contract_definition.unwrap() 64 | }; 65 | 66 | // 67 | // Get the type name of the requested type to use the library for 68 | // 69 | 70 | let for_type_name = match context.using_for_directive.type_name.as_ref() { 71 | Some(type_name) => type_name, 72 | None => return Ok(()) 73 | }; 74 | 75 | // 76 | // Get the contract definition of the requested type to use the library for (if any) 77 | // 78 | 79 | let mut for_contract_definition = None; 80 | 81 | if let &TypeName::UserDefinedTypeName(UserDefinedTypeName { referenced_declaration, .. }) = for_type_name { 82 | for source_unit in context.source_units.iter() { 83 | if let Some(contract_definition) = source_unit.contract_definition(referenced_declaration) { 84 | for_contract_definition = Some(contract_definition); 85 | break; 86 | } 87 | } 88 | } 89 | 90 | // 91 | // Determine if the library contains any functions usable with the requested type 92 | // 93 | 94 | let mut usable_function_found = false; 95 | 96 | for function_definition in using_contract_definition.function_definitions() { 97 | // 98 | // Check to see if the parameter type matches the requested type 99 | // 100 | 101 | let parameter_list = if function_definition.parameters.parameters.is_empty() { 102 | &function_definition.return_parameters 103 | } else { 104 | &function_definition.parameters 105 | }; 106 | 107 | let parameter_type_name = match parameter_list.parameters.first().map(|p| p.type_name.as_ref()) { 108 | Some(Some(type_name)) => type_name, 109 | _ => continue 110 | }; 111 | 112 | if parameter_type_name == for_type_name { 113 | usable_function_found = true; 114 | break; 115 | } 116 | 117 | // 118 | // Check to see if the requested type inherits from the parameter type 119 | // 120 | 121 | let parameter_type_id = match parameter_type_name { 122 | TypeName::UserDefinedTypeName(UserDefinedTypeName { referenced_declaration, .. }) => referenced_declaration, 123 | _ => continue 124 | }; 125 | 126 | if let Some(Some(linearized_base_contracts)) = for_contract_definition.map(|x| x.linearized_base_contracts.as_ref()) { 127 | if linearized_base_contracts.contains(parameter_type_id) { 128 | usable_function_found = true; 129 | break; 130 | } 131 | } 132 | } 133 | 134 | // 135 | // If the library does not contain any usable functions for the requested type, print a message 136 | // 137 | 138 | if !usable_function_found { 139 | self.add_report_entry( 140 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 141 | context.contract_definition, 142 | context.definition_node, 143 | context.current_source_unit.source_line(context.using_for_directive.src.as_str())?, 144 | context.using_for_directive 145 | ); 146 | } 147 | 148 | Ok(()) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /solast/src/analysis/large_literals.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{io, cell::RefCell, rc::Rc}; 4 | 5 | pub struct LargeLiteralsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl LargeLiteralsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for LargeLiteralsVisitor { 16 | fn visit_literal<'a, 'b>(&mut self, context: &mut LiteralContext<'a, 'b>) -> io::Result<()> { 17 | if let Some(value) = context.literal.value.as_ref() { 18 | let n = value.len(); 19 | 20 | if value.chars().all(char::is_numeric) && (n > 6) && ((n % 3) != 0) { 21 | self.report.borrow_mut().add_entry( 22 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 23 | Some(context.current_source_unit.source_line(context.literal.src.as_str())?), 24 | format!( 25 | "{} contains a large literal, which may be difficult to read: `{}`", 26 | context.contract_definition.definition_node_location(context.definition_node), 27 | context.literal 28 | ), 29 | ); 30 | } 31 | } 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solast/src/analysis/manipulatable_balance_usage.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | // 6 | // TODO: 7 | // * determine if balance can actually be manipulated 8 | // * determine if manipulating balance has consequences 9 | // 10 | 11 | pub struct ManipulatableBalanceUsageVisitor { 12 | report: Rc>, 13 | } 14 | 15 | impl ManipulatableBalanceUsageVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { report } 18 | } 19 | 20 | fn add_report_entry( 21 | &mut self, 22 | source_unit_path: String, 23 | contract_definition: &ContractDefinition, 24 | definition_node: &ContractDefinitionNode, 25 | source_line: usize, 26 | expression: &dyn std::fmt::Display 27 | ) { 28 | self.report.borrow_mut().add_entry( 29 | source_unit_path, 30 | Some(source_line), 31 | format!( 32 | "{} contains manipulatable balance usage: `{}`", 33 | contract_definition.definition_node_location(definition_node), 34 | expression 35 | ), 36 | ); 37 | } 38 | } 39 | 40 | impl AstVisitor for ManipulatableBalanceUsageVisitor { 41 | fn visit_member_access<'a, 'b>(&mut self, context: &mut MemberAccessContext<'a, 'b>) -> io::Result<()> { 42 | if context.member_access.member_name != "balance" { 43 | return Ok(()) 44 | } 45 | 46 | let (expression, arguments) = match context.member_access.expression.as_ref() { 47 | Expression::FunctionCall(FunctionCall { 48 | expression, 49 | arguments, 50 | .. 51 | }) => (expression, arguments), 52 | 53 | _ => return Ok(()) 54 | }; 55 | 56 | match expression.as_ref() { 57 | Expression::ElementaryTypeNameExpression(ElementaryTypeNameExpression { 58 | type_name: TypeName::ElementaryTypeName(ElementaryTypeName { 59 | name: type_name, 60 | .. 61 | }), 62 | .. 63 | }) if type_name == "address" => {} 64 | 65 | _ => return Ok(()) 66 | } 67 | 68 | if arguments.len() != 1 { 69 | return Ok(()) 70 | } 71 | 72 | match arguments.first().unwrap() { 73 | Expression::Identifier(Identifier { 74 | name, 75 | .. 76 | }) if name == "this" => {} 77 | 78 | _ => return Ok(()) 79 | } 80 | 81 | self.add_report_entry( 82 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 83 | context.contract_definition, 84 | context.definition_node, 85 | context.current_source_unit.source_line(context.member_access.src.as_str())?, 86 | context.member_access 87 | ); 88 | 89 | Ok(()) 90 | } 91 | 92 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 93 | match context.function_call.expression.as_ref() { 94 | Expression::MemberAccess(MemberAccess { 95 | member_name, 96 | .. 97 | }) if member_name == "balanceOf" => {} 98 | 99 | _ => return Ok(()) 100 | } 101 | 102 | if context.function_call.arguments.len() != 1 { 103 | return Ok(()) 104 | } 105 | 106 | let (expression, arguments) = match context.function_call.arguments.first().unwrap() { 107 | Expression::FunctionCall(FunctionCall { 108 | expression, 109 | arguments, 110 | .. 111 | }) => (expression, arguments), 112 | 113 | _ => return Ok(()) 114 | }; 115 | 116 | match expression.as_ref() { 117 | Expression::ElementaryTypeNameExpression(ElementaryTypeNameExpression { 118 | type_name: TypeName::ElementaryTypeName(ElementaryTypeName { 119 | name: type_name, 120 | .. 121 | }), 122 | .. 123 | }) if type_name == "address" => {} 124 | 125 | _ => return Ok(()) 126 | } 127 | 128 | if arguments.len() != 1 { 129 | return Ok(()) 130 | } 131 | 132 | match arguments.first().unwrap() { 133 | Expression::Identifier(Identifier { 134 | name, 135 | .. 136 | }) if name == "this" => {} 137 | 138 | _ => return Ok(()) 139 | } 140 | 141 | self.add_report_entry( 142 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 143 | context.contract_definition, 144 | context.definition_node, 145 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 146 | context.function_call 147 | ); 148 | 149 | Ok(()) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /solast/src/analysis/missing_return.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::{HashMap, HashSet}, io, rc::Rc}; 5 | 6 | struct FunctionInfo { 7 | assigned_return_variables: HashSet, 8 | } 9 | 10 | pub struct MissingReturnVisitor { 11 | report: Rc>, 12 | function_info: HashMap, 13 | } 14 | 15 | impl MissingReturnVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { 18 | report, 19 | function_info: HashMap::new(), 20 | } 21 | } 22 | 23 | fn add_report_entry( 24 | &mut self, 25 | source_unit_path: String, 26 | contract_definition: &ContractDefinition, 27 | definition_node: &ContractDefinitionNode, 28 | source_line: usize 29 | ) { 30 | self.report.borrow_mut().add_entry( 31 | source_unit_path, 32 | Some(source_line), 33 | format!( 34 | "{} is missing an explicit return statement", 35 | contract_definition.definition_node_location(definition_node), 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | impl AstVisitor for MissingReturnVisitor { 42 | fn visit_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 43 | if context.function_definition.return_parameters.parameters.is_empty() { 44 | return Ok(()); 45 | } 46 | 47 | if context.function_definition.body.is_none() { 48 | return Ok(()); 49 | } 50 | 51 | self.function_info.entry(context.function_definition.id).or_insert_with(|| FunctionInfo { 52 | assigned_return_variables: HashSet::new(), 53 | }); 54 | 55 | Ok(()) 56 | } 57 | 58 | fn leave_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 59 | let function_info = match self.function_info.get(&context.function_definition.id) { 60 | Some(function_info) => function_info, 61 | None => return Ok(()) 62 | }; 63 | 64 | if BlockOrStatement::Block(Box::new(context.function_definition.body.as_ref().unwrap().clone())).contains_returns() { 65 | return Ok(()); 66 | } 67 | 68 | let mut assigned = vec![]; 69 | 70 | for variable_declaration in context.function_definition.return_parameters.parameters.iter() { 71 | assigned.push(function_info.assigned_return_variables.contains(&variable_declaration.id)); 72 | } 73 | 74 | if assigned.iter().all(|assigned| !assigned) { 75 | self.add_report_entry( 76 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 77 | context.contract_definition, 78 | context.definition_node, 79 | context.current_source_unit.source_line(context.function_definition.src.as_str())? 80 | ); 81 | } 82 | 83 | Ok(()) 84 | } 85 | 86 | fn visit_assignment<'a, 'b>(&mut self, context: &mut AssignmentContext<'a, 'b>) -> io::Result<()> { 87 | let function_definition = match context.definition_node { 88 | ContractDefinitionNode::FunctionDefinition(function_definition) => function_definition, 89 | _ => return Ok(()) 90 | }; 91 | 92 | let function_info = match self.function_info.get_mut(&function_definition.id) { 93 | Some(function_info) => function_info, 94 | _ => return Ok(()) 95 | }; 96 | 97 | for id in function_definition.get_assigned_return_variables(context.assignment.left_hand_side.as_ref()) { 98 | if !function_info.assigned_return_variables.contains(&id) { 99 | function_info.assigned_return_variables.insert(id); 100 | } 101 | } 102 | 103 | Ok(()) 104 | } 105 | 106 | fn visit_yul_assignment<'a, 'b, 'c>(&mut self, context: &mut YulAssignmentContext<'a, 'b, 'c>) -> io::Result<()> { 107 | let function_definition = match context.definition_node { 108 | ContractDefinitionNode::FunctionDefinition(function_definition) => function_definition, 109 | _ => return Ok(()) 110 | }; 111 | 112 | let function_info = match self.function_info.get_mut(&function_definition.id) { 113 | Some(function_info) => function_info, 114 | _ => return Ok(()) 115 | }; 116 | 117 | for yul_identifier in context.yul_assignment.variable_names.iter() { 118 | if let Some(variable_declaration) = function_definition.return_parameters.parameters.iter().find(|p| p.name == yul_identifier.name) { 119 | if !function_info.assigned_return_variables.contains(&variable_declaration.id) { 120 | function_info.assigned_return_variables.insert(variable_declaration.id); 121 | } 122 | } 123 | } 124 | 125 | Ok(()) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /solast/src/analysis/mod.rs: -------------------------------------------------------------------------------- 1 | mod abi_encoding; 2 | mod abstract_contracts; 3 | mod address_balance; 4 | mod address_zero; 5 | mod array_assignment; 6 | mod assert_usage; 7 | mod assignment_comparisons; 8 | mod check_effects_interactions; 9 | mod comparison_utilization; 10 | mod divide_before_multiply; 11 | mod explicit_variable_return; 12 | mod external_calls_in_loop; 13 | mod floating_solidity_version; 14 | mod ineffectual_statements; 15 | mod inline_assembly; 16 | mod invalid_using_for_directives; 17 | mod large_literals; 18 | mod manipulatable_balance_usage; 19 | mod missing_return; 20 | mod no_spdx_identifier; 21 | mod node_modules_imports; 22 | mod redundant_assignments; 23 | mod redundant_comparisons; 24 | mod redundant_getter_function; 25 | mod redundant_imports; 26 | mod redundant_state_variable_access; 27 | mod require_without_message; 28 | mod safe_erc20_functions; 29 | mod secure_ether_transfer; 30 | mod selfdestruct_usage; 31 | mod state_variable_mutability; 32 | mod state_variable_shadowing; 33 | mod storage_array_loop; 34 | mod tight_variable_packing; 35 | mod unchecked_casting; 36 | mod unchecked_erc20_transfer; 37 | mod unnecessary_pragmas; 38 | mod unpaid_payable_functions; 39 | mod unreferenced_state_variables; 40 | mod unrestricted_setter_functions; 41 | mod unused_return; 42 | 43 | use self::{ 44 | abi_encoding::*, abstract_contracts::*, address_balance::*, address_zero::*, 45 | array_assignment::*, assert_usage::*, assignment_comparisons::*, check_effects_interactions::*, 46 | comparison_utilization::*, divide_before_multiply::*, explicit_variable_return::*, 47 | external_calls_in_loop::*, floating_solidity_version::*, ineffectual_statements::*, 48 | inline_assembly::*, invalid_using_for_directives::*, large_literals::*, 49 | manipulatable_balance_usage::*, missing_return::*, no_spdx_identifier::*, 50 | node_modules_imports::*, redundant_assignments::*, redundant_comparisons::*, 51 | redundant_getter_function::*, redundant_imports::*, redundant_state_variable_access::*, 52 | require_without_message::*, safe_erc20_functions::*, secure_ether_transfer::*, 53 | selfdestruct_usage::*, state_variable_mutability::*, state_variable_shadowing::*, 54 | storage_array_loop::*, tight_variable_packing::*, unchecked_casting::*, 55 | unchecked_erc20_transfer::*, unnecessary_pragmas::*, unpaid_payable_functions::*, 56 | unreferenced_state_variables::*, unrestricted_setter_functions::*, unused_return::*, 57 | }; 58 | 59 | use crate::report::Report; 60 | use solidity::ast::AstVisitor; 61 | use std::{rc::Rc, cell::RefCell}; 62 | 63 | type VisitorConstructor = fn(report: Rc>) -> Box; 64 | type VisitorEntry = (&'static str, VisitorConstructor); 65 | 66 | pub const VISITOR_TYPES: &[VisitorEntry] = &[ 67 | ("no_spdx_identifier", |report: Rc>| Box::new(NoSpdxIdentifierVisitor::new(report))), 68 | ("floating_solidity_version", |report: Rc>| Box::new(FloatingSolidityVersionVisitor::new(report))), 69 | ("node_modules_imports", |report: Rc>| Box::new(NodeModulesImportsVisitor::new(report))), 70 | ("redundant_imports", |report: Rc>| Box::new(RedundantImportsVisitor::new(report))), 71 | ("abstract_contracts", |report: Rc>| Box::new(AbstractContractsVisitor::new(report))), 72 | ("large_literals", |report: Rc>| Box::new(LargeLiteralsVisitor::new(report))), 73 | ("tight_variable_packing", |report: Rc>| Box::new(TightVariablePackingVisitor::new(report))), 74 | ("redundant_getter_function", |report: Rc>| Box::new(RedundantGetterFunctionVisitor::new(report))), 75 | ("require_without_message", |report: Rc>| Box::new(RequireWithoutMessageVisitor::new(report))), 76 | ("state_variable_shadowing", |report: Rc>| Box::new(StateVariableShadowingVisitor::new(report))), 77 | ("explicit_variable_return", |report: Rc>| Box::new(ExplicitVariableReturnVisitor::new(report))), 78 | ("unused_return", |report: Rc>| Box::new(UnusedReturnVisitor::new(report))), 79 | ("storage_array_loop", |report: Rc>| Box::new(StorageArrayLoopVisitor::new(report))), 80 | ("external_calls_in_loop", |report: Rc>| Box::new(ExternalCallsInLoopVisitor::new(report))), 81 | ("check_effects_interactions", |report: Rc>| Box::new(CheckEffectsInteractionsVisitor::new(report))), 82 | ("secure_ether_transfer", |report: Rc>| Box::new(SecureEtherTransferVisitor::new(report))), 83 | ("safe_erc20_functions", |report: Rc>| Box::new(SafeERC20FunctionsVisitor::new(report))), 84 | ("unchecked_erc20_transfer", |report: Rc>| Box::new(UncheckedERC20TransferVisitor::new(report))), 85 | ("unpaid_payable_functions", |report: Rc>| Box::new(UnpaidPayableFunctionsVisitor::new(report))), 86 | ("divide_before_multiply", |report: Rc>| Box::new(DivideBeforeMultiplyVisitor::new(report))), 87 | ("comparison_utilization", |report: Rc>| Box::new(ComparisonUtilizationVisitor::new(report))), 88 | ("assignment_comparisons", |report: Rc>| Box::new(AssignmentComparisonsVisitor::new(report))), 89 | ("state_variable_mutability", |report: Rc>| Box::new(StateVariableMutabilityVisitor::new(report))), 90 | ("unused_state_variables", |report: Rc>| Box::new(UnusedStateVariablesVisitor::new(report))), 91 | ("ineffectual_statements", |report: Rc>| Box::new(IneffectualStatementsVisitor::new(report))), 92 | ("inline_assembly", |report: Rc>| Box::new(InlineAssemblyVisitor::new(report))), 93 | ("unchecked_casting", |report: Rc>| Box::new(UncheckedCastingVisitor::new(report))), 94 | ("unnecessary_pragmas", |report: Rc>| Box::new(UnnecessaryPragmasVisitor::new(report))), 95 | ("missing_return", |report: Rc>| Box::new(MissingReturnVisitor::new(report))), 96 | ("redundant_state_variable_access", |report: Rc>| Box::new(RedundantStateVariableAccessVisitor::new(report))), 97 | ("redundant_comparisons", |report: Rc>| Box::new(RedundantComparisonsVisitor::new(report))), 98 | ("assert_usage", |report: Rc>| Box::new(AssertUsageVisitor::new(report))), 99 | ("selfdestruct_usage", |report: Rc>| Box::new(SelfdestructUsageVisitor::new(report))), 100 | ("unrestricted_setter_functions", |report: Rc>| Box::new(UnrestrictedSetterFunctionsVisitor::new(report))), 101 | ("manipulatable_balance_usage", |report: Rc>| Box::new(ManipulatableBalanceUsageVisitor::new(report))), 102 | ("redundant_assignments", |report: Rc>| Box::new(RedundantAssignmentsVisitor::new(report))), 103 | ("invalid_using_for_directives", |report: Rc>| Box::new(InvalidUsingForDirectivesVisitor::new(report))), 104 | ("abi_encoding", |report: Rc>| Box::new(AbiEncodingVisitor::new(report))), 105 | ("address_balance", |report: Rc>| Box::new(AddressBalanceVisitor::new(report))), 106 | ("address_zero", |report: Rc>| Box::new(AddressZeroVisitor::new(report))), 107 | ("array_assignment", |report: Rc>| Box::new(ArrayAssignmentVisitor::new(report))), 108 | ]; 109 | -------------------------------------------------------------------------------- /solast/src/analysis/no_spdx_identifier.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct NoSpdxIdentifierVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl NoSpdxIdentifierVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for NoSpdxIdentifierVisitor { 16 | fn visit_source_unit<'a>(&mut self, context: &mut SourceUnitContext<'a>) -> io::Result<()> { 17 | if context.current_source_unit.license.is_none() { 18 | self.report.borrow_mut().add_entry( 19 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 20 | None, 21 | "SPDX license identifier not provided in source file; Consider adding one before deployment" 22 | ); 23 | } 24 | 25 | Ok(()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /solast/src/analysis/node_modules_imports.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct NodeModulesImportsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl NodeModulesImportsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for NodeModulesImportsVisitor { 16 | fn visit_import_directive<'a>( 17 | &mut self, 18 | context: &mut ImportDirectiveContext<'a>, 19 | ) -> io::Result<()> { 20 | if context.import_directive.file.contains("../node_modules") { 21 | self.report.borrow_mut().add_entry( 22 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 23 | Some(context.current_source_unit.source_line(context.import_directive.src.as_str())?), 24 | format!("Unnecessary relative node_modules import: `{}`", context.import_directive.file), 25 | ); 26 | } 27 | 28 | Ok(()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /solast/src/analysis/redundant_assignments.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, io, rc::Rc}; 5 | 6 | // 7 | // TODO: 8 | // determine if something is assigned to, then re-assigned to without being referenced 9 | // 10 | 11 | pub struct RedundantAssignmentsVisitor { 12 | report: Rc>, 13 | } 14 | 15 | impl RedundantAssignmentsVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { report } 18 | } 19 | 20 | fn add_report_entry( 21 | &mut self, 22 | source_unit_path: String, 23 | contract_definition: &ContractDefinition, 24 | definition_node: &ContractDefinitionNode, 25 | source_line: usize, 26 | assignment: &Assignment 27 | ) { 28 | self.report.borrow_mut().add_entry( 29 | source_unit_path, 30 | Some(source_line), 31 | format!( 32 | "{} contains a redundant assignment: `{}`", 33 | contract_definition.definition_node_location(definition_node), 34 | assignment, 35 | ), 36 | ); 37 | } 38 | } 39 | 40 | impl AstVisitor for RedundantAssignmentsVisitor { 41 | fn visit_assignment<'a, 'b>(&mut self, context: &mut AssignmentContext<'a, 'b>) -> io::Result<()> { 42 | if let Expression::TupleExpression(tuple_expression) = context.assignment.left_hand_side.as_ref() { 43 | let mut tuple_component_ids: Vec> = vec![]; 44 | 45 | for component in tuple_expression.components.iter() { 46 | let mut component_ids = vec![]; 47 | 48 | if let Some(component) = component.as_ref() { 49 | component_ids.extend(component.referenced_declarations()); 50 | } 51 | 52 | if !component_ids.is_empty() && tuple_component_ids.iter().any(|ids| ids.eq(&component_ids)) { 53 | self.add_report_entry( 54 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 55 | context.contract_definition, 56 | context.definition_node, 57 | context.current_source_unit.source_line(context.assignment.src.as_str())?, 58 | context.assignment 59 | ); 60 | } 61 | 62 | tuple_component_ids.push(component_ids); 63 | } 64 | } 65 | 66 | Ok(()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /solast/src/analysis/redundant_comparisons.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use primitive_types::U512; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, io, rc::Rc, str::FromStr}; 5 | 6 | pub struct RedundantComparisonsVisitor { 7 | report: Rc>, 8 | } 9 | 10 | impl RedundantComparisonsVisitor { 11 | pub fn new(report: Rc>) -> Self { 12 | Self { report } 13 | } 14 | 15 | fn add_report_entry( 16 | &mut self, 17 | source_unit_path: String, 18 | contract_definition: &ContractDefinition, 19 | definition_node: &ContractDefinitionNode, 20 | source_line: usize, 21 | binary_operation: &BinaryOperation 22 | ) { 23 | self.report.borrow_mut().add_entry( 24 | source_unit_path, 25 | Some(source_line), 26 | format!( 27 | "{} contains a redundant comparison: `{}`", 28 | contract_definition.definition_node_location(definition_node), 29 | binary_operation 30 | ), 31 | ); 32 | } 33 | 34 | fn is_right_literal_redundant<'a, 'b>(&mut self, context: &mut BinaryOperationContext<'a, 'b>) -> bool { 35 | let type_name = match context.binary_operation.left_expression.type_descriptions() { 36 | Some(TypeDescriptions { type_string: Some(type_string), .. }) => type_string.as_str(), 37 | _ => return false 38 | }; 39 | 40 | let literal_value = match context.binary_operation.right_expression.as_ref() { 41 | Expression::Literal( 42 | Literal { hex_value: Some(value), .. } 43 | ) => match U512::from_str(value) { 44 | Ok(value) => value, 45 | Err(_) => return false 46 | } 47 | 48 | Expression::Literal( 49 | Literal { value: Some(value), .. } 50 | ) => match U512::from_dec_str(value) { 51 | Ok(value) => value, 52 | Err(_) => return false 53 | } 54 | 55 | _ => return false 56 | }; 57 | 58 | let mut contains_redundant_comparison = false; 59 | 60 | match type_name { 61 | type_name if type_name.starts_with("uint") => { 62 | let type_bits: usize = match type_name.trim_start_matches("uint") { 63 | "" => 256, 64 | string => match string.parse() { 65 | Ok(type_bits) => type_bits, 66 | Err(_) => return false 67 | } 68 | }; 69 | 70 | let type_max = (U512::one() << type_bits) - U512::one(); 71 | 72 | contains_redundant_comparison = match context.binary_operation.operator.as_str() { 73 | ">=" => literal_value.is_zero(), 74 | "<=" => literal_value > type_max, 75 | ">" | "<" => literal_value >= type_max, 76 | _ => false 77 | } 78 | } 79 | 80 | type_name if type_name.starts_with("int") => { 81 | // TODO 82 | } 83 | 84 | _ => {} 85 | } 86 | 87 | contains_redundant_comparison 88 | } 89 | 90 | fn is_left_literal_redundant<'a, 'b>(&mut self, _context: &mut BinaryOperationContext<'a, 'b>) -> bool { 91 | // TODO 92 | false 93 | } 94 | } 95 | 96 | impl AstVisitor for RedundantComparisonsVisitor { 97 | fn visit_binary_operation<'a, 'b>(&mut self, context: &mut BinaryOperationContext<'a, 'b>) -> io::Result<()> { 98 | match context.binary_operation.operator.as_str() { 99 | "==" | "!=" | ">" | ">=" | "<" | "<=" => (), 100 | _ => return Ok(()) 101 | } 102 | 103 | if match (context.binary_operation.left_expression.as_ref(), context.binary_operation.right_expression.as_ref()) { 104 | (Expression::Literal(_), Expression::Literal(_)) => true, 105 | (_, Expression::Literal(_)) => self.is_right_literal_redundant(context), 106 | (Expression::Literal(_), _) => self.is_left_literal_redundant(context), 107 | _ => false 108 | } { 109 | self.add_report_entry( 110 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 111 | context.contract_definition, 112 | context.definition_node, 113 | context.current_source_unit.source_line(context.binary_operation.src.as_str())?, 114 | context.binary_operation 115 | ); 116 | } 117 | 118 | Ok(()) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /solast/src/analysis/redundant_getter_function.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct RedundantGetterFunctionVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl RedundantGetterFunctionVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for RedundantGetterFunctionVisitor { 16 | fn visit_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 17 | if context.function_definition.name.is_empty() || context.function_definition.body.is_none() { 18 | return Ok(()); 19 | } 20 | 21 | if context.function_definition.return_parameters.parameters.len() != 1 { 22 | return Ok(()); 23 | } 24 | 25 | if context.function_definition.visibility != Visibility::Public { 26 | return Ok(()); 27 | } 28 | 29 | let statements = context.function_definition 30 | .body 31 | .as_ref() 32 | .unwrap() 33 | .statements 34 | .as_slice(); 35 | 36 | if statements.len() != 1 { 37 | return Ok(()); 38 | } 39 | 40 | let return_statement = match &statements[0] { 41 | Statement::Return(return_statement) => return_statement, 42 | _ => return Ok(()), 43 | }; 44 | 45 | let variable_declaration = match return_statement.expression.as_ref() { 46 | Some(Expression::Identifier(identifier)) => { 47 | match context.contract_definition.variable_declaration(identifier.referenced_declaration) { 48 | Some(variable_declaration) => variable_declaration, 49 | None => return Ok(()), 50 | } 51 | } 52 | _ => return Ok(()), 53 | }; 54 | 55 | if (variable_declaration.name != context.function_definition.name) 56 | && !(variable_declaration.name.starts_with('_') 57 | && variable_declaration.name[1..] == context.function_definition.name) 58 | { 59 | return Ok(()); 60 | } 61 | 62 | self.report.borrow_mut().add_entry( 63 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 64 | Some(context.current_source_unit.source_line(context.function_definition.src.as_str())?), 65 | format!( 66 | "{} is a redundant getter function for the {} `{}.{}` state variable", 67 | context.contract_definition.definition_node_location(context.definition_node), 68 | variable_declaration.visibility, 69 | context.contract_definition.name, 70 | variable_declaration.name, 71 | ), 72 | ); 73 | 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /solast/src/analysis/redundant_imports.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 5 | 6 | struct SourceUnitInfo { 7 | imported_paths: HashMap, 8 | } 9 | 10 | pub struct RedundantImportsVisitor { 11 | report: Rc>, 12 | source_unit_info: HashMap, 13 | } 14 | 15 | impl RedundantImportsVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { 18 | report, 19 | source_unit_info: HashMap::new(), 20 | } 21 | } 22 | } 23 | 24 | impl AstVisitor for RedundantImportsVisitor { 25 | fn visit_source_unit<'a>(&mut self, context: &mut SourceUnitContext<'a>) -> std::io::Result<()> { 26 | self.source_unit_info.entry(context.current_source_unit.id).or_insert_with(|| SourceUnitInfo { 27 | imported_paths: HashMap::new(), 28 | }); 29 | 30 | Ok(()) 31 | } 32 | 33 | fn visit_import_directive<'a>(&mut self, context: &mut ImportDirectiveContext<'a>) -> std::io::Result<()> { 34 | let source_unit_info = self.source_unit_info.get_mut(&context.current_source_unit.id).unwrap(); 35 | 36 | match source_unit_info.imported_paths.get_mut(&context.import_directive.file) { 37 | Some(reported) => if !*reported { 38 | self.report.borrow_mut().add_entry( 39 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 40 | Some(context.current_source_unit.source_line(context.import_directive.src.as_str())?), 41 | format!("Redundant import specified: `{}`", context.import_directive.file), 42 | ); 43 | *reported = true; 44 | } 45 | 46 | None => { 47 | source_unit_info.imported_paths.insert(context.import_directive.file.clone(), false); 48 | } 49 | } 50 | 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /solast/src/analysis/redundant_state_variable_access.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | // 6 | // TODO: 7 | // * Check if a state variable is accessed multiple times in a block without any changes being made 8 | // * Check if member access is a local variable bound to an array state variable 9 | // 10 | 11 | pub struct RedundantStateVariableAccessVisitor { 12 | report: Rc>, 13 | } 14 | 15 | impl RedundantStateVariableAccessVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { report } 18 | } 19 | 20 | fn add_report_entry( 21 | &mut self, 22 | source_unit_path: String, 23 | contract_definition: &ContractDefinition, 24 | definition_node: &ContractDefinitionNode, 25 | source_line: usize, 26 | message: &str, 27 | expression: &dyn std::fmt::Display 28 | ) { 29 | self.report.borrow_mut().add_entry( 30 | source_unit_path, 31 | Some(source_line), 32 | format!( 33 | "{} contains {} which redundantly accesses storage: `{}`", 34 | contract_definition.definition_node_location(definition_node), 35 | message, 36 | expression 37 | ), 38 | ); 39 | } 40 | } 41 | 42 | impl AstVisitor for RedundantStateVariableAccessVisitor { 43 | fn visit_for_statement<'a, 'b>(&mut self, context: &mut ForStatementContext<'a, 'b>) -> std::io::Result<()> { 44 | // 45 | // Check if the for statement's condition directly references a state variable 46 | // 47 | 48 | let condition = match context.for_statement.condition.as_ref() { 49 | Some(condition) => condition, 50 | None => return Ok(()) 51 | }; 52 | 53 | for id in condition.referenced_declarations() { 54 | if context.contract_definition.hierarchy_contains_state_variable(context.source_units, id) { 55 | self.add_report_entry( 56 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 57 | context.contract_definition, 58 | context.definition_node, 59 | condition.source_line(context.current_source_unit)?, 60 | "a for statement with a condition", 61 | condition 62 | ); 63 | return Ok(()) 64 | } 65 | } 66 | 67 | Ok(()) 68 | } 69 | 70 | fn visit_while_statement<'a, 'b>(&mut self, context: &mut WhileStatementContext<'a, 'b>) -> std::io::Result<()> { 71 | // 72 | // Check if the while statement's condition directly references a state variable 73 | // 74 | 75 | for id in context.while_statement.condition.referenced_declarations() { 76 | if context.contract_definition.hierarchy_contains_state_variable(context.source_units, id) { 77 | self.add_report_entry( 78 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 79 | context.contract_definition, 80 | context.definition_node, 81 | context.while_statement.condition.source_line(context.current_source_unit)?, 82 | "a while statement with a condition", 83 | &context.while_statement.condition 84 | ); 85 | return Ok(()) 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /solast/src/analysis/require_without_message.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct RequireWithoutMessageVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl RequireWithoutMessageVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for RequireWithoutMessageVisitor { 16 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 17 | if let Expression::Identifier(Identifier { name, .. }) = context.function_call.expression.as_ref() { 18 | if name == "require" && context.function_call.arguments.len() < 2 { 19 | self.report.borrow_mut().add_entry( 20 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 21 | Some(context.current_source_unit.source_line(context.function_call.src.as_str())?), 22 | format!( 23 | "{} contains a requirement without a message: `{}`", 24 | context.contract_definition.definition_node_location(context.definition_node), 25 | context.function_call 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | Ok(()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solast/src/analysis/safe_erc20_functions.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct SafeERC20FunctionsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl SafeERC20FunctionsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | unsafe_name: &str, 21 | safe_name: &str 22 | ) { 23 | self.report.borrow_mut().add_entry( 24 | source_unit_path, 25 | Some(source_line), 26 | format!( 27 | "{} uses `ERC20.{}` instead of `SafeERC20.{}`", 28 | contract_definition.definition_node_location(definition_node), 29 | unsafe_name, 30 | safe_name, 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | impl AstVisitor for SafeERC20FunctionsVisitor { 37 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 38 | if context.contract_definition.name == "SafeERC20" { 39 | return Ok(()) 40 | } 41 | 42 | for referenced_declaration in context.function_call.expression.referenced_declarations() { 43 | for source_unit in context.source_units.iter() { 44 | let (called_contract_definition, called_function_definition) = match source_unit.function_and_contract_definition(referenced_declaration) { 45 | Some((contract_definition, function_definition)) => (contract_definition, function_definition), 46 | None => continue 47 | }; 48 | 49 | match called_contract_definition.name.to_ascii_lowercase().as_str() { 50 | "erc20" | "ierc20" | "erc20interface" => {} 51 | _ => return Ok(()) 52 | } 53 | 54 | let (unsafe_name, safe_name) = match called_function_definition.name.as_str() { 55 | "transfer" => ("transfer", "safeTransfer"), 56 | "transferFrom" => ("transferFrom", "safeTransferFrom"), 57 | "approve" => ("approve", "safeApprove"), 58 | _ => continue, 59 | }; 60 | 61 | self.add_report_entry( 62 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 63 | context.contract_definition, 64 | context.definition_node, 65 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 66 | unsafe_name, 67 | safe_name 68 | ); 69 | 70 | break; 71 | } 72 | } 73 | 74 | Ok(()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /solast/src/analysis/secure_ether_transfer.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct SecureEtherTransferVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl SecureEtherTransferVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | expression: &dyn std::fmt::Display 21 | ) { 22 | self.report.borrow_mut().add_entry( 23 | source_unit_path, 24 | Some(source_line), 25 | format!( 26 | "{} ignores the Secure-Ether-Transfer pattern: `{}`", 27 | contract_definition.definition_node_location(definition_node), 28 | expression 29 | ), 30 | ); 31 | } 32 | } 33 | 34 | impl AstVisitor for SecureEtherTransferVisitor { 35 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 36 | if let Expression::MemberAccess(member_access) = context.function_call.expression.as_ref() { 37 | if let Some(TypeDescriptions { type_string: Some(type_string), .. }) = member_access.expression.as_ref().type_descriptions() { 38 | match type_string.as_str() { 39 | "address" | "address payable" => {} 40 | _ => return Ok(()) 41 | } 42 | } 43 | 44 | match member_access.member_name.as_str() { 45 | "transfer" | "send" => {} 46 | _ => return Ok(()) 47 | } 48 | 49 | if member_access.referenced_declaration.is_none() || member_access.referenced_declaration.map(|id| id == 0).unwrap_or(false) { 50 | self.add_report_entry( 51 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 52 | context.contract_definition, 53 | context.definition_node, 54 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 55 | context.function_call 56 | ); 57 | } 58 | } 59 | 60 | Ok(()) 61 | } 62 | 63 | fn visit_function_call_options<'a, 'b>(&mut self, context: &mut FunctionCallOptionsContext<'a, 'b>) -> io::Result<()> { 64 | if let Expression::MemberAccess(member_access) = context.function_call_options.expression.as_ref() { 65 | if let Some(TypeDescriptions { type_string: Some(type_string), .. }) = member_access.expression.as_ref().type_descriptions() { 66 | match type_string.as_str() { 67 | "address" | "address payable" => {} 68 | _ => return Ok(()) 69 | } 70 | } 71 | 72 | match member_access.member_name.as_str() { 73 | "transfer" | "send" => {} 74 | _ => return Ok(()) 75 | } 76 | 77 | if member_access.referenced_declaration.is_none() || member_access.referenced_declaration.map(|id| id == 0).unwrap_or(false) { 78 | self.add_report_entry( 79 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 80 | context.contract_definition, 81 | context.definition_node, 82 | context.current_source_unit.source_line(context.function_call_options.src.as_str())?, 83 | context.function_call_options 84 | ); 85 | } 86 | } 87 | 88 | Ok(()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /solast/src/analysis/selfdestruct_usage.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{rc::Rc, cell::RefCell}; 4 | 5 | pub struct SelfdestructUsageVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl SelfdestructUsageVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | ) { 21 | self.report.borrow_mut().add_entry( 22 | source_unit_path, 23 | Some(source_line), 24 | format!( 25 | "{} contains `selfdestruct` usage", 26 | contract_definition.definition_node_location(definition_node), 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | impl AstVisitor for SelfdestructUsageVisitor { 33 | fn visit_statement<'a, 'b>(&mut self, context: &mut StatementContext<'a, 'b>) -> std::io::Result<()> { 34 | if let Statement::ExpressionStatement(ExpressionStatement { 35 | expression: Expression::FunctionCall(FunctionCall { 36 | expression, 37 | src, 38 | .. 39 | }) 40 | }) = context.statement { 41 | if let Expression::Identifier(Identifier { name, .. }) = expression.as_ref() { 42 | if name == "selfdestruct" { 43 | self.add_report_entry( 44 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 45 | context.contract_definition, 46 | context.definition_node, 47 | context.current_source_unit.source_line(src.as_str())?, 48 | ); 49 | } 50 | } 51 | } 52 | 53 | Ok(()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /solast/src/analysis/state_variable_mutability.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::{HashMap, HashSet}, io, rc::Rc}; 5 | 6 | struct VariableInfo { 7 | assigned: bool, 8 | constant: bool, 9 | } 10 | 11 | struct ContractInfo { 12 | variable_info: HashMap, 13 | variable_aliases: HashMap>, 14 | } 15 | 16 | pub struct StateVariableMutabilityVisitor { 17 | report: Rc>, 18 | contract_info: HashMap, 19 | } 20 | 21 | impl StateVariableMutabilityVisitor { 22 | pub fn new(report: Rc>) -> Self { 23 | Self { 24 | report, 25 | contract_info: HashMap::new(), 26 | } 27 | } 28 | } 29 | 30 | // 31 | // TODO: 32 | // check for local variables which are bound to array state variable entries 33 | // if the local variable mutates state, don't suggest that the state variable should be immutable 34 | // 35 | 36 | impl AstVisitor for StateVariableMutabilityVisitor { 37 | fn visit_contract_definition<'a>(&mut self, context: &mut ContractDefinitionContext<'a>) -> io::Result<()> { 38 | self.contract_info.entry(context.contract_definition.id).or_insert_with(|| ContractInfo { 39 | variable_info: HashMap::new(), 40 | variable_aliases: HashMap::new(), 41 | }); 42 | 43 | Ok(()) 44 | } 45 | 46 | fn leave_contract_definition<'a>(&mut self, context: &mut ContractDefinitionContext<'a>) -> io::Result<()> { 47 | if let Some(contract_info) = self.contract_info.get(&context.contract_definition.id) { 48 | for (&id, variable_info) in contract_info.variable_info.iter() { 49 | if let Some(variable_declaration) = context.contract_definition.variable_declaration(id) { 50 | if let Some(solidity::ast::Mutability::Constant | solidity::ast::Mutability::Immutable) = variable_declaration.mutability.as_ref() { 51 | continue; 52 | } 53 | 54 | if variable_declaration.constant { 55 | continue; 56 | } 57 | 58 | if let Some(TypeName::ElementaryTypeName(ElementaryTypeName { 59 | name: type_name, 60 | .. 61 | })) = variable_declaration.type_name.as_ref() { 62 | match type_name.as_str() { 63 | "bytes" | "string" => continue, 64 | _ => () 65 | } 66 | } 67 | 68 | if !variable_info.assigned { 69 | self.report.borrow_mut().add_entry( 70 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 71 | Some(context.current_source_unit.source_line(variable_declaration.src.as_str())?), 72 | format!( 73 | "The {} `{}.{}` {} state variable can be declared `{}`", 74 | variable_declaration.visibility, 75 | context.contract_definition.name, 76 | variable_declaration.name, 77 | variable_declaration.type_name.as_ref().unwrap(), 78 | if variable_info.constant { "constant" } else { "immutable" } 79 | ), 80 | ); 81 | } 82 | } 83 | } 84 | } 85 | 86 | Ok(()) 87 | } 88 | 89 | fn visit_variable_declaration<'a, 'b>(&mut self, context: &mut VariableDeclarationContext<'a, 'b>) -> io::Result<()> { 90 | let contract_definition = match context.contract_definition.as_ref() { 91 | Some(contract_definition) => contract_definition, 92 | None => return Ok(()) 93 | }; 94 | 95 | let contract_info = match self.contract_info.get_mut(&contract_definition.id) { 96 | Some(contract_info) => contract_info, 97 | None => return Ok(()) 98 | }; 99 | 100 | let definition_node = match context.definition_node.as_ref() { 101 | Some(definition_node) => definition_node, 102 | None => return Ok(()) 103 | }; 104 | 105 | match definition_node { 106 | ContractDefinitionNode::VariableDeclaration(_) => { 107 | contract_info.variable_info.entry(context.variable_declaration.id).or_insert_with(|| VariableInfo { 108 | assigned: false, 109 | constant: context.variable_declaration.value.is_some(), 110 | }); 111 | 112 | contract_info.variable_aliases.entry(context.variable_declaration.id).or_insert_with(HashSet::new); 113 | } 114 | 115 | ContractDefinitionNode::FunctionDefinition(_) | ContractDefinitionNode::ModifierDefinition(_) => { 116 | if let StorageLocation::Storage = context.variable_declaration.storage_location { 117 | if let Some(value) = context.variable_declaration.value.as_ref() { 118 | for id in value.referenced_declarations() { 119 | if let Some(variable_aliases) = contract_info.variable_aliases.get_mut(&id) { 120 | if !variable_aliases.contains(&context.variable_declaration.id) { 121 | variable_aliases.insert(context.variable_declaration.id); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | _ => {} 130 | } 131 | 132 | Ok(()) 133 | } 134 | 135 | fn visit_assignment<'a, 'b>(&mut self, context: &mut AssignmentContext<'a, 'b>) -> io::Result<()> { 136 | if let ContractDefinitionNode::FunctionDefinition(FunctionDefinition { 137 | kind: FunctionKind::Constructor, 138 | .. 139 | }) = context.definition_node { 140 | return Ok(()) 141 | } 142 | 143 | let contract_info = match self.contract_info.get_mut(&context.contract_definition.id) { 144 | Some(contract_info) => contract_info, 145 | None => return Ok(()) 146 | }; 147 | 148 | if let Expression::MemberAccess(member_access) = context.assignment.left_hand_side.as_ref() { 149 | let referenced_declarations = member_access.expression.referenced_declarations(); 150 | 151 | if let Some(&referenced_declaration) = referenced_declarations.first() { 152 | if let Some((id, _)) = contract_info.variable_aliases.iter_mut().find(|(_, aliases)| aliases.contains(&referenced_declaration)) { 153 | contract_info.variable_info.get_mut(id).unwrap().assigned = true; 154 | } 155 | } 156 | } 157 | 158 | let ids = context.contract_definition.get_assigned_state_variables( 159 | context.source_units, 160 | context.definition_node, 161 | context.assignment.left_hand_side.as_ref(), 162 | ); 163 | 164 | for id in ids { 165 | if contract_info.variable_info.contains_key(&id) { 166 | contract_info.variable_info.get_mut(&id).unwrap().assigned = true; 167 | } 168 | } 169 | 170 | Ok(()) 171 | } 172 | 173 | fn visit_unary_operation<'a, 'b>(&mut self, context: &mut UnaryOperationContext<'a, 'b>) -> io::Result<()> { 174 | if let ContractDefinitionNode::FunctionDefinition(FunctionDefinition { 175 | kind: FunctionKind::Constructor, 176 | .. 177 | }) = context.definition_node { 178 | return Ok(()) 179 | } 180 | 181 | let ids = context.contract_definition.get_assigned_state_variables( 182 | context.source_units, 183 | context.definition_node, 184 | context.unary_operation.sub_expression.as_ref(), 185 | ); 186 | 187 | for id in ids { 188 | let contract_info = match self.contract_info.get_mut(&context.contract_definition.id) { 189 | Some(contract_info) => contract_info, 190 | None => continue 191 | }; 192 | 193 | if contract_info.variable_info.contains_key(&id) { 194 | contract_info.variable_info.get_mut(&id).unwrap().assigned = true; 195 | } 196 | } 197 | 198 | Ok(()) 199 | } 200 | 201 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 202 | if let Expression::MemberAccess(member_access) = context.function_call.expression.as_ref() { 203 | if member_access.referenced_declaration.is_none() && (member_access.member_name == "push" || member_access.member_name == "pop") { 204 | if let ContractDefinitionNode::FunctionDefinition(FunctionDefinition { 205 | kind: FunctionKind::Constructor, 206 | .. 207 | }) = context.definition_node { 208 | return Ok(()) 209 | } 210 | 211 | let ids = context.contract_definition.get_assigned_state_variables( 212 | context.source_units, 213 | context.definition_node, 214 | member_access.expression.as_ref(), 215 | ); 216 | 217 | for id in ids { 218 | let contract_info = match self.contract_info.get_mut(&context.contract_definition.id) { 219 | Some(contract_info) => contract_info, 220 | None => continue 221 | }; 222 | 223 | if contract_info.variable_info.contains_key(&id) { 224 | contract_info.variable_info.get_mut(&id).unwrap().assigned = true; 225 | } 226 | } 227 | } 228 | } 229 | 230 | Ok(()) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /solast/src/analysis/state_variable_shadowing.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct StateVariableShadowingVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl StateVariableShadowingVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for StateVariableShadowingVisitor { 16 | fn visit_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 17 | let contract_ids = match context.contract_definition.linearized_base_contracts.as_ref() { 18 | Some(contract_ids) => contract_ids, 19 | None => return Ok(()), 20 | }; 21 | 22 | for &base_contract_id in contract_ids.iter() { 23 | let mut base_contract_definition = None; 24 | 25 | for source_unit in context.source_units.iter() { 26 | if let Some(contract_definition) = source_unit.contract_definition(base_contract_id) { 27 | base_contract_definition = Some(contract_definition); 28 | break; 29 | } 30 | } 31 | 32 | if let Some(base_contract_definition) = base_contract_definition { 33 | for variable_declaration in context.function_definition.parameters.parameters.iter() { 34 | for base_variable_declaration in base_contract_definition.variable_declarations() { 35 | if let solidity::ast::Visibility::Private = base_variable_declaration.visibility { 36 | continue; 37 | } 38 | 39 | if variable_declaration.name == base_variable_declaration.name { 40 | self.report.borrow_mut().add_entry( 41 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 42 | Some(context.current_source_unit.source_line(variable_declaration.src.as_str())?), 43 | format!( 44 | "{:?} {} {} has a {} {} parameter '{}' which shadows the {} {} {} state variable", 45 | 46 | context.function_definition.visibility, 47 | 48 | if context.function_definition.name.is_empty() { 49 | context.contract_definition.name.to_string() 50 | } else { 51 | format!("{}.{}", context.contract_definition.name, context.function_definition.name) 52 | }, 53 | 54 | context.function_definition.kind, 55 | 56 | variable_declaration.type_descriptions.type_string.as_ref().unwrap(), 57 | 58 | format!("{:?}", variable_declaration.storage_location).to_lowercase(), 59 | 60 | variable_declaration.name, 61 | 62 | format!("{:?}", base_variable_declaration.visibility).to_lowercase(), 63 | 64 | base_variable_declaration.type_descriptions.type_string.as_ref().unwrap(), 65 | 66 | if base_variable_declaration.name.is_empty() { 67 | base_contract_definition.name.to_string() 68 | } else { 69 | format!("{}.{}", base_contract_definition.name, base_variable_declaration.name) 70 | }, 71 | ), 72 | ); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | Ok(()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /solast/src/analysis/storage_array_loop.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::{HashMap, HashSet}, io, rc::Rc}; 5 | 6 | struct FunctionInfo { 7 | loops_over_storage_array: bool, 8 | } 9 | 10 | pub struct StorageArrayLoopVisitor { 11 | report: Rc>, 12 | storage_arrays: HashSet, 13 | functions: HashMap, 14 | } 15 | 16 | impl StorageArrayLoopVisitor { 17 | pub fn new(report: Rc>) -> Self { 18 | Self { 19 | report, 20 | storage_arrays: HashSet::new(), 21 | functions: HashMap::new(), 22 | } 23 | } 24 | 25 | fn expression_contains_storage_array_length(&self, expression: &solidity::ast::Expression) -> bool { 26 | match expression { 27 | solidity::ast::Expression::BinaryOperation(binary_operation) => { 28 | if self.expression_contains_storage_array_length(binary_operation.left_expression.as_ref()) { 29 | return true; 30 | } 31 | 32 | self.expression_contains_storage_array_length( 33 | binary_operation.right_expression.as_ref(), 34 | ) 35 | } 36 | 37 | solidity::ast::Expression::Conditional(conditional) => { 38 | if self.expression_contains_storage_array_length(conditional.true_expression.as_ref()) { 39 | return true; 40 | } 41 | 42 | self.expression_contains_storage_array_length(conditional.false_expression.as_ref()) 43 | } 44 | 45 | solidity::ast::Expression::Assignment(assignment) => { 46 | self.expression_contains_storage_array_length(assignment.right_hand_side.as_ref()) 47 | } 48 | 49 | solidity::ast::Expression::MemberAccess(member_access) => { 50 | let referenced_declarations = member_access.expression.referenced_declarations(); 51 | 52 | if referenced_declarations.is_empty() { 53 | return false; 54 | } 55 | 56 | member_access.member_name == "length" 57 | && self 58 | .storage_arrays 59 | .contains(referenced_declarations.iter().last().unwrap_or(&0)) 60 | } 61 | 62 | solidity::ast::Expression::TupleExpression(tuple_expression) => { 63 | for component in tuple_expression.components.iter().flatten() { 64 | if self.expression_contains_storage_array_length(component) { 65 | return true; 66 | } 67 | } 68 | 69 | false 70 | } 71 | 72 | _ => false, 73 | } 74 | } 75 | } 76 | 77 | impl AstVisitor for StorageArrayLoopVisitor { 78 | fn visit_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 79 | self.functions.entry(context.function_definition.id).or_insert_with(|| FunctionInfo { 80 | loops_over_storage_array: false, 81 | }); 82 | 83 | for variable_declaration in context.function_definition.parameters.parameters.iter() { 84 | if let solidity::ast::StorageLocation::Storage = variable_declaration.storage_location { 85 | if let Some(solidity::ast::TypeName::ArrayTypeName(_)) = variable_declaration.type_name { 86 | if !self.storage_arrays.contains(&variable_declaration.id) { 87 | self.storage_arrays.insert(variable_declaration.id); 88 | } 89 | } 90 | } 91 | } 92 | 93 | Ok(()) 94 | } 95 | 96 | fn leave_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 97 | if let Some(function_info) = self.functions.get(&context.function_definition.id) { 98 | if function_info.loops_over_storage_array { 99 | self.report.borrow_mut().add_entry( 100 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 101 | Some(context.current_source_unit.source_line(context.function_definition.src.as_str())?), 102 | format!( 103 | "{:?} {} {} performs a loop over a storage array, querying the length over each iteration", 104 | 105 | context.function_definition.visibility, 106 | 107 | if context.function_definition.name.is_empty() { 108 | context.contract_definition.name.to_string() 109 | } else { 110 | format!("{}.{}", context.contract_definition.name, context.function_definition.name) 111 | }, 112 | 113 | context.function_definition.kind 114 | ), 115 | ); 116 | } 117 | } 118 | 119 | Ok(()) 120 | } 121 | 122 | fn visit_variable_declaration<'a, 'b>(&mut self, context: &mut VariableDeclarationContext<'a, 'b>) -> io::Result<()> { 123 | let storage_location = match &context.variable_declaration.storage_location { 124 | solidity::ast::StorageLocation::Default if context.variable_declaration.state_variable => solidity::ast::StorageLocation::Storage, 125 | storage_location => *storage_location, 126 | }; 127 | 128 | if let solidity::ast::StorageLocation::Storage = storage_location { 129 | if let Some(solidity::ast::TypeName::ArrayTypeName(_)) = context.variable_declaration.type_name { 130 | if !self.storage_arrays.contains(&context.variable_declaration.id) { 131 | self.storage_arrays.insert(context.variable_declaration.id); 132 | } 133 | } 134 | } 135 | 136 | Ok(()) 137 | } 138 | 139 | fn visit_for_statement<'a, 'b>(&mut self, context: &mut ForStatementContext<'a, 'b>) -> io::Result<()> { 140 | let definition_id = match context.definition_node { 141 | solidity::ast::ContractDefinitionNode::FunctionDefinition(definition) => definition.id, 142 | solidity::ast::ContractDefinitionNode::ModifierDefinition(definition) => definition.id, 143 | _ => return Ok(()) 144 | }; 145 | 146 | if let Some(expression) = context.for_statement.condition.as_ref() { 147 | if self.expression_contains_storage_array_length(expression) { 148 | self.functions 149 | .get_mut(&definition_id) 150 | .unwrap() 151 | .loops_over_storage_array = true; 152 | } 153 | } 154 | 155 | Ok(()) 156 | } 157 | 158 | fn visit_while_statement<'a, 'b>(&mut self, context: &mut WhileStatementContext<'a, 'b>) -> io::Result<()> { 159 | let definition_id = match context.definition_node { 160 | solidity::ast::ContractDefinitionNode::FunctionDefinition(definition) => definition.id, 161 | solidity::ast::ContractDefinitionNode::ModifierDefinition(definition) => definition.id, 162 | _ => return Ok(()) 163 | }; 164 | 165 | if self.expression_contains_storage_array_length(&context.while_statement.condition) { 166 | self.functions 167 | .get_mut(&definition_id) 168 | .unwrap() 169 | .loops_over_storage_array = true; 170 | } 171 | 172 | Ok(()) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /solast/src/analysis/tight_variable_packing.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::HashMap, rc::Rc}; 5 | 6 | struct StorageSlot { 7 | member_sizes: Vec 8 | } 9 | 10 | pub struct TightVariablePackingVisitor { 11 | report: Rc>, 12 | storage_slots: HashMap> 13 | } 14 | 15 | impl TightVariablePackingVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { 18 | report, 19 | storage_slots: HashMap::new(), 20 | } 21 | } 22 | } 23 | 24 | fn type_name_size(source_units: &[SourceUnit], type_name: &TypeName) -> std::io::Result { 25 | Ok(match type_name { 26 | TypeName::ElementaryTypeName(ElementaryTypeName { name, .. }) => match name.as_str() { 27 | "bool" => 1, 28 | 29 | "address" => 20, 30 | 31 | type_name if type_name.starts_with("uint") => { 32 | let size_in_bits = match type_name.trim_start_matches("uint") { 33 | "" => 256, 34 | s => s.parse().map_err(|err| { 35 | std::io::Error::new( 36 | std::io::ErrorKind::InvalidData, 37 | format!("unhandled {} type: {}", type_name, err) 38 | ) 39 | })? 40 | }; 41 | 42 | size_in_bits / 8 43 | } 44 | 45 | type_name if type_name.starts_with("int") => { 46 | let size_in_bits = match type_name.trim_start_matches("int") { 47 | "" => 256, 48 | s => s.parse().map_err(|err| { 49 | std::io::Error::new( 50 | std::io::ErrorKind::InvalidData, 51 | format!("unhandled {} type: {}", type_name, err) 52 | ) 53 | })? 54 | }; 55 | 56 | size_in_bits / 8 57 | } 58 | 59 | type_name if type_name.starts_with("bytes") => { 60 | let size_in_bytes: usize = match type_name.trim_start_matches("bytes") { 61 | "" => 32, 62 | s => s.parse().map_err(|err| { 63 | std::io::Error::new( 64 | std::io::ErrorKind::InvalidData, 65 | format!("unhandled {} type: {}", type_name, err) 66 | ) 67 | })? 68 | }; 69 | 70 | size_in_bytes 71 | } 72 | 73 | _ => 32 // TODO: handle this correctly... 74 | } 75 | 76 | TypeName::UserDefinedTypeName(UserDefinedTypeName { referenced_declaration, .. }) => { 77 | let id = *referenced_declaration; 78 | 79 | for source_unit in source_units.iter() { 80 | if source_unit.enum_definition(id).is_some() { 81 | return Ok(1) 82 | } else if let Some(struct_definition) = source_unit.struct_definition(id) { 83 | let mut size = 0; 84 | 85 | for member in struct_definition.members.iter() { 86 | size += type_name_size(source_units, member.type_name.as_ref().unwrap())?; 87 | } 88 | 89 | return Ok(size) 90 | } else { 91 | for contract_definition in source_unit.contract_definitions() { 92 | if contract_definition.enum_definition(id).is_some() { 93 | return Ok(1) 94 | } else if let Some(struct_definition) = contract_definition.struct_definition(id) { 95 | let mut size = 0; 96 | 97 | for member in struct_definition.members.iter() { 98 | size += type_name_size(source_units, member.type_name.as_ref().unwrap())?; 99 | } 100 | 101 | return Ok(size) 102 | } 103 | } 104 | } 105 | } 106 | 107 | return Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("User-defined type not found: {}", type_name))) 108 | } 109 | 110 | TypeName::ArrayTypeName(ArrayTypeName { base_type, length: Some(Literal { value, .. }), .. }) => { 111 | let value = value.as_ref().unwrap(); 112 | 113 | if let Ok(length) = if value.starts_with("0x") { 114 | i64::from_str_radix(value.trim_start_matches("0x"), 16) 115 | } else { 116 | value.parse() 117 | } { 118 | return Ok(type_name_size(source_units, base_type.as_ref())? * length as usize) 119 | } 120 | 121 | return Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("Unhandled array type: {}", type_name))) 122 | } 123 | 124 | TypeName::ArrayTypeName(ArrayTypeName { length: None, .. }) => 32, 125 | TypeName::FunctionTypeName(_) => 32, 126 | TypeName::Mapping(_) => 32, 127 | TypeName::String(_) => 32, 128 | }) 129 | } 130 | 131 | impl AstVisitor for TightVariablePackingVisitor { 132 | fn visit_struct_definition<'a>(&mut self, context: &mut StructDefinitionContext<'a>) -> std::io::Result<()> { 133 | if self.storage_slots.contains_key(&context.struct_definition.id) { 134 | return Ok(()) 135 | } 136 | 137 | self.storage_slots.insert(context.struct_definition.id, vec![]); 138 | 139 | let storage_slots = self.storage_slots.get_mut(&context.struct_definition.id).unwrap(); 140 | 141 | let mut current_slot = None; 142 | 143 | for member in context.struct_definition.members.iter() { 144 | if current_slot.is_none() { 145 | current_slot = Some(StorageSlot { member_sizes: vec![] }); 146 | } 147 | 148 | let current_slot_size: usize = current_slot.as_ref().unwrap().member_sizes.iter().sum(); 149 | 150 | let member_type_name_size = match type_name_size(context.source_units, member.type_name.as_ref().unwrap()) { 151 | Ok(x) => x, 152 | Err(_) => continue, 153 | }; 154 | 155 | if current_slot_size + member_type_name_size > 32 { 156 | storage_slots.push(current_slot.unwrap()); 157 | current_slot = Some(StorageSlot { member_sizes: vec![] }); 158 | } 159 | 160 | current_slot.as_mut().unwrap().member_sizes.push(member_type_name_size); 161 | } 162 | 163 | if let Some(false) = current_slot.as_ref().map(|slot| slot.member_sizes.is_empty()) { 164 | storage_slots.push(current_slot.unwrap()); 165 | } 166 | 167 | let mut has_loose_variable_packing = false; 168 | 169 | for slot in storage_slots.split_last().map(|x| x.1).unwrap_or(&[]) { 170 | if slot.member_sizes.iter().sum::() != 32 { 171 | has_loose_variable_packing = true; 172 | break; 173 | } 174 | } 175 | 176 | // 177 | // TODO: try to see if looseness can be made tight 178 | // if no optimizations can be made, set has_loose_variable_packing to false 179 | // 180 | 181 | if has_loose_variable_packing { 182 | // TODO: only print when this works... 183 | // println!("\tStruct {} has loose variable packing", context.struct_definition.name); 184 | } 185 | 186 | Ok(()) 187 | } 188 | 189 | fn visit_contract_definition<'a>(&mut self, context: &mut ContractDefinitionContext<'a>) -> std::io::Result<()> { 190 | if let ContractKind::Interface | ContractKind::Library = context.contract_definition.kind { 191 | return Ok(()) 192 | } 193 | 194 | if self.storage_slots.contains_key(&context.contract_definition.id) { 195 | return Ok(()) 196 | } 197 | 198 | self.storage_slots.insert(context.contract_definition.id, vec![]); 199 | 200 | let storage_slots = self.storage_slots.get_mut(&context.contract_definition.id).unwrap(); 201 | 202 | let mut current_slot = None; 203 | 204 | for member in context.contract_definition.variable_declarations() { 205 | if current_slot.is_none() { 206 | current_slot = Some(StorageSlot { member_sizes: vec![] }); 207 | } 208 | 209 | let current_slot_size: usize = current_slot.as_ref().unwrap().member_sizes.iter().sum(); 210 | 211 | let member_type_name_size = match type_name_size(context.source_units, member.type_name.as_ref().unwrap()) { 212 | Ok(x) => x, 213 | Err(_) => continue, 214 | }; 215 | 216 | if current_slot_size + member_type_name_size > 32 { 217 | storage_slots.push(current_slot.unwrap()); 218 | current_slot = Some(StorageSlot { member_sizes: vec![] }); 219 | } 220 | 221 | current_slot.as_mut().unwrap().member_sizes.push(member_type_name_size); 222 | } 223 | 224 | if let Some(false) = current_slot.as_ref().map(|slot| slot.member_sizes.is_empty()) { 225 | storage_slots.push(current_slot.unwrap()); 226 | } 227 | 228 | let mut has_loose_variable_packing = false; 229 | 230 | for slot in storage_slots.split_last().map(|x| x.1).unwrap_or(&[]) { 231 | if slot.member_sizes.iter().sum::() != 32 { 232 | has_loose_variable_packing = true; 233 | break; 234 | } 235 | } 236 | 237 | // 238 | // TODO: try to see if looseness can be made tight 239 | // if no optimizations can be made, set has_loose_variable_packing to false 240 | // 241 | 242 | if has_loose_variable_packing { 243 | // TODO: only print when this works... 244 | // println!("{:?} {} has loose variable packing", context.contract_definition.kind, context.contract_definition.name); 245 | } 246 | 247 | Ok(()) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /solast/src/analysis/unchecked_casting.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct UncheckedCastingVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl UncheckedCastingVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for UncheckedCastingVisitor { 16 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 17 | if context.function_call.kind != FunctionCallKind::TypeConversion { 18 | return Ok(()) 19 | } 20 | 21 | let (type_descriptions, type_name) = match context.function_call.expression.as_ref() { 22 | Expression::ElementaryTypeNameExpression(ElementaryTypeNameExpression { 23 | type_name: TypeName::ElementaryTypeName(type_name), 24 | type_descriptions, 25 | .. 26 | }) => (type_descriptions, type_name), 27 | 28 | _ => return Ok(()) 29 | }; 30 | 31 | if type_name.name.starts_with("int") || type_name.name.starts_with("uint") { 32 | // 33 | // TODO: if the argument is a non-literal, verify its expression was 34 | // checked for validity via require, if/else or a conditional 35 | // 36 | } 37 | 38 | // 39 | // Check for redundant cast (i.e: casting uint256 to uint256) 40 | // 41 | 42 | let Some(argument_expression) = context.function_call.arguments.first() else { return Ok(()) }; 43 | let Some(argument_type_descriptions) = argument_expression.type_descriptions() else { return Ok(()) }; 44 | 45 | if type_descriptions == argument_type_descriptions { 46 | let name = match context.definition_node { 47 | ContractDefinitionNode::FunctionDefinition(function_definition) => format!( 48 | "{} {}", 49 | function_definition.visibility, 50 | if let FunctionKind::Constructor = function_definition.kind { 51 | "constructor".to_string() 52 | } else { 53 | format!("`{}` {}", function_definition.name, function_definition.kind) 54 | }, 55 | ), 56 | 57 | ContractDefinitionNode::ModifierDefinition(modifier_definition) => format!( 58 | "`{}` modifier", 59 | modifier_definition.name, 60 | ), 61 | 62 | _ => return Ok(()), 63 | }; 64 | 65 | self.report.borrow_mut().add_entry( 66 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 67 | Some(context.current_source_unit.source_line(context.function_call.src.as_str())?), 68 | format!( 69 | "The {name} in the `{}` {} contains a redundant cast: `{}`", 70 | context.contract_definition.name, 71 | context.contract_definition.kind, 72 | context.function_call 73 | ), 74 | ); 75 | } 76 | 77 | Ok(()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /solast/src/analysis/unnecessary_pragmas.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct UnnecessaryPragmasVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl UnnecessaryPragmasVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn check_pragma_directives( 15 | &mut self, 16 | source_unit_path: String, 17 | source_line: Option, 18 | solidity: &mut Vec<&str>, 19 | abicoder: &mut Vec<&str>, 20 | ) { 21 | let mut op = None; 22 | let mut lower: Option = None; 23 | let mut upper: Option = None; 24 | 25 | for &literal in solidity.iter() { 26 | match literal { 27 | "^" | ">" | ">=" | "<=" | "<" => op = Some(literal), 28 | 29 | _ if !literal.starts_with('.') => { 30 | match op { 31 | None | Some("=" | "^" | ">" | ">=") => { 32 | lower = Some(literal.parse().unwrap_or(0.0)); 33 | 34 | if let Some(f) = lower { 35 | if f == 0.0 { 36 | lower = None; 37 | } 38 | } 39 | } 40 | 41 | Some("<" | "<=") => { 42 | upper = Some(literal.parse().unwrap_or(0.0)); 43 | 44 | if let Some(f) = upper { 45 | if f == 0.0 { 46 | upper = None; 47 | } 48 | } 49 | } 50 | 51 | _ => {} 52 | } 53 | } 54 | 55 | _ => {} 56 | } 57 | } 58 | 59 | if let (Some(lower), None) = (lower, upper) { 60 | upper = Some(lower + 0.1); 61 | } 62 | 63 | if let (None, Some(upper)) = (lower, upper) { 64 | lower = Some(upper - 0.1); 65 | } 66 | 67 | if abicoder.contains(&"v2") { 68 | if let Some(lower) = lower { 69 | if lower >= 0.8 { 70 | self.report.borrow_mut().add_entry( 71 | source_unit_path, 72 | source_line, 73 | "Unnecessary specification of `pragma abicoder v2`, which is enabled in Solidity v0.8.0 and above", 74 | ) 75 | } 76 | } 77 | } 78 | 79 | solidity.clear(); 80 | abicoder.clear(); 81 | } 82 | } 83 | 84 | impl AstVisitor for UnnecessaryPragmasVisitor { 85 | fn visit_source_unit<'a>(&mut self, context: &mut SourceUnitContext<'a>) -> io::Result<()> { 86 | let mut solidity: Vec<&str> = vec![]; 87 | let mut abicoder: Vec<&str> = vec![]; 88 | 89 | for pragma_directive in context.current_source_unit.pragma_directives() { 90 | match pragma_directive.literals.first().map(String::as_str) { 91 | Some("solidity") => { 92 | if !solidity.is_empty() { 93 | self.check_pragma_directives( 94 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 95 | Some(context.current_source_unit.source_line(pragma_directive.src.as_str())?), 96 | &mut solidity, 97 | &mut abicoder 98 | ); 99 | } 100 | 101 | solidity.extend(pragma_directive.literals.iter().skip(1).map(String::as_str)); 102 | } 103 | 104 | Some("abicoder") => { 105 | if !abicoder.is_empty() { 106 | self.check_pragma_directives( 107 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 108 | Some(context.current_source_unit.source_line(pragma_directive.src.as_str())?), 109 | &mut solidity, 110 | &mut abicoder 111 | ); 112 | } 113 | 114 | abicoder.extend(pragma_directive.literals.iter().skip(1).map(String::as_str)); 115 | } 116 | 117 | _ => {} 118 | } 119 | } 120 | 121 | self.check_pragma_directives( 122 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 123 | None, 124 | &mut solidity, 125 | &mut abicoder 126 | ); 127 | 128 | Ok(()) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /solast/src/analysis/unpaid_payable_functions.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct UnpaidPayableFunctionsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl UnpaidPayableFunctionsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn add_report_entry( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | expression: &dyn std::fmt::Display 21 | ) { 22 | self.report.borrow_mut().add_entry( 23 | source_unit_path, 24 | Some(source_line), 25 | format!( 26 | "{} calls a payable function without paying: `{}`", 27 | contract_definition.definition_node_location(definition_node), 28 | expression 29 | ), 30 | ); 31 | } 32 | } 33 | 34 | impl AstVisitor for UnpaidPayableFunctionsVisitor { 35 | fn visit_function_call<'a, 'b>(&mut self, context: &mut FunctionCallContext<'a, 'b>) -> io::Result<()> { 36 | match context.function_call.expression.as_ref() { 37 | solidity::ast::Expression::Identifier(identifier) => { 38 | for source_unit in context.source_units.iter() { 39 | if let Some(FunctionDefinition { 40 | state_mutability: StateMutability::Payable, 41 | .. 42 | }) = source_unit.function_definition(identifier.referenced_declaration) { 43 | self.add_report_entry( 44 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 45 | context.contract_definition, 46 | context.definition_node, 47 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 48 | context.function_call, 49 | ); 50 | break; 51 | } 52 | } 53 | } 54 | 55 | solidity::ast::Expression::MemberAccess(member_access) => { 56 | let referenced_declaration = match member_access.referenced_declaration { 57 | Some(id) => id, 58 | None => return Ok(()), 59 | }; 60 | 61 | for source_unit in context.source_units.iter() { 62 | if let Some(FunctionDefinition { 63 | state_mutability: StateMutability::Payable, 64 | .. 65 | }) = source_unit.function_definition(referenced_declaration) { 66 | self.add_report_entry( 67 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 68 | context.contract_definition, 69 | context.definition_node, 70 | context.current_source_unit.source_line(context.function_call.src.as_str())?, 71 | context.function_call, 72 | ); 73 | break; 74 | } 75 | } 76 | } 77 | 78 | _ => {} 79 | } 80 | 81 | Ok(()) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /solast/src/analysis/unreferenced_state_variables.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use eth_lang_utils::ast::*; 3 | use solidity::ast::*; 4 | use std::{cell::RefCell, collections::HashMap, io, rc::Rc}; 5 | 6 | pub struct ContractInfo { 7 | variable_info: HashMap, 8 | } 9 | 10 | pub struct UnusedStateVariablesVisitor { 11 | report: Rc>, 12 | contract_info: HashMap, 13 | } 14 | 15 | impl UnusedStateVariablesVisitor { 16 | pub fn new(report: Rc>) -> Self { 17 | Self { 18 | report, 19 | contract_info: HashMap::new(), 20 | } 21 | } 22 | } 23 | 24 | impl AstVisitor for UnusedStateVariablesVisitor { 25 | fn visit_contract_definition<'a>(&mut self, context: &mut ContractDefinitionContext<'a>) -> io::Result<()> { 26 | self.contract_info.entry(context.contract_definition.id).or_insert_with(|| ContractInfo { 27 | variable_info: HashMap::new(), 28 | }); 29 | 30 | Ok(()) 31 | } 32 | 33 | fn leave_contract_definition<'a>(&mut self, context: &mut ContractDefinitionContext<'a>) -> io::Result<()> { 34 | if let Some(contract_info) = self.contract_info.get(&context.contract_definition.id) { 35 | for (&id, &referenced) in contract_info.variable_info.iter() { 36 | if let Some(variable_declaration) = context.contract_definition.variable_declaration(id) { 37 | if let Some(solidity::ast::Mutability::Constant) = variable_declaration.mutability.as_ref() { 38 | continue; 39 | } 40 | 41 | if !referenced { 42 | self.report.borrow_mut().add_entry( 43 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 44 | Some(context.current_source_unit.source_line(variable_declaration.src.as_str())?), 45 | format!( 46 | "The {} `{}.{}` {} state variable is never referenced", 47 | variable_declaration.visibility, 48 | context.contract_definition.name, 49 | variable_declaration.name, 50 | variable_declaration.type_name.as_ref().unwrap(), 51 | ), 52 | ); 53 | } 54 | } 55 | } 56 | } 57 | 58 | Ok(()) 59 | } 60 | 61 | fn visit_variable_declaration<'a, 'b>(&mut self, context: &mut VariableDeclarationContext<'a, 'b>) -> io::Result<()> { 62 | let contract_definition = match context.contract_definition.as_ref() { 63 | Some(contract_definition) => contract_definition, 64 | None => return Ok(()) 65 | }; 66 | 67 | let definition_node = match context.definition_node.as_ref() { 68 | Some(definition_node) => definition_node, 69 | None => return Ok(()) 70 | }; 71 | 72 | if let ContractDefinitionNode::VariableDeclaration(variable_declaration) = definition_node { 73 | let contract_info = self.contract_info.get_mut(&contract_definition.id).unwrap(); 74 | 75 | contract_info.variable_info.entry(variable_declaration.id).or_insert_with(|| false); 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | fn visit_identifier<'a, 'b>(&mut self, context: &mut IdentifierContext<'a, 'b>) -> io::Result<()> { 82 | match context.definition_node { 83 | ContractDefinitionNode::FunctionDefinition(function_definition) if function_definition.kind != FunctionKind::Constructor => {} 84 | ContractDefinitionNode::ModifierDefinition(_) => {} 85 | _ => return Ok(()) 86 | } 87 | 88 | let contract_info = self.contract_info.get_mut(&context.contract_definition.id).unwrap(); 89 | 90 | if let Some(variable_info) = contract_info.variable_info.get_mut(&context.identifier.referenced_declaration) { 91 | *variable_info = true; 92 | } 93 | 94 | Ok(()) 95 | } 96 | 97 | fn visit_member_access<'a, 'b>(&mut self, context: &mut MemberAccessContext<'a, 'b>) -> io::Result<()> { 98 | match context.definition_node { 99 | ContractDefinitionNode::FunctionDefinition(function_definition) if function_definition.kind != FunctionKind::Constructor => {} 100 | ContractDefinitionNode::ModifierDefinition(_) => {} 101 | _ => return Ok(()) 102 | } 103 | 104 | let contract_info = self.contract_info.get_mut(&context.contract_definition.id).unwrap(); 105 | 106 | if let Some(referenced_declaration) = context.member_access.referenced_declaration { 107 | if let Some(variable_info) = contract_info.variable_info.get_mut(&referenced_declaration) { 108 | *variable_info = true; 109 | } 110 | } 111 | 112 | Ok(()) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /solast/src/analysis/unrestricted_setter_functions.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, io, rc::Rc}; 4 | 5 | pub struct UnrestrictedSetterFunctionsVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl UnrestrictedSetterFunctionsVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | 14 | fn print_message( 15 | &mut self, 16 | source_unit_path: String, 17 | contract_definition: &ContractDefinition, 18 | definition_node: &ContractDefinitionNode, 19 | source_line: usize, 20 | ) { 21 | self.report.borrow_mut().add_entry( 22 | source_unit_path, 23 | Some(source_line), 24 | format!( 25 | "{} is an unprotected setter function", 26 | contract_definition.definition_node_location(definition_node), 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | impl AstVisitor for UnrestrictedSetterFunctionsVisitor { 33 | fn visit_function_definition<'a>(&mut self, context: &mut FunctionDefinitionContext<'a>) -> io::Result<()> { 34 | if let FunctionKind::Constructor = context.function_definition.kind { 35 | return Ok(()) 36 | } 37 | 38 | if let Visibility::Private | Visibility::Internal = context.function_definition.visibility { 39 | return Ok(()) 40 | } 41 | 42 | if let StateMutability::Pure | StateMutability::View = context.function_definition.state_mutability { 43 | return Ok(()) 44 | } 45 | 46 | if !context.function_definition.modifiers.is_empty() { 47 | // 48 | // TODO: check for onlyOwner-like modifiers? 49 | // 50 | 51 | return Ok(()) 52 | } 53 | 54 | match context.function_definition.body.as_ref() { 55 | Some(block) if !block.statements.is_empty() => {} 56 | _ => return Ok(()) 57 | } 58 | 59 | for statement in context.function_definition.body.as_ref().unwrap().statements.iter() { 60 | match statement { 61 | Statement::ExpressionStatement(ExpressionStatement { 62 | expression: Expression::Assignment(_), 63 | }) => continue, 64 | 65 | _ => return Ok(()) 66 | } 67 | } 68 | 69 | self.print_message( 70 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 71 | context.contract_definition, 72 | context.definition_node, 73 | context.current_source_unit.source_line(context.function_definition.src.as_str())?, 74 | ); 75 | 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /solast/src/analysis/unused_return.rs: -------------------------------------------------------------------------------- 1 | use crate::report::Report; 2 | use solidity::ast::*; 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub struct UnusedReturnVisitor { 6 | report: Rc>, 7 | } 8 | 9 | impl UnusedReturnVisitor { 10 | pub fn new(report: Rc>) -> Self { 11 | Self { report } 12 | } 13 | } 14 | 15 | impl AstVisitor for UnusedReturnVisitor { 16 | fn visit_statement<'a, 'b>(&mut self, context: &mut StatementContext<'a, 'b>) -> std::io::Result<()> { 17 | let (referenced_declaration, src) = match context.statement { 18 | Statement::ExpressionStatement(ExpressionStatement { 19 | expression: Expression::FunctionCall(FunctionCall { 20 | arguments, 21 | expression, 22 | src, 23 | .. 24 | }) 25 | }) if !arguments.is_empty() => match expression.root_expression() { 26 | Some(&Expression::Identifier(Identifier { 27 | referenced_declaration, 28 | .. 29 | })) => (referenced_declaration, src), 30 | 31 | Some(&Expression::MemberAccess(MemberAccess { 32 | referenced_declaration: Some(referenced_delcaration), 33 | .. 34 | })) => (referenced_delcaration, src), 35 | 36 | _ => return Ok(()) 37 | } 38 | 39 | _ => return Ok(()) 40 | }; 41 | 42 | for source_unit in context.source_units.iter() { 43 | if let Some((called_contract_definition, called_function_definition)) = source_unit.function_and_contract_definition(referenced_declaration) { 44 | if !called_function_definition.return_parameters.parameters.is_empty() { 45 | self.report.borrow_mut().add_entry( 46 | context.current_source_unit.absolute_path.clone().unwrap_or_else(String::new), 47 | Some(context.current_source_unit.source_line(src)?), 48 | format!( 49 | "{} makes a call to the {}, ignoring the returned {}", 50 | 51 | context.contract_definition.definition_node_location(context.definition_node), 52 | 53 | format!( 54 | "{} `{}` {}", 55 | 56 | format!("{:?}", called_function_definition.visibility).to_lowercase(), 57 | 58 | if called_function_definition.name.is_empty() { 59 | called_contract_definition.name.to_string() 60 | } else { 61 | format!("{}.{}", called_contract_definition.name, called_function_definition.name) 62 | }, 63 | 64 | format!("{:?}", called_function_definition.kind).to_lowercase(), 65 | ), 66 | 67 | if called_function_definition.return_parameters.parameters.len() == 1 { "value" } else { "values" }, 68 | ), 69 | ); 70 | } 71 | 72 | break; 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /solast/src/brownie.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use solidity::ast::*; 3 | 4 | #[derive(Clone, Debug, Deserialize, Serialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct File { 7 | // TODO: abi 8 | // TODO: all_source_paths 9 | pub ast: Option, 10 | pub bytecode: Option, 11 | // TODO: bytecode_sha1 12 | // TODO: compiler 13 | pub contract_name: Option, 14 | // TODO: coverage_map 15 | // TODO: dependencies 16 | pub deployed_bytecode: Option, 17 | pub deployed_source_map: Option, 18 | // TODO: language 19 | // TODO: natspec 20 | // TODO: offset 21 | // TODO: opcodes 22 | // TODO: pc_map 23 | // TODO: sha1 24 | pub source: Option, 25 | pub source_map: Option, 26 | pub source_path: Option, 27 | // TODO: type 28 | } 29 | -------------------------------------------------------------------------------- /solast/src/foundry.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use solidity::ast::*; 3 | 4 | #[derive(Clone, Debug, Deserialize, Serialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct File { 7 | pub ast: SourceUnit, 8 | } 9 | -------------------------------------------------------------------------------- /solast/src/hardhat.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use solidity::ast::*; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct InputSource { 9 | pub content: String, 10 | } 11 | 12 | #[derive(Clone, Debug, Deserialize, Serialize)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct Input { 15 | pub language: String, 16 | pub sources: HashMap, 17 | pub settings: serde_json::Value, 18 | } 19 | 20 | #[derive(Clone, Debug, Deserialize, Serialize)] 21 | #[serde(rename_all = "camelCase")] 22 | pub struct OutputSource { 23 | pub ast: SourceUnit, 24 | pub id: u64, 25 | } 26 | 27 | #[derive(Clone, Debug, Deserialize, Serialize)] 28 | #[serde(rename_all = "camelCase")] 29 | pub struct Output { 30 | pub contracts: HashMap, 31 | pub sources: HashMap, 32 | } 33 | 34 | #[derive(Clone, Debug, Deserialize, Serialize)] 35 | #[serde(rename_all = "camelCase")] 36 | pub struct File { 37 | pub id: String, 38 | #[serde(rename = "_format")] 39 | pub format: String, 40 | pub solc_version: String, 41 | pub solc_long_version: String, 42 | pub input: Input, 43 | pub output: Output, 44 | } 45 | -------------------------------------------------------------------------------- /solast/src/report.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::{collections::HashMap, fmt::Display, path::PathBuf}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Serialize)] 5 | pub struct Entry { 6 | pub line: Option, 7 | pub text: String, 8 | } 9 | 10 | impl Display for Entry { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | write!( 13 | f, 14 | "{}{}", 15 | if let Some(line) = self.line.as_ref() { 16 | format!("L{}: ", line) 17 | } else { 18 | String::new() 19 | }, 20 | self.text, 21 | ) 22 | } 23 | } 24 | 25 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 26 | pub struct Report { 27 | pub entries: HashMap>, 28 | } 29 | 30 | impl Report { 31 | pub fn add_entry, S: Into>( 32 | &mut self, 33 | file: P, 34 | line: Option, 35 | text: S, 36 | ) { 37 | self.entries 38 | .entry(file.into()) 39 | .or_insert_with(|| vec![]) 40 | .push(Entry { 41 | line, 42 | text: text.into(), 43 | }); 44 | } 45 | 46 | pub fn sort_entries(&mut self) { 47 | self.entries.iter_mut().for_each(|(_, entries)| { 48 | entries.sort_by(|a, b| { 49 | a.line 50 | .unwrap_or_else(|| 0) 51 | .cmp(&b.line.unwrap_or_else(|| 0)) 52 | }); 53 | }); 54 | } 55 | } 56 | 57 | impl Display for Report { 58 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 59 | for (i, (path, entries)) in self.entries.iter().enumerate() { 60 | if i > 0 { 61 | writeln!(f)?; 62 | } 63 | 64 | writeln!(f, "{}:", path.to_string_lossy())?; 65 | 66 | for entry in entries.iter() { 67 | writeln!(f, "\t{entry}")?; 68 | } 69 | } 70 | 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /solast/src/todo_list.rs: -------------------------------------------------------------------------------- 1 | use solidity::ast::*; 2 | 3 | pub fn print(source_units: &[SourceUnit]) { 4 | for source_unit in source_units.iter() { 5 | for contract_definition in source_unit.contract_definitions() { 6 | if let solidity::ast::ContractKind::Interface = contract_definition.kind { 7 | continue; 8 | } 9 | 10 | print!("### `"); 11 | 12 | if contract_definition.is_abstract.unwrap_or(false) { 13 | print!("abstract "); 14 | } 15 | 16 | print!("{} ", contract_definition.kind); 17 | 18 | println!("{}`:", contract_definition.name); 19 | 20 | let mut lines = vec![]; 21 | 22 | // 23 | // Print enums 24 | // 25 | 26 | for definition_node in contract_definition.nodes.iter() { 27 | if let solidity::ast::ContractDefinitionNode::EnumDefinition(enum_definition) = definition_node { 28 | lines.push(format!("- [ ] `enum {}`", enum_definition.name).to_string()); 29 | } 30 | } 31 | 32 | if !lines.is_empty() { 33 | println!(); 34 | println!("#### Enums:"); 35 | 36 | for line in lines.iter() { 37 | println!("{}", line); 38 | } 39 | 40 | lines.clear(); 41 | } 42 | 43 | // 44 | // Print structs 45 | // 46 | 47 | for definition_node in contract_definition.nodes.iter() { 48 | if let solidity::ast::ContractDefinitionNode::StructDefinition(struct_definition) = definition_node { 49 | lines.push(format!("- [ ] `struct {}`", struct_definition.name).to_string()); 50 | } 51 | } 52 | 53 | if !lines.is_empty() { 54 | println!(); 55 | println!("#### Structs:"); 56 | 57 | for line in lines.iter() { 58 | println!("{}", line); 59 | } 60 | 61 | lines.clear(); 62 | } 63 | 64 | // 65 | // Print variables 66 | // 67 | 68 | for definition_node in contract_definition.nodes.iter() { 69 | if let solidity::ast::ContractDefinitionNode::VariableDeclaration(variable_declaration) = definition_node { 70 | lines.push(format!("- [ ] `{}`", variable_declaration).to_string()); 71 | } 72 | } 73 | 74 | if !lines.is_empty() { 75 | println!(); 76 | println!("#### Variables:"); 77 | 78 | for line in lines.iter() { 79 | println!("{}", line); 80 | } 81 | 82 | lines.clear(); 83 | } 84 | 85 | // 86 | // Print modifiers 87 | // 88 | 89 | for definition_node in contract_definition.nodes.iter() { 90 | if let solidity::ast::ContractDefinitionNode::ModifierDefinition(modifier_definition) = definition_node { 91 | let mut line = String::new(); 92 | 93 | line.push_str("- [ ] `modifier"); 94 | 95 | if !modifier_definition.name.is_empty() { 96 | line.push_str(format!(" {}", modifier_definition.name).as_str()); 97 | } 98 | 99 | line.push_str(format!("{}", modifier_definition.parameters).as_str()); 100 | 101 | if modifier_definition.visibility != solidity::ast::Visibility::Internal { 102 | line.push_str(format!("{} {}", modifier_definition.parameters, modifier_definition.visibility).as_str()); 103 | } 104 | 105 | if let Some(true) = modifier_definition.is_virtual { 106 | line.push_str(" virtual"); 107 | } 108 | 109 | if let Some(overrides) = modifier_definition.overrides.as_ref() { 110 | line.push_str(format!(" {}", overrides).as_str()); 111 | } 112 | 113 | line.push('`'); 114 | 115 | lines.push(line); 116 | } 117 | } 118 | 119 | if !lines.is_empty() { 120 | println!(); 121 | println!("#### Modifiers:"); 122 | 123 | for line in lines.iter() { 124 | println!("{}", line); 125 | } 126 | 127 | lines.clear(); 128 | } 129 | 130 | // 131 | // Print functions 132 | // 133 | 134 | for definition_node in contract_definition.nodes.iter() { 135 | if let solidity::ast::ContractDefinitionNode::FunctionDefinition(function_definition) = definition_node { 136 | if function_definition.body.is_none() { 137 | continue; 138 | } 139 | 140 | let mut line = String::new(); 141 | 142 | line.push_str(format!("- [ ] `{}", function_definition.kind).as_str()); 143 | 144 | if !function_definition.name.is_empty() { 145 | line.push_str(format!(" {}", function_definition.name).as_str()); 146 | } 147 | 148 | line.push_str(format!("{} {}", function_definition.parameters, function_definition.visibility).as_str()); 149 | 150 | if function_definition.state_mutability != solidity::ast::StateMutability::NonPayable { 151 | line.push_str(format!(" {}", function_definition.state_mutability).as_str()); 152 | } 153 | 154 | if let Some(true) = function_definition.is_virtual { 155 | line.push_str(" virtual"); 156 | } 157 | 158 | if let Some(overrides) = function_definition.overrides.as_ref() { 159 | line.push_str(format!(" {}", overrides).as_str()); 160 | } 161 | 162 | for modifier in function_definition.modifiers.iter() { 163 | line.push_str(format!(" {}", modifier).as_str()); 164 | } 165 | 166 | if !function_definition.return_parameters.parameters.is_empty() { 167 | line.push_str(format!(" returns {}", function_definition.return_parameters).as_str()); 168 | } 169 | 170 | line.push('`'); 171 | 172 | lines.push(line); 173 | } 174 | } 175 | 176 | if !lines.is_empty() { 177 | println!(); 178 | println!("#### Functions:"); 179 | 180 | for line in lines.iter() { 181 | println!("{}", line); 182 | } 183 | 184 | lines.clear(); 185 | } 186 | 187 | println!(); 188 | } 189 | 190 | println!("---"); 191 | println!(); 192 | } 193 | } -------------------------------------------------------------------------------- /solast/src/truffle.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use solidity::ast::*; 3 | 4 | #[derive(Clone, Debug, Deserialize, Serialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct File { 7 | pub contract_name: Option, 8 | // TODO: abi 9 | pub metadata: Option, 10 | pub bytecode: Option, 11 | pub deployed_bytecode: Option, 12 | pub source_map: Option, 13 | pub deployed_source_map: Option, 14 | pub source: Option, 15 | pub source_path: Option, 16 | pub ast: Option, 17 | // TODO: compiler 18 | // TODO: networks 19 | // TODO: schemaVersion 20 | // TODO: updatedAt 21 | // TODO: devdoc 22 | // TODO: userdoc 23 | } 24 | -------------------------------------------------------------------------------- /solidity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solidity" 3 | version = "0.1.0" 4 | authors = ["Camden Smallwood "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | eth-lang-utils = { path = "../eth-lang-utils" } 9 | serde = { version = "1.0", features = ["derive"] } 10 | serde_json = "1.0" 11 | solang-parser = "0.3.0" 12 | yul = { path = "../yul" } 13 | -------------------------------------------------------------------------------- /solidity/src/ast/blocks.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct Block { 9 | pub statements: Vec, 10 | pub src: String, 11 | pub id: NodeID, 12 | } 13 | 14 | impl Display for Block { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | f.write_str("{\n")?; 17 | 18 | for statement in self.statements.iter() { 19 | f.write_fmt(format_args!("\t{};\n", statement))?; 20 | } 21 | 22 | f.write_str("}") 23 | } 24 | } 25 | 26 | pub struct BlockContext<'a, 'b> { 27 | pub source_units: &'a [SourceUnit], 28 | pub current_source_unit: &'a SourceUnit, 29 | pub contract_definition: &'a ContractDefinition, 30 | pub definition_node: &'a ContractDefinitionNode, 31 | pub blocks: &'b mut Vec<&'a Block>, 32 | pub block: &'a Block, 33 | } 34 | -------------------------------------------------------------------------------- /solidity/src/ast/documentation.rs: -------------------------------------------------------------------------------- 1 | use eth_lang_utils::ast::*; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 5 | #[serde(untagged)] 6 | pub enum Documentation { 7 | String(Option), 8 | Structured(Option), 9 | } 10 | 11 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 12 | #[serde(rename_all = "camelCase")] 13 | pub struct StructuredDocumentation { 14 | pub text: String, 15 | pub src: String, 16 | pub id: NodeID, 17 | } 18 | -------------------------------------------------------------------------------- /solidity/src/ast/enumerations.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct EnumValue { 9 | pub name: String, 10 | pub name_location: Option, 11 | pub src: String, 12 | pub id: NodeID, 13 | } 14 | 15 | impl Display for EnumValue { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | f.write_str(self.name.as_str()) 18 | } 19 | } 20 | 21 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 22 | #[serde(rename_all = "camelCase")] 23 | pub struct EnumDefinition { 24 | pub name: String, 25 | pub name_location: Option, 26 | pub members: Vec, 27 | pub canonical_name: Option, 28 | pub src: String, 29 | pub id: NodeID, 30 | } 31 | 32 | impl Display for EnumDefinition { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | f.write_fmt(format_args!("enum {} {{\n", self.name))?; 35 | 36 | for member in self.members.iter() { 37 | f.write_fmt(format_args!("\t{},", member))?; 38 | } 39 | 40 | f.write_str("}") 41 | } 42 | } 43 | 44 | pub struct EnumDefinitionContext<'a> { 45 | pub source_units: &'a [SourceUnit], 46 | pub current_source_unit: &'a SourceUnit, 47 | pub contract_definition: Option<&'a ContractDefinition>, 48 | pub enum_definition: &'a EnumDefinition, 49 | } 50 | -------------------------------------------------------------------------------- /solidity/src/ast/errors.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct ErrorDefinition { 9 | pub documentation: Option, 10 | pub name: String, 11 | pub name_location: Option, 12 | pub parameters: ParameterList, 13 | pub src: String, 14 | pub id: NodeID, 15 | } 16 | 17 | impl Display for ErrorDefinition { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | f.write_fmt(format_args!("error {}{}", self.name, self.parameters)) 20 | } 21 | } 22 | 23 | pub struct ErrorDefinitionContext<'a> { 24 | pub source_units: &'a [SourceUnit], 25 | pub current_source_unit: &'a SourceUnit, 26 | pub contract_definition: Option<&'a ContractDefinition>, 27 | pub error_definition: &'a ErrorDefinition, 28 | } 29 | -------------------------------------------------------------------------------- /solidity/src/ast/events.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct EventDefinition { 9 | pub anonymous: bool, 10 | pub documentation: Option, 11 | pub name: String, 12 | pub name_location: Option, 13 | pub parameters: ParameterList, 14 | pub src: String, 15 | pub id: NodeID, 16 | } 17 | 18 | impl Display for EventDefinition { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | f.write_fmt(format_args!("event {}{}", self.name, self.parameters)) 21 | } 22 | } 23 | 24 | pub struct EventDefinitionContext<'a> { 25 | pub source_units: &'a [SourceUnit], 26 | pub current_source_unit: &'a SourceUnit, 27 | pub contract_definition: &'a ContractDefinition, 28 | pub event_definition: &'a EventDefinition, 29 | } 30 | -------------------------------------------------------------------------------- /solidity/src/ast/functions.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 7 | #[serde(rename_all = "camelCase")] 8 | pub enum FunctionKind { 9 | Constructor, 10 | Function, 11 | Receive, 12 | Fallback, 13 | } 14 | 15 | impl Display for FunctionKind { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | f.write_fmt(format_args!("{}", format!("{:?}", self).to_lowercase())) 18 | } 19 | } 20 | 21 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 22 | #[serde(rename_all = "camelCase")] 23 | pub struct ParameterList { 24 | pub parameters: Vec, 25 | pub src: String, 26 | pub id: NodeID, 27 | } 28 | 29 | impl Display for ParameterList { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | f.write_str("(")?; 32 | 33 | for (i, parameter) in self.parameters.iter().enumerate() { 34 | if i > 0 { 35 | f.write_str(", ")?; 36 | } 37 | 38 | f.write_fmt(format_args!("{}", parameter))?; 39 | } 40 | 41 | f.write_str(")") 42 | } 43 | } 44 | 45 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 46 | #[serde(rename_all = "camelCase")] 47 | pub struct OverrideSpecifier { 48 | pub overrides: Vec, 49 | pub src: String, 50 | pub id: NodeID, 51 | } 52 | 53 | impl Display for OverrideSpecifier { 54 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 55 | f.write_str("override")?; 56 | 57 | if !self.overrides.is_empty() { 58 | f.write_str("(")?; 59 | 60 | for (i, identifier_path) in self.overrides.iter().enumerate() { 61 | if i > 0 { 62 | f.write_str(", ")?; 63 | } 64 | 65 | f.write_fmt(format_args!("{}", identifier_path))?; 66 | } 67 | 68 | f.write_str(")")?; 69 | } 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 76 | #[serde(rename_all = "camelCase")] 77 | pub struct FunctionDefinition { 78 | pub base_functions: Option>, 79 | pub body: Option, 80 | pub documentation: Option, 81 | pub function_selector: Option, 82 | pub implemented: bool, 83 | pub kind: FunctionKind, 84 | pub modifiers: Vec, 85 | pub name: String, 86 | pub name_location: Option, 87 | pub overrides: Option, 88 | pub parameters: ParameterList, 89 | pub return_parameters: ParameterList, 90 | pub scope: NodeID, 91 | pub state_mutability: StateMutability, 92 | pub super_function: Option, 93 | #[serde(rename = "virtual")] 94 | pub is_virtual: Option, 95 | pub visibility: Visibility, 96 | pub src: String, 97 | pub id: NodeID, 98 | } 99 | 100 | impl FunctionDefinition { 101 | pub fn get_assigned_return_variables( 102 | &self, 103 | expression: &Expression, 104 | ) -> Vec { 105 | let mut ids = vec![]; 106 | 107 | match expression { 108 | Expression::Identifier(identifier) => { 109 | if self.return_parameters.parameters.iter().any(|p| p.id == identifier.referenced_declaration) { 110 | ids.push(identifier.referenced_declaration); 111 | } 112 | } 113 | 114 | Expression::Assignment(assignment) => { 115 | ids.extend(self.get_assigned_return_variables(assignment.left_hand_side.as_ref())); 116 | } 117 | 118 | Expression::IndexAccess(index_access) => { 119 | ids.extend(self.get_assigned_return_variables(index_access.base_expression.as_ref())); 120 | } 121 | 122 | Expression::IndexRangeAccess(index_range_access) => { 123 | ids.extend(self.get_assigned_return_variables(index_range_access.base_expression.as_ref())); 124 | } 125 | 126 | Expression::MemberAccess(member_access) => { 127 | ids.extend(self.get_assigned_return_variables(member_access.expression.as_ref())); 128 | } 129 | 130 | Expression::TupleExpression(tuple_expression) => { 131 | for component in tuple_expression.components.iter().flatten() { 132 | ids.extend(self.get_assigned_return_variables(component)); 133 | } 134 | } 135 | 136 | _ => (), 137 | } 138 | 139 | ids 140 | } 141 | } 142 | 143 | impl Display for FunctionDefinition { 144 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 145 | f.write_fmt(format_args!("{}", self.kind))?; 146 | 147 | if !self.name.is_empty() { 148 | f.write_fmt(format_args!(" {}", self.name))?; 149 | } 150 | 151 | f.write_fmt(format_args!("{} {}", self.parameters, self.visibility))?; 152 | 153 | if self.state_mutability != StateMutability::NonPayable { 154 | f.write_fmt(format_args!(" {}", self.state_mutability))?; 155 | } 156 | 157 | if let Some(true) = self.is_virtual { 158 | f.write_str(" virtual")?; 159 | } 160 | 161 | if let Some(overrides) = self.overrides.as_ref() { 162 | f.write_fmt(format_args!(" {}", overrides))?; 163 | } 164 | 165 | for modifier in self.modifiers.iter() { 166 | f.write_fmt(format_args!(" {}", modifier))?; 167 | } 168 | 169 | if !self.return_parameters.parameters.is_empty() { 170 | f.write_fmt(format_args!(" returns {}", self.return_parameters))?; 171 | } 172 | 173 | match self.body.as_ref() { 174 | Some(body) => f.write_fmt(format_args!(" {}", body)), 175 | None => f.write_str(";"), 176 | } 177 | } 178 | } 179 | 180 | pub struct FunctionDefinitionContext<'a> { 181 | pub source_units: &'a [SourceUnit], 182 | pub current_source_unit: &'a SourceUnit, 183 | pub contract_definition: &'a ContractDefinition, 184 | pub definition_node: &'a ContractDefinitionNode, 185 | pub function_definition: &'a FunctionDefinition, 186 | } 187 | -------------------------------------------------------------------------------- /solidity/src/ast/identifiers.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct Identifier { 9 | pub argument_types: Option>, 10 | pub name: String, 11 | pub overloaded_declarations: Vec, 12 | pub referenced_declaration: NodeID, 13 | pub type_descriptions: TypeDescriptions, 14 | pub src: String, 15 | pub id: NodeID, 16 | } 17 | 18 | impl PartialEq for Identifier { 19 | fn eq(&self, other: &Self) -> bool { 20 | self.argument_types.eq(&other.argument_types) && 21 | self.name.eq(&other.name) && 22 | self.overloaded_declarations.eq(&other.overloaded_declarations) && 23 | self.referenced_declaration.eq(&other.referenced_declaration) && 24 | self.type_descriptions.eq(&other.type_descriptions) 25 | } 26 | } 27 | 28 | impl Display for Identifier { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | f.write_str(self.name.as_str()) 31 | } 32 | } 33 | 34 | pub struct IdentifierContext<'a, 'b> { 35 | pub source_units: &'a [SourceUnit], 36 | pub current_source_unit: &'a SourceUnit, 37 | pub contract_definition: &'a ContractDefinition, 38 | pub definition_node: &'a ContractDefinitionNode, 39 | pub blocks: &'b mut Vec<&'a Block>, 40 | pub statement: Option<&'a Statement>, 41 | pub identifier: &'a Identifier, 42 | } 43 | 44 | #[derive(Clone, Debug, Deserialize, Eq, Serialize)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct IdentifierPath { 47 | pub name: String, 48 | pub referenced_declaration: Option, 49 | pub src: String, 50 | pub id: NodeID, 51 | } 52 | 53 | impl PartialEq for IdentifierPath { 54 | fn eq(&self, other: &Self) -> bool { 55 | self.name.eq(&other.name) && self.referenced_declaration.eq(&other.referenced_declaration) 56 | } 57 | } 58 | 59 | impl Display for IdentifierPath { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | f.write_str(self.name.as_str()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /solidity/src/ast/import_directives.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct SymbolAlias { 8 | pub foreign: Expression, 9 | pub local: Option, 10 | pub name_location: Option, 11 | } 12 | 13 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 14 | #[serde(rename_all = "camelCase")] 15 | pub struct ImportDirective { 16 | pub file: String, 17 | pub source_unit: NodeID, 18 | pub scope: NodeID, 19 | pub absolute_path: Option, 20 | pub unit_alias: String, 21 | pub name_location: Option, 22 | pub symbol_aliases: Vec, 23 | pub src: String, 24 | pub id: NodeID, 25 | } 26 | 27 | pub struct ImportDirectiveContext<'a> { 28 | pub source_units: &'a [SourceUnit], 29 | pub current_source_unit: &'a SourceUnit, 30 | pub import_directive: &'a ImportDirective, 31 | } 32 | -------------------------------------------------------------------------------- /solidity/src/ast/literals.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 7 | #[serde(rename_all = "camelCase")] 8 | pub enum LiteralKind { 9 | Bool, 10 | Number, 11 | String, 12 | HexString, 13 | Address, 14 | } 15 | 16 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 17 | #[serde(rename_all = "camelCase")] 18 | pub struct Literal { 19 | pub hex_value: Option, 20 | pub value: Option, 21 | pub subdenomination: Option, 22 | pub kind: LiteralKind, 23 | pub argument_types: Option>, 24 | pub is_constant: bool, 25 | pub is_l_value: bool, 26 | pub is_pure: bool, 27 | pub l_value_requested: bool, 28 | pub type_descriptions: TypeDescriptions, 29 | pub src: String, 30 | pub id: NodeID, 31 | } 32 | 33 | impl Display for Literal { 34 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 35 | if let LiteralKind::String = self.kind { 36 | f.write_str("\"")?; 37 | } 38 | 39 | if let Some(value) = self.value.as_ref() { 40 | f.write_str(value.as_str())?; 41 | } else if let Some(hex_value) = self.hex_value.as_ref() { 42 | f.write_str(hex_value.as_str())?; 43 | } 44 | 45 | if let Some(subdenomination) = self.subdenomination.as_ref() { 46 | subdenomination.fmt(f)?; 47 | } 48 | 49 | if let LiteralKind::String = self.kind { 50 | f.write_str("\"")?; 51 | } 52 | 53 | Ok(()) 54 | } 55 | } 56 | 57 | pub struct LiteralContext<'a, 'b> { 58 | pub source_units: &'a [SourceUnit], 59 | pub current_source_unit: &'a SourceUnit, 60 | pub contract_definition: &'a ContractDefinition, 61 | pub definition_node: &'a ContractDefinitionNode, 62 | pub blocks: &'b mut Vec<&'a Block>, 63 | pub statement: Option<&'a Statement>, 64 | pub literal: &'a Literal, 65 | } 66 | -------------------------------------------------------------------------------- /solidity/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | mod blocks; 2 | mod builder; 3 | mod contracts; 4 | mod documentation; 5 | mod enumerations; 6 | mod errors; 7 | mod events; 8 | mod expressions; 9 | mod functions; 10 | mod identifiers; 11 | mod import_directives; 12 | mod literals; 13 | mod modifiers; 14 | mod pragma_directives; 15 | mod source_units; 16 | mod statements; 17 | mod structures; 18 | mod types; 19 | mod user_defined_value_types; 20 | mod using_for_directives; 21 | mod variables; 22 | mod visitor; 23 | 24 | pub use self::{ 25 | blocks::*, builder::*, contracts::*, documentation::*, enumerations::*, errors::*, events::*, 26 | expressions::*, functions::*, identifiers::*, import_directives::*, literals::*, modifiers::*, 27 | pragma_directives::*, source_units::*, statements::*, structures::*, types::*, 28 | user_defined_value_types::*, using_for_directives::*, variables::*, visitor::*, 29 | }; 30 | -------------------------------------------------------------------------------- /solidity/src/ast/modifiers.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct ModifierDefinition { 9 | pub body: Block, 10 | pub overrides: Option, 11 | pub documentation: Option, 12 | pub name: String, 13 | pub name_location: Option, 14 | pub parameters: ParameterList, 15 | #[serde(rename = "virtual")] 16 | pub is_virtual: Option, 17 | pub visibility: Visibility, 18 | pub src: String, 19 | pub id: NodeID, 20 | } 21 | 22 | impl Display for ModifierDefinition { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | f.write_str("modifier")?; 25 | 26 | if !self.name.is_empty() { 27 | f.write_fmt(format_args!(" {}", self.name))?; 28 | } 29 | 30 | f.write_fmt(format_args!("{}", self.parameters))?; 31 | 32 | if self.visibility != Visibility::Internal { 33 | f.write_fmt(format_args!("{} {}", self.parameters, self.visibility))?; 34 | } 35 | 36 | if let Some(true) = self.is_virtual { 37 | f.write_fmt(format_args!(" virtual"))?; 38 | } 39 | 40 | if let Some(overrides) = self.overrides.as_ref() { 41 | f.write_fmt(format_args!(" {}", overrides))?; 42 | } 43 | 44 | f.write_fmt(format_args!(" {}", self.body)) 45 | } 46 | } 47 | 48 | pub struct ModifierDefinitionContext<'a> { 49 | pub source_units: &'a [SourceUnit], 50 | pub current_source_unit: &'a SourceUnit, 51 | pub contract_definition: &'a ContractDefinition, 52 | pub definition_node: &'a ContractDefinitionNode, 53 | pub modifier_definition: &'a ModifierDefinition, 54 | } 55 | 56 | impl<'a> ModifierDefinitionContext<'a> { 57 | pub fn create_block_context<'b>(&self, block: &'a Block, blocks: &'b mut Vec<&'a Block>) -> BlockContext::<'a, 'b> { 58 | BlockContext { 59 | source_units: self.source_units, 60 | current_source_unit: self.current_source_unit, 61 | contract_definition: self.contract_definition, 62 | definition_node: self.definition_node, 63 | blocks, 64 | block, 65 | } 66 | } 67 | } 68 | 69 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 70 | #[serde(rename_all = "camelCase")] 71 | pub enum ModifierInvocationKind { 72 | ModifierInvocation, 73 | BaseConstructorSpecifier, 74 | } 75 | 76 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 77 | #[serde(rename_all = "camelCase")] 78 | pub struct ModifierInvocation { 79 | pub arguments: Option>, 80 | pub modifier_name: IdentifierPath, 81 | pub src: String, 82 | pub id: NodeID, 83 | pub kind: Option, 84 | } 85 | 86 | impl Display for ModifierInvocation { 87 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 88 | f.write_fmt(format_args!("{}", self.modifier_name))?; 89 | 90 | if let Some(arguments) = self.arguments.as_ref() { 91 | f.write_str("(")?; 92 | 93 | for (i, argument) in arguments.iter().enumerate() { 94 | if i > 0 { 95 | f.write_str(", ")?; 96 | } 97 | 98 | f.write_fmt(format_args!("{}", argument))?; 99 | } 100 | 101 | f.write_str(")")?; 102 | } 103 | 104 | Ok(()) 105 | } 106 | } 107 | 108 | pub struct ModifierInvocationContext<'a> { 109 | pub source_units: &'a [SourceUnit], 110 | pub current_source_unit: &'a SourceUnit, 111 | pub contract_definition: &'a ContractDefinition, 112 | pub definition_node: &'a ContractDefinitionNode, 113 | pub modifier_invocation: &'a ModifierInvocation, 114 | } 115 | -------------------------------------------------------------------------------- /solidity/src/ast/pragma_directives.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct PragmaDirective { 8 | pub literals: Vec, 9 | pub src: String, 10 | pub id: NodeID, 11 | } 12 | 13 | pub struct PragmaDirectiveContext<'a> { 14 | pub source_units: &'a [SourceUnit], 15 | pub current_source_unit: &'a SourceUnit, 16 | pub pragma_directive: &'a PragmaDirective, 17 | } 18 | -------------------------------------------------------------------------------- /solidity/src/ast/structures.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct StructDefinition { 9 | pub name: String, 10 | pub name_location: Option, 11 | pub visibility: Visibility, 12 | pub members: Vec, 13 | pub scope: NodeID, 14 | pub canonical_name: Option, 15 | pub src: String, 16 | pub id: NodeID, 17 | } 18 | 19 | impl Display for StructDefinition { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | f.write_fmt(format_args!("struct {} {{\n", self.name))?; 22 | 23 | for member in self.members.iter() { 24 | f.write_fmt(format_args!("\t{};\n", member))?; 25 | } 26 | 27 | f.write_str("}") 28 | } 29 | } 30 | 31 | pub struct StructDefinitionContext<'a> { 32 | pub source_units: &'a [SourceUnit], 33 | pub current_source_unit: &'a SourceUnit, 34 | pub contract_definition: Option<&'a ContractDefinition>, 35 | pub struct_definition: &'a StructDefinition, 36 | } 37 | -------------------------------------------------------------------------------- /solidity/src/ast/types.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct TypeDescriptions { 9 | pub type_identifier: Option, 10 | pub type_string: Option, 11 | } 12 | 13 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 14 | #[serde(untagged)] 15 | pub enum TypeName { 16 | FunctionTypeName(FunctionTypeName), 17 | ArrayTypeName(ArrayTypeName), 18 | Mapping(Mapping), 19 | UserDefinedTypeName(UserDefinedTypeName), 20 | ElementaryTypeName(ElementaryTypeName), 21 | String(String), 22 | } 23 | 24 | impl Display for TypeName { 25 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 | match self { 27 | TypeName::FunctionTypeName(function_type_name) => function_type_name.type_descriptions.type_string.as_ref().unwrap().fmt(f), 28 | TypeName::ArrayTypeName(array_type_name) => array_type_name.fmt(f), 29 | TypeName::Mapping(mapping) => mapping.fmt(f), 30 | TypeName::UserDefinedTypeName(user_defined_type_name) => user_defined_type_name.fmt(f), 31 | TypeName::ElementaryTypeName(elementary_type_name) => elementary_type_name.fmt(f), 32 | TypeName::String(string) => string.fmt(f), 33 | } 34 | } 35 | } 36 | 37 | #[derive(Clone, Debug, Deserialize, Eq, Serialize)] 38 | #[serde(rename_all = "camelCase")] 39 | pub struct ElementaryTypeName { 40 | pub state_mutability: Option, 41 | pub name: String, 42 | pub type_descriptions: TypeDescriptions, 43 | } 44 | 45 | impl PartialEq for ElementaryTypeName { 46 | fn eq(&self, other: &Self) -> bool { 47 | self.state_mutability.eq(&other.state_mutability) && 48 | self.type_descriptions.eq(&other.type_descriptions) 49 | } 50 | } 51 | 52 | impl Display for ElementaryTypeName { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | f.write_str(self.name.as_str())?; 55 | 56 | if let Some(state_mutability) = self.state_mutability { 57 | if state_mutability != StateMutability::NonPayable { 58 | f.write_fmt(format_args!(" {}", state_mutability))?; 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | } 65 | 66 | #[derive(Clone, Debug, Deserialize, Eq, Serialize)] 67 | #[serde(rename_all = "camelCase")] 68 | pub struct UserDefinedTypeName { 69 | pub path_node: Option, 70 | pub referenced_declaration: NodeID, 71 | pub name: Option, 72 | pub type_descriptions: TypeDescriptions, 73 | } 74 | 75 | impl PartialEq for UserDefinedTypeName { 76 | fn eq(&self, other: &Self) -> bool { 77 | self.referenced_declaration.eq(&other.referenced_declaration) 78 | } 79 | } 80 | 81 | impl Display for UserDefinedTypeName { 82 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 | if let Some(path_node) = self.path_node.as_ref() { 84 | f.write_fmt(format_args!("{}", path_node)) 85 | } else { 86 | f.write_fmt(format_args!("{}", self.name.as_deref().unwrap_or(""))) 87 | } 88 | } 89 | } 90 | 91 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 92 | #[serde(rename_all = "camelCase")] 93 | pub struct FunctionTypeName { 94 | pub visibility: Visibility, 95 | pub state_mutability: StateMutability, 96 | pub parameter_types: ParameterList, 97 | pub return_parameter_types: ParameterList, 98 | pub type_descriptions: TypeDescriptions, 99 | } 100 | 101 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 102 | #[serde(rename_all = "camelCase")] 103 | pub struct ArrayTypeName { 104 | pub base_type: Box, 105 | pub length: Option, 106 | pub type_descriptions: TypeDescriptions, 107 | } 108 | 109 | impl Display for ArrayTypeName { 110 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 111 | f.write_fmt(format_args!("{}", self.base_type))?; 112 | f.write_str("[")?; 113 | 114 | if let Some(length) = self.length.as_ref() { 115 | f.write_fmt(format_args!("{}", length))?; 116 | } 117 | 118 | f.write_str("]") 119 | } 120 | } 121 | 122 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 123 | #[serde(rename_all = "camelCase")] 124 | pub struct Mapping { 125 | pub key_type: Box, 126 | pub value_type: Box, 127 | pub type_descriptions: TypeDescriptions, 128 | } 129 | 130 | impl Display for Mapping { 131 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 132 | f.write_fmt(format_args!("mapping({} => {})", self.key_type, self.value_type)) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /solidity/src/ast/user_defined_value_types.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct UserDefinedValueTypeDefinition { 9 | pub underlying_type: TypeName, 10 | pub name: String, 11 | pub name_location: Option, 12 | pub canonical_name: Option, 13 | pub src: String, 14 | pub id: NodeID, 15 | } 16 | 17 | impl Display for UserDefinedValueTypeDefinition { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | f.write_fmt(format_args!( 20 | "type {} is {}", 21 | self.name, 22 | self.underlying_type, 23 | )) 24 | } 25 | } 26 | 27 | pub struct UserDefinedValueTypeDefinitionContext<'a> { 28 | pub source_units: &'a [SourceUnit], 29 | pub current_source_unit: &'a SourceUnit, 30 | pub contract_definition: Option<&'a ContractDefinition>, 31 | pub user_defined_value_type_definition: &'a UserDefinedValueTypeDefinition, 32 | } 33 | -------------------------------------------------------------------------------- /solidity/src/ast/using_for_directives.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct UsingForDirective { 9 | pub library_name: IdentifierPath, 10 | pub type_name: Option, 11 | pub src: String, 12 | pub id: NodeID, 13 | } 14 | 15 | impl Display for UsingForDirective { 16 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 17 | f.write_fmt(format_args!( 18 | "using {} for {}", 19 | self.library_name, 20 | match self.type_name.as_ref() { 21 | Some(type_name) => format!("{}", type_name), 22 | None => "_".to_string(), 23 | } 24 | )) 25 | } 26 | } 27 | 28 | pub struct UsingForDirectiveContext<'a> { 29 | pub source_units: &'a [SourceUnit], 30 | pub current_source_unit: &'a SourceUnit, 31 | pub contract_definition: &'a ContractDefinition, 32 | pub definition_node: &'a ContractDefinitionNode, 33 | pub using_for_directive: &'a UsingForDirective, 34 | } 35 | -------------------------------------------------------------------------------- /solidity/src/ast/variables.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use eth_lang_utils::ast::*; 3 | use serde::{Deserialize, Serialize}; 4 | use std::fmt::Display; 5 | 6 | #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 7 | #[serde(rename_all = "lowercase")] 8 | pub enum Mutability { 9 | Immutable, 10 | Mutable, 11 | Constant, 12 | } 13 | 14 | impl Display for Mutability { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | f.write_fmt(format_args!("{}", format!("{:?}", self).to_lowercase())) 17 | } 18 | } 19 | 20 | #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 21 | #[serde(rename_all = "lowercase")] 22 | pub enum StateMutability { 23 | NonPayable, 24 | Payable, 25 | View, 26 | Pure, 27 | } 28 | 29 | impl Display for StateMutability { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | f.write_fmt(format_args!("{}", format!("{:?}", self).to_lowercase())) 32 | } 33 | } 34 | 35 | #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 36 | #[serde(rename_all = "lowercase")] 37 | pub enum Visibility { 38 | Public, 39 | Private, 40 | Internal, 41 | External, 42 | } 43 | 44 | impl Display for Visibility { 45 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 46 | f.write_fmt(format_args!("{}", format!("{:?}", self).to_lowercase())) 47 | } 48 | } 49 | 50 | #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord, Hash)] 51 | #[serde(rename_all = "lowercase")] 52 | pub enum StorageLocation { 53 | Default, 54 | Memory, 55 | Calldata, 56 | Storage, 57 | } 58 | 59 | impl Display for StorageLocation { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | f.write_fmt(format_args!("{}", format!("{:?}", self).to_lowercase())) 62 | } 63 | } 64 | 65 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 66 | #[serde(rename_all = "camelCase")] 67 | pub struct VariableDeclaration { 68 | pub base_functions: Option>, 69 | pub constant: bool, 70 | pub documentation: Option, 71 | pub function_selector: Option, 72 | pub indexed: Option, 73 | pub mutability: Option, 74 | pub name: String, 75 | pub name_location: Option, 76 | pub overrides: Option, 77 | pub scope: NodeID, 78 | pub state_variable: bool, 79 | pub storage_location: StorageLocation, 80 | pub type_descriptions: TypeDescriptions, 81 | pub type_name: Option, 82 | pub value: Option, 83 | pub visibility: Visibility, 84 | pub src: String, 85 | pub id: NodeID, 86 | } 87 | 88 | impl Display for VariableDeclaration { 89 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 90 | f.write_fmt(format_args!("{}", self.type_name.as_ref().unwrap()))?; 91 | 92 | if self.storage_location != StorageLocation::Default { 93 | f.write_fmt(format_args!(" {}", self.storage_location))?; 94 | } 95 | 96 | if let Some(mutability) = self.mutability.as_ref() { 97 | if mutability != &Mutability::Mutable { 98 | f.write_fmt(format_args!(" {}", mutability))?; 99 | } 100 | } 101 | 102 | if let Some(true) = self.indexed { 103 | f.write_str(" indexed")?; 104 | } 105 | 106 | if self.state_variable { 107 | f.write_fmt(format_args!(" {}", self.visibility))?; 108 | } 109 | 110 | if !self.name.is_empty() { 111 | f.write_fmt(format_args!(" {}", self.name))?; 112 | } 113 | 114 | if let Some(value) = self.value.as_ref() { 115 | f.write_fmt(format_args!(" = {}", value))?; 116 | } 117 | 118 | Ok(()) 119 | } 120 | } 121 | 122 | pub struct VariableDeclarationContext<'a, 'b> { 123 | pub source_units: &'a [SourceUnit], 124 | pub current_source_unit: &'a SourceUnit, 125 | pub contract_definition: Option<&'a ContractDefinition>, 126 | pub definition_node: Option<&'a ContractDefinitionNode>, 127 | pub blocks: Option<&'b mut Vec<&'a Block>>, 128 | pub variable_declaration: &'a VariableDeclaration, 129 | } 130 | -------------------------------------------------------------------------------- /solidity/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | -------------------------------------------------------------------------------- /yul/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yul" 3 | version = "0.1.0" 4 | authors = ["Camden Smallwood "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | serde = { version = "1.0", features = ["derive"] } 9 | serde_json = "1.0" 10 | eth-lang-utils = { path = "../eth-lang-utils" } 11 | -------------------------------------------------------------------------------- /yul/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | use eth_lang_utils::ast::*; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | 5 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 6 | #[serde(untagged)] 7 | pub enum ExternalReference { 8 | Untagged(ExternalReferenceData), 9 | Tagged(HashMap), 10 | } 11 | 12 | #[derive(Clone, Debug, Eq, Deserialize, Serialize, PartialEq)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct ExternalReferenceData { 15 | declaration: NodeID, 16 | is_offset: bool, 17 | is_slot: bool, 18 | src: String, 19 | value_size: NodeID, 20 | } 21 | 22 | #[derive(Clone, Debug, Eq, Serialize, PartialEq)] 23 | #[serde(untagged)] 24 | pub enum YulExpression { 25 | YulLiteral(YulLiteral), 26 | YulIdentifier(YulIdentifier), 27 | YulFunctionCall(YulFunctionCall), 28 | } 29 | 30 | impl<'de> Deserialize<'de> for YulExpression { 31 | fn deserialize>(deserializer: D) -> Result { 32 | let json = serde_json::Value::deserialize(deserializer)?; 33 | let node_type = json.get("nodeType").unwrap().as_str().unwrap(); 34 | 35 | match node_type { 36 | "YulLiteral" => Ok(YulExpression::YulLiteral(serde_json::from_value(json).unwrap())), 37 | "YulIdentifier" => Ok(YulExpression::YulIdentifier(serde_json::from_value(json).unwrap())), 38 | "YulFunctionCall" => Ok(YulExpression::YulFunctionCall(serde_json::from_value(json).unwrap())), 39 | _ => panic!("Invalid yul expression node type: {node_type}"), 40 | } 41 | } 42 | } 43 | 44 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 45 | #[serde(rename_all = "camelCase")] 46 | pub struct YulLiteral { 47 | pub kind: YulLiteralKind, 48 | pub value: Option, 49 | pub hex_value: Option, 50 | } 51 | 52 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 53 | #[serde(rename_all = "camelCase")] 54 | pub enum YulLiteralKind { 55 | Bool, 56 | Number, 57 | String, 58 | HexString, 59 | Address, 60 | } 61 | 62 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 63 | #[serde(rename_all = "camelCase")] 64 | pub struct YulIdentifier { 65 | pub name: String, 66 | } 67 | 68 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 69 | #[serde(rename_all = "camelCase")] 70 | pub struct YulFunctionCall { 71 | pub function_name: YulIdentifier, 72 | pub arguments: Vec, 73 | } 74 | 75 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 76 | #[serde(rename_all = "camelCase")] 77 | pub struct YulBlock { 78 | pub statements: Vec, 79 | } 80 | 81 | pub struct YulBlockContext<'a, 'b> { 82 | pub yul_blocks: &'b mut Vec<&'a YulBlock>, 83 | pub yul_block: &'a YulBlock, 84 | } 85 | 86 | #[derive(Clone, Debug, Eq, Serialize, PartialEq)] 87 | #[serde(untagged)] 88 | pub enum YulStatement { 89 | YulIf(YulIf), 90 | YulSwitch(YulSwitch), 91 | YulForLoop(YulForLoop), 92 | YulAssignment(YulAssignment), 93 | YulVariableDeclaration(YulVariableDeclaration), 94 | YulExpressionStatement(YulExpressionStatement), 95 | YulFunctionDefinition(YulFunctionDefinition), 96 | YulBlock(YulBlock), 97 | YulLeave, 98 | YulBreak, 99 | YulContinue, 100 | } 101 | 102 | impl<'de> Deserialize<'de> for YulStatement { 103 | fn deserialize>(deserializer: D) -> Result { 104 | let json = serde_json::Value::deserialize(deserializer)?; 105 | let node_type = json.get("nodeType").unwrap().as_str().unwrap(); 106 | 107 | match node_type { 108 | "YulIf" => Ok(YulStatement::YulIf(serde_json::from_value(json).unwrap())), 109 | "YulSwitch" => Ok(YulStatement::YulSwitch(serde_json::from_value(json).unwrap())), 110 | "YulForLoop" => Ok(YulStatement::YulForLoop(serde_json::from_value(json).unwrap())), 111 | "YulAssignment" => Ok(YulStatement::YulAssignment(serde_json::from_value(json).unwrap())), 112 | "YulVariableDeclaration" => Ok(YulStatement::YulVariableDeclaration(serde_json::from_value(json).unwrap())), 113 | "YulExpressionStatement" => Ok(YulStatement::YulExpressionStatement(serde_json::from_value(json).unwrap())), 114 | "YulFunctionDefinition" => Ok(YulStatement::YulFunctionDefinition(serde_json::from_value(json).unwrap())), 115 | "YulBlock" => Ok(YulStatement::YulBlock(serde_json::from_value(json).unwrap())), 116 | "YulLeave" => Ok(YulStatement::YulLeave), 117 | "YulBreak" => Ok(YulStatement::YulBreak), 118 | "YulContinue" => Ok(YulStatement::YulContinue), 119 | _ => panic!("Invalid yul statement node type: {node_type}"), 120 | } 121 | } 122 | } 123 | 124 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 125 | #[serde(rename_all = "camelCase")] 126 | pub struct YulIf { 127 | pub condition: YulExpression, 128 | pub body: YulBlock, 129 | } 130 | 131 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 132 | #[serde(rename_all = "camelCase")] 133 | pub struct YulSwitch { 134 | pub cases: Vec, 135 | pub expression: YulExpression, 136 | } 137 | 138 | #[derive(Clone, Debug, Eq, Serialize, PartialEq)] 139 | #[serde(rename_all = "camelCase")] 140 | pub struct YulCase { 141 | pub body: YulBlock, 142 | pub value: Option, 143 | } 144 | 145 | impl<'de> Deserialize<'de> for YulCase { 146 | fn deserialize>(deserializer: D) -> Result { 147 | let json = serde_json::Value::deserialize(deserializer)?; 148 | let body = json.get("body").unwrap(); 149 | let value = json.get("value").unwrap(); 150 | 151 | Ok(YulCase { 152 | body: serde_json::from_value(body.clone()).unwrap(), 153 | value: if matches!(value.as_str(), Some("default")) { 154 | None 155 | } else { 156 | Some(serde_json::from_value(value.clone()).unwrap()) 157 | }, 158 | }) 159 | } 160 | } 161 | 162 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 163 | #[serde(rename_all = "camelCase")] 164 | pub struct YulForLoop { 165 | pub pre: YulBlock, 166 | pub condition: YulExpression, 167 | pub post: YulBlock, 168 | pub body: YulBlock, 169 | } 170 | 171 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 172 | #[serde(rename_all = "camelCase")] 173 | pub struct YulAssignment { 174 | pub value: YulExpression, 175 | pub variable_names: Vec, 176 | } 177 | 178 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 179 | #[serde(rename_all = "camelCase")] 180 | pub struct YulVariableDeclaration { 181 | pub value: Option, 182 | pub variables: Vec, 183 | } 184 | 185 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 186 | #[serde(rename_all = "camelCase")] 187 | pub struct YulTypedName { 188 | pub r#type: String, 189 | pub name: String, 190 | } 191 | 192 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 193 | #[serde(rename_all = "camelCase")] 194 | pub struct YulExpressionStatement { 195 | pub expression: YulExpression, 196 | } 197 | 198 | #[derive(Clone, Debug, Deserialize, Eq, Serialize, PartialEq)] 199 | #[serde(rename_all = "camelCase")] 200 | pub struct YulFunctionDefinition { 201 | pub name: String, 202 | pub parameters: Option>, 203 | pub return_parameters: Option>, 204 | pub body: YulBlock, 205 | } 206 | -------------------------------------------------------------------------------- /yul/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | --------------------------------------------------------------------------------