├── .github └── workflows │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── Solstat.toml ├── docs ├── Contributing.md ├── identified-optimizations.md ├── identified-quality-assurance.md └── identified-vulnerabilities.md ├── examples └── parse-contract-into-ast.rs └── src ├── analyzer ├── ast.rs ├── mod.rs ├── optimizations │ ├── address_balance.rs │ ├── address_zero.rs │ ├── assign_update_array_value.rs │ ├── bool_equals_bool.rs │ ├── cache_array_length.rs │ ├── constant_variables.rs │ ├── immutable_variables.rs │ ├── increment_decrement.rs │ ├── memory_to_calldata.rs │ ├── mod.rs │ ├── multiple_require.rs │ ├── optimal_comparison.rs │ ├── pack_storage_variables.rs │ ├── pack_struct_variables.rs │ ├── payable_function.rs │ ├── private_constant.rs │ ├── safe_math.rs │ ├── shift_math.rs │ ├── short_revert_string.rs │ ├── solidity_keccak256.rs │ ├── solidity_math.rs │ ├── sstore.rs │ ├── string_errors.rs │ └── template.rs ├── qa │ ├── constructor_order.rs │ ├── mod.rs │ ├── private_func_leading_underscore.rs │ ├── private_vars_leading_underscore.rs │ └── template.rs ├── utils.rs └── vulnerabilities │ ├── divide_before_multiply.rs │ ├── floating_pragma.rs │ ├── mod.rs │ ├── template.rs │ ├── unprotected_selfdestruct.rs │ └── unsafe_erc20_operation.rs ├── lib.rs ├── main.rs ├── opts.rs └── report ├── generation.rs ├── mod.rs ├── optimization_report.rs ├── qa_report.rs ├── report_sections ├── mod.rs ├── optimizations │ ├── address_balance.rs │ ├── address_zero.rs │ ├── assign_update_array_value.rs │ ├── bool_equals_bool.rs │ ├── cache_array_length.rs │ ├── constant_variable.rs │ ├── immutable_variable.rs │ ├── increment_decrement.rs │ ├── memory_to_calldata.rs │ ├── mod.rs │ ├── multiple_require.rs │ ├── optimal_comparison.rs │ ├── overview.rs │ ├── pack_storage_variables.rs │ ├── pack_struct_variables.rs │ ├── payable_function.rs │ ├── private_constant.rs │ ├── safe_math_post_080.rs │ ├── safe_math_pre_080.rs │ ├── shift_math.rs │ ├── short_revert_string.rs │ ├── solidity_keccak256.rs │ ├── solidity_math.rs │ ├── sstore.rs │ ├── string_errors.rs │ └── template.rs ├── qa │ ├── constructor_order.rs │ ├── mod.rs │ ├── overview.rs │ ├── private_func_leading_underscore.rs │ ├── private_vars_leading_underscore.rs │ └── template.rs └── vulnerabilities │ ├── divide_before_multiply.rs │ ├── floating_pragma.rs │ ├── mod.rs │ ├── overview.rs │ ├── template.rs │ ├── unprotected_selfdestruct.rs │ └── unsafe_erc20_operation.rs └── vulnerability_report.rs /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | - development 6 | pull_request: 7 | branches: 8 | - main 9 | - development 10 | 11 | name: Continuous integration 12 | 13 | jobs: 14 | check: 15 | name: Check 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | - uses: actions-rs/cargo@v1 25 | with: 26 | command: check 27 | 28 | test: 29 | name: Test Suite 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: stable 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | command: test 41 | 42 | fmt: 43 | name: Rustfmt 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v2 47 | - uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: stable 51 | override: true 52 | - run: rustup component add rustfmt 53 | - uses: actions-rs/cargo@v1 54 | with: 55 | command: fmt 56 | args: --all -- --check 57 | 58 | # clippy: 59 | # name: Clippy 60 | # runs-on: ubuntu-latest 61 | # steps: 62 | # - uses: actions/checkout@v2 63 | # - uses: actions-rs/toolchain@v1 64 | # with: 65 | # profile: minimal 66 | # toolchain: stable 67 | # override: true 68 | # - run: rustup component add clippy 69 | # - uses: actions-rs/cargo@v1 70 | # with: 71 | # command: clippy 72 | # args: -- -D warnings 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vscode 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solstat" 3 | version = "0.5.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "A Solidity static analyzer to identify contract vulnerabilities and gas efficiencies." 7 | readme = "README.md" 8 | homepage = "https://github.com/0xKitsune/solstat" 9 | repository = "https://github.com/0xKitsune/solstat" 10 | keywords = ["solidity", "static_analysis", "gas", "security", "audit"] 11 | exclude = [ 12 | "target/*", 13 | "example_report.md", 14 | ".github/*", 15 | ".gitignore" 16 | ] 17 | 18 | 19 | [dependencies] 20 | clap = {version= "4.0.15", features = ["derive"]} 21 | solang-parser = "0.1.13" 22 | indicatif = "0.16.2" 23 | regex = "1.6.0" 24 | toml = "0.5.9" 25 | serde = {version= "1.0.147", features = ["derive"]} 26 | colour = "0.6.0" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solstat 2 | A Solidity static analyzer to identify contract vulnerabilities and gas efficiencies. 3 | 4 | ```js 5 | .------. .------. .------. .------. .------. .------. .------. 6 | |S.--. | |O.--. | |L.--. | |S.--. | |T.--. | |A.--. | |T.--. | 7 | | :/\: | | :/\: | | :/\: | | :/\: | | :/\: | | (\/) | | :/\: | 8 | | :\/: | | :\/: | | (__) | | :\/: | | (__) | | :\/: | | (__) | 9 | | '--'S| | '--'O| | '--'L| | '--'S| | '--'T| | '--'A| | '--'T| 10 | `------' `------' `------' `------' `------' `------' `------' 11 | ``` 12 | 13 | # Table of Contents 14 | - [Installation](#installation) 15 | - [Usage](#usage) 16 | - [Identified Issues](https://github.com/0xKitsune/solstat/tree/main/docs) 17 | - [⚡Optimizations](https://github.com/0xKitsune/solstat/blob/main/docs/identified-optimizations.md) 18 | - [🪲Vulnerabilities](https://github.com/0xKitsune/solstat/blob/main/docs/identified-vulnerabilities.md) 19 | - [👍Quality Assurance](https://github.com/0xKitsune/solstat/blob/main/docs/identified-quality-assurance.md) 20 | - [Example Reports](https://github.com/0xKitsune/solstat-reports) 21 | - [Contributing](#contributing) 22 | 23 | 24 |   25 | # Installation 26 | First, make sure that you have [Rust installed](https://www.rust-lang.org/tools/install). Then you can choose either of the installation methods by entering the corresponding command in your terminal below. 27 | 28 |   29 | ### Install from crates.io 30 | ``` 31 | cargo install solstat 32 | ``` 33 | 34 |   35 | ### Install from source 36 | ``` 37 | git clone https://github.com/0xKitsune/solstat && 38 | cd solstat && 39 | cargo install --path . 40 | ``` 41 | 42 |   43 | # Usage 44 | Now that you have solstat involved, you can use the `solstat` command from anywhere in your terminal. By default, solstat looks for a `./contracts` directory and analyzes every file within the folder. If you would like to specify the directory solstat should use, you can pass the `--path` flag (ex. `solstat --path `). 45 | 46 | In the default configuration, solstat runs analysis for every [currently included Optimization, Vulnerability and QA](https://github.com/0xKitsune/solstat#currently-identified-optimizations-vulnerabilities-and-qa), however if you would like to run analysis for select patterns, you can create a `.toml` file for your custom configuration. Check out the [default solstat.toml configuration](https://github.com/0xKitsune/solstat/blob/main/Solstat.toml) for reference. After creating a custom `.toml` file, make sure to pass the `--toml` flag when running solstat (ex. `solstat --toml `). 47 | 48 | Once solstat runs its analysis, a report will be generated and output as `solstat_report.md`. 49 | 50 | At any point you can use `solstat --help` to see a list of all commands and options. 51 | 52 | ``` 53 | Usage: solstat [OPTIONS] 54 | 55 | Options: 56 | -p, --path Path to the directory containing the files solstat will analyze. The default directory is `./contracts` 57 | -t, --toml Path to the toml file containing the solstat configuration when not using the default settings. 58 | -h, --help Print help information 59 | ``` 60 | 61 |   62 | # Contributing 63 | First off, thanks for taking the time to contribute! Contributions are welcomed and greatly appreciated. 64 | 65 | If you are interested in contributing, please check out [Contributing.md](https://github.com/0xKitsune/solstat/blob/main/docs/Contributing.md). 66 | -------------------------------------------------------------------------------- /Solstat.toml: -------------------------------------------------------------------------------- 1 | # Directory that contain contracts to be analyzed 2 | path = './contracts' 3 | 4 | # Optimizations that each contract will be analyzed for 5 | optimizations = ["address_balance", "address_zero", "assign_update_array_value", "cache_array_length", "constant_variables", 6 | "bool_equals_bool", "immutable_variables", "increment_decrement", "memory_to_calldata", "multiple_require", "pack_storage_variables", 7 | "payable_function", "private_constant", "safe_math_pre_080", "safe_math_post_080", "shift_math", "solidity_keccak256", "solidity_math", 8 | "sstore", "string_errors"] 9 | 10 | # Vulnerabilities that each contract will be analyzed for 11 | vulnerabilities = ["unsafe_erc20_operation"] 12 | 13 | # QA that each contract will be analyzed for 14 | qa = [] -------------------------------------------------------------------------------- /docs/identified-optimizations.md: -------------------------------------------------------------------------------- 1 |   2 | ## ⚡Identified Gas Optimizations 3 | Below are the currently identified optimizations that solstat identifies. If you would like to check out a list of patterns that are ready to be implemented and you would like to add them to the repo, you can check out the [Contribution.md](https://github.com/0xKitsune/solstat/blob/main/docs/Contributing.md#potential-optimizations-vulnerability-and-qa-additions)! 4 | 5 | | Optimization | Description | 6 | | ------------------------- | ------------------------------------------------------- | 7 | | address_balance | Use `selfbalance()` instead of `address(this).balance`. | 8 | | address_zero | Use assembly to check for `address(0)`. | 9 | | assign_update_array_value | When updating a value in an array with arithmetic, using `array[index] += amount` is cheaper than `array[index] = array[index] + amount`. This optimization also catches other arithmetic, bitwise and other operations. | 10 | | bool_equals_bool | Instead of `if (x == bool)`, use `if(x)` or when applicable, use assembly with `iszero(iszero(x))`. | 11 | | cache_array_length | Cache array length during for loops. | 12 | | constant_variables | Mark storage variables as `constant` if they never change and are not marked as constants.| 13 | | immutable_variables | Mark storage variables as `immutable` if variables are assigned during deployment and never change afterwards.| 14 | | increment_decrement | Use `unchecked{++i}` instead of `i++`, or `++i` (or use assembly when applicable). This also applies to decrementing as well.| 15 | | memory_to_calldata | Use `calldata` for function arguments marked as `memory` that do not get mutated.| 16 | | multiple_require | Use multiple require() statements instead of require(expression && expression && ...). | 17 | | optimal_comparison | Use strict `>` & `<` operators over `>=` & `<=` operators. | 18 | | pack_storage_variables | Tightly pack storage variables for efficient contract storage. | 19 | | pack_struct_variables | Tightly pack struct variables for efficient contract storage. | 20 | | payable_function | Mark functions as payable (with discretion). | 21 | | private_constant | Mark constant variables in storage as private to save gas. | 22 | | safe_math_post_080 | Identifies when SafeMath is being used if the contract using solidity >= 0.8.0. Using SafeMath when using version >= 0.8.0 is redundant and will incur additional gas costs. | 23 | | safe_math_pre_080 | Identifies when SafeMath is being used if the contract using solidity < 0.8.0. Consider using assembly with overflow/undeflow protection for math (add, sub, mul, div) instead of SafeMath. | 24 | | shift_math | Right shift or Left shift instead of dividing or multiplying by powers of two. | 25 | | short_revert_string | Use revert strings that fit in one word. | 26 | | solidity_keccak256 | Use assembly to hash instead of Solidity. | 27 | | solidity_math | Use assembly for math (add, sub, mul, div). | 28 | | sstore | Use assembly to write storage values. | 29 | | string_error | Use custom errors instead of string error messages for contracts using Solidity version >= 0.8.4.| 30 | -------------------------------------------------------------------------------- /docs/identified-quality-assurance.md: -------------------------------------------------------------------------------- 1 |   2 | ## 👍 Identified QA 3 | Below are the currently identified QA that solstat identifies. If you would like to check out a list of patterns that are ready to be implemented and you would like to add them to the repo, you can check out the [Contribution.md](https://github.com/0xKitsune/solstat/blob/main/docs/Contributing.md#potential-optimizations-vulnerability-and-qa-additions)! 4 | 5 | | Quality Assurance | Description | 6 | | ------------------------- | ------------------------------------------------------- | 7 | | constructor_order | Constructor must be placed before any other function | 8 | | private_func_leading_underscore | Use leading underscore for private functions | 9 | | private_vars_leading_underscore | Use leading underscore for private variables | 10 | -------------------------------------------------------------------------------- /docs/identified-vulnerabilities.md: -------------------------------------------------------------------------------- 1 |   2 | ## 🪲 Identified Vulnerabilities 3 | Below are the currently identified vulnerabilities that solstat identifies. If you would like to check out a list of patterns that are ready to be implemented and you would like to add them to the repo, you can check out the [Contribution.md](https://github.com/0xKitsune/solstat/blob/main/docs/Contributing.md#potential-optimizations-vulnerability-and-qa-additions)! 4 | 5 | | Vulnerability | Description | 6 | | ------------------------- | ------------------------------------------------------- | 7 | | divide_before_multiply | Use multiplication symbol before division symbol | 8 | | floating_pragma | Use locked pragma rather than floating pragma | 9 | | unprotected_selfdestruct | Add sufficient access control to methods that call `selfdestruct` | 10 | | unsafe_erc20_operation | Use `safeTransfer()`, `safeTransferFrom()`, `safeApprove()` instead of ERC20 `transfer()`, `transferFrom()`, `approve()`. | 11 | -------------------------------------------------------------------------------- /examples/parse-contract-into-ast.rs: -------------------------------------------------------------------------------- 1 | use solang_parser; 2 | 3 | fn main() { 4 | let file_contents = r#" 5 | 6 | pragma solidity ^0.8.16; 7 | 8 | contract SimpleStore { 9 | uint x; 10 | 11 | function set(uint newValue) { 12 | x = newValue; 13 | } 14 | 15 | function get() returns (uint) { 16 | return x; 17 | } 18 | } 19 | "#; 20 | 21 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 22 | 23 | println!("{:#?}", source_unit); 24 | } 25 | -------------------------------------------------------------------------------- /src/analyzer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod optimizations; 3 | pub mod qa; 4 | pub mod utils; 5 | pub mod vulnerabilities; 6 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/address_balance.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | //Use selfbalance() instead of address(this).balance() 9 | pub fn address_balance_optimization(source_unit: SourceUnit) -> HashSet { 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | let target_nodes = ast::extract_target_from_node(Target::MemberAccess, source_unit.into()); 13 | 14 | for node in target_nodes { 15 | //We can use unwrap because Target::MemberAccess is an expression 16 | let expression = node.expression().unwrap(); 17 | 18 | if let pt::Expression::MemberAccess(loc, box_expression, identifier) = expression { 19 | if let pt::Expression::FunctionCall(_, box_expression, _) = *box_expression { 20 | if let pt::Expression::Type(_, ty) = *box_expression { 21 | if let pt::Type::Address = ty { 22 | //if address(0x...).balance or address(this).balance 23 | if identifier.name == "balance".to_string() { 24 | optimization_locations.insert(loc); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | 32 | optimization_locations 33 | } 34 | 35 | #[test] 36 | fn test_address_balance_optimization() { 37 | let file_contents = r#" 38 | 39 | contract Contract0 { 40 | function addressInternalBalance(){ 41 | uint256 bal = address(this).balance; 42 | bal++; 43 | } 44 | 45 | function addressExternalBalance(address addr) public { 46 | uint256 bal = address(addr).balance; 47 | bal++; 48 | } 49 | } 50 | 51 | "#; 52 | 53 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 54 | 55 | let optimization_locations = address_balance_optimization(source_unit); 56 | 57 | assert_eq!(optimization_locations.len(), 2) 58 | } 59 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/address_zero.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc, SourceUnit}; 4 | 5 | use crate::analyzer::ast::{self, Target}; 6 | 7 | pub fn address_zero_optimization(source_unit: SourceUnit) -> HashSet { 8 | let mut optimization_locations: HashSet = HashSet::new(); 9 | 10 | let target_nodes = 11 | ast::extract_targets_from_node(vec![Target::Equal, Target::NotEqual], source_unit.into()); 12 | 13 | for node in target_nodes { 14 | //We can use unwrap because Target::Equal and Target::NotEqual are expressions 15 | 16 | let expression = node.expression().unwrap(); 17 | 18 | match expression { 19 | pt::Expression::NotEqual(loc, box_expression, box_expression_1) => { 20 | if check_for_address_zero(box_expression) 21 | || check_for_address_zero(box_expression_1) 22 | { 23 | optimization_locations.insert(loc); 24 | } 25 | } 26 | pt::Expression::Equal(loc, box_expression, box_expression_1) => { 27 | if check_for_address_zero(box_expression) 28 | || check_for_address_zero(box_expression_1) 29 | { 30 | optimization_locations.insert(loc); 31 | } 32 | } 33 | _ => {} 34 | } 35 | } 36 | 37 | optimization_locations 38 | } 39 | 40 | fn check_for_address_zero(box_expression: Box) -> bool { 41 | //create a boolean to determine if address(0) is present 42 | let mut address_zero: bool = false; 43 | 44 | //if the first expression is address(0) 45 | if let pt::Expression::FunctionCall(_, func_call_box_expression, vec_expression) = 46 | *box_expression 47 | { 48 | if let pt::Expression::Type(_, ty) = *func_call_box_expression { 49 | if let pt::Type::Address = ty { 50 | if let pt::Expression::NumberLiteral(_, val, _) = &vec_expression[0] { 51 | if val == "0" { 52 | address_zero = true; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | //return true or false for address_zero 60 | address_zero 61 | } 62 | 63 | #[test] 64 | fn test_address_zero_optimization() { 65 | let file_contents = r#" 66 | 67 | contract Contract0 { 68 | 69 | function ownerNotZero(address _addr) public pure { 70 | require(_addr == address(0), "zero address"); 71 | } 72 | 73 | function ownerNotZero(address _addr) public pure { 74 | require(_addr != address(0), "zero address"); 75 | } 76 | 77 | function ownerNotZero1(address _addr) public pure { 78 | require(address(0) == _addr, "zero address"); 79 | } 80 | 81 | function ownerNotZero1(address _addr) public pure { 82 | require(address(0) != _addr, "zero address"); 83 | } 84 | 85 | } 86 | "#; 87 | 88 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 89 | 90 | let optimization_locations = address_zero_optimization(source_unit); 91 | 92 | assert_eq!(optimization_locations.len(), 4) 93 | } 94 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/bool_equals_bool.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn bool_equals_bool_optimization(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each optimization target identified 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = 14 | ast::extract_targets_from_node(vec![Target::Equal, Target::NotEqual], source_unit.into()); 15 | 16 | //For each target node that was extracted, check for the optimization patterns 17 | for node in target_nodes { 18 | let expression = node.expression().unwrap(); 19 | 20 | match expression { 21 | pt::Expression::NotEqual(loc, box_expression, box_expression_1) => { 22 | if check_for_bool_equals_bool(box_expression, box_expression_1) { 23 | optimization_locations.insert(loc); 24 | } 25 | } 26 | 27 | pt::Expression::Equal(loc, box_expression, box_expression_1) => { 28 | if check_for_bool_equals_bool(box_expression, box_expression_1) { 29 | optimization_locations.insert(loc); 30 | } 31 | } 32 | 33 | _ => {} 34 | } 35 | } 36 | 37 | //Return the identified optimization locations 38 | optimization_locations 39 | } 40 | 41 | fn check_for_bool_equals_bool( 42 | box_expression: Box, 43 | box_expression_1: Box, 44 | ) -> bool { 45 | //create a boolean to determine if address(0) is present 46 | let mut bool_equals_bool: bool = false; 47 | 48 | //if the first expression is true or false 49 | if let pt::Expression::BoolLiteral(_, _) = *box_expression { 50 | bool_equals_bool = true; 51 | } 52 | //if the second expression is true or false 53 | if let pt::Expression::BoolLiteral(_, _) = *box_expression_1 { 54 | bool_equals_bool = true; 55 | } 56 | 57 | //return true or false for bool equals bool 58 | bool_equals_bool 59 | } 60 | 61 | #[test] 62 | fn test_analyze_for_if_bool_equals_bool_optimization() { 63 | let file_contents = r#" 64 | 65 | 66 | contract Contract0 { 67 | 68 | function boolEqualsBool0(bool check) public pure { 69 | if (check == true){ 70 | return; 71 | } 72 | } 73 | 74 | 75 | function boolEqualsBool1(bool check) public pure { 76 | if (check == false){ 77 | return; 78 | } 79 | } 80 | 81 | function boolEqualsBool2(bool check) public pure { 82 | if (false == check){ 83 | return; 84 | } 85 | } 86 | 87 | function boolEqualsBool3(bool check) public pure { 88 | if (true == check){ 89 | return; 90 | } 91 | } 92 | 93 | function boolEqualsBool4(bool check) public pure { 94 | if (check != true){ 95 | return; 96 | } 97 | } 98 | 99 | 100 | function boolEqualsBool5(bool check) public pure { 101 | if (check != false){ 102 | return; 103 | } 104 | } 105 | 106 | function boolEqualsBool6(bool check) public pure { 107 | if (false != check){ 108 | return; 109 | } 110 | } 111 | 112 | function boolEqualsBool7(bool check) public pure { 113 | if (true != check){ 114 | return; 115 | } 116 | } 117 | 118 | } 119 | "#; 120 | 121 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 122 | 123 | let optimization_locations = bool_equals_bool_optimization(source_unit); 124 | 125 | assert_eq!(optimization_locations.len(), 8) 126 | } 127 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/cache_array_length.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn cache_array_length_optimization(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each optimization target identified 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_target_from_node(Target::For, source_unit.into()); 14 | 15 | //For each target node that was extracted, check for the optimization patterns 16 | for node in target_nodes { 17 | //Can unwrap because Target::For will always be a statement 18 | let statement = node.statement().unwrap(); 19 | 20 | if let pt::Statement::For(_, _, option_box_expression, _, _) = statement { 21 | //get all of the .length in the for loop definition 22 | if option_box_expression.is_some() { 23 | let box_expression = option_box_expression.unwrap(); 24 | 25 | let member_access_nodes = 26 | ast::extract_target_from_node(Target::MemberAccess, box_expression.into()); 27 | 28 | for node in member_access_nodes { 29 | //Can unwrap because Target::MemberAccess will always be an expression 30 | let member_access = node.expression().unwrap(); 31 | if let pt::Expression::MemberAccess(loc, _, identifier) = member_access { 32 | if identifier.name == "length" { 33 | optimization_locations.insert(loc); 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | //Return the identified optimization locations 42 | optimization_locations 43 | } 44 | 45 | #[test] 46 | fn test_cache_array_length_optimization() { 47 | let file_contents = r#" 48 | 49 | contract Contract1 { 50 | 51 | 52 | //loop with i++ 53 | function memoryArray(uint256[] memory arr) public { 54 | uint256 j; 55 | for (uint256 i; i < arr.length; i++) { 56 | j = arr[i] + 10; 57 | } 58 | } 59 | 60 | //loop with i++ 61 | function calldataArray(uint256[] calldata arr) public { 62 | uint256 j; 63 | for (uint256 i; i < 100; i++) { 64 | j = arr[i] + arr.length; 65 | } 66 | } 67 | 68 | //loop with i++ 69 | function memoryArray(uint256[] memory arr) public { 70 | uint256 j; 71 | for (uint256 i; arr.length<1000; i++) { 72 | arr[i] = 10; 73 | } 74 | } 75 | 76 | } 77 | "#; 78 | 79 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 80 | 81 | let optimization_locations = cache_array_length_optimization(source_unit); 82 | 83 | assert_eq!(optimization_locations.len(), 2) 84 | } 85 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/increment_decrement.rs: -------------------------------------------------------------------------------- 1 | use solang_parser::pt::{self, Expression, Loc}; 2 | use solang_parser::{self, pt::SourceUnit}; 3 | use std::collections::HashSet; 4 | 5 | use crate::analyzer::ast::Node; 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn increment_decrement_optimization(source_unit: SourceUnit) -> HashSet { 9 | let mut optimization_locations: HashSet = HashSet::new(); 10 | 11 | //Get all increment/decrement expressions in unchecked blocks so that the analyzer does not mark these as optimization targets 12 | let block_nodes = ast::extract_target_from_node(Target::Block, source_unit.clone().into()); 13 | let mut unchecked_locations: HashSet = HashSet::new(); 14 | for node in block_nodes { 15 | if let pt::Statement::Block { 16 | loc: _, 17 | unchecked, 18 | statements, 19 | } = node.statement().unwrap() 20 | { 21 | if unchecked { 22 | for statement in statements { 23 | unchecked_locations 24 | .extend(extract_pre_increment_pre_decrement(statement.into())); 25 | } 26 | } 27 | } 28 | } 29 | 30 | //Get all increment / decrement locations 31 | let locations = extract_increment_decrement(source_unit.into()); 32 | 33 | for loc in locations { 34 | if !unchecked_locations.contains(&loc) { 35 | optimization_locations.insert(loc); 36 | } 37 | } 38 | 39 | optimization_locations 40 | } 41 | 42 | pub fn extract_increment_decrement(node: Node) -> HashSet { 43 | let mut locations: HashSet = HashSet::new(); 44 | 45 | let target_nodes = ast::extract_targets_from_node( 46 | vec![ 47 | Target::PreIncrement, 48 | Target::PreDecrement, 49 | Target::PostIncrement, 50 | Target::PostDecrement, 51 | ], 52 | node, 53 | ); 54 | 55 | for node in target_nodes { 56 | //We can use expect because all targets are expressions 57 | let expression = node.expression().unwrap(); 58 | 59 | match expression { 60 | Expression::PreIncrement(loc, _) => { 61 | locations.insert(loc); 62 | } 63 | Expression::PreDecrement(loc, _) => { 64 | locations.insert(loc); 65 | } 66 | Expression::PostIncrement(loc, _) => { 67 | locations.insert(loc); 68 | } 69 | Expression::PostDecrement(loc, _) => { 70 | locations.insert(loc); 71 | } 72 | 73 | _ => {} 74 | } 75 | } 76 | locations 77 | } 78 | 79 | pub fn extract_pre_increment_pre_decrement(node: Node) -> HashSet { 80 | let mut locations: HashSet = HashSet::new(); 81 | 82 | let target_nodes = 83 | ast::extract_targets_from_node(vec![Target::PreIncrement, Target::PreDecrement], node); 84 | 85 | for node in target_nodes { 86 | //We can use expect because all targets are expressions 87 | let expression = node.expression().unwrap(); 88 | 89 | match expression { 90 | Expression::PreIncrement(loc, _) => { 91 | locations.insert(loc); 92 | } 93 | Expression::PreDecrement(loc, _) => { 94 | locations.insert(loc); 95 | } 96 | 97 | _ => {} 98 | } 99 | } 100 | locations 101 | } 102 | 103 | #[test] 104 | fn test_increment_optimization() { 105 | let file_contents = r#" 106 | 107 | contract Contract0 { 108 | function iPlusPlus(){ 109 | uint256 i = 0; 110 | i++; 111 | } 112 | 113 | function plusPlusI() public { 114 | uint256 i = 0; 115 | ++i; 116 | 117 | for (uint256 j = 0; j < numNfts; ) { 118 | bytes32 hash = keccak256(abi.encode(ORDER_ITEM_HASH, nfts[i].collection, _tokensHash(nfts[i].tokens))); 119 | hashes[i] = hash; 120 | unchecked { 121 | ++j; 122 | } 123 | } 124 | 125 | unchecked{ 126 | i++; 127 | } 128 | } 129 | 130 | } 131 | 132 | "#; 133 | 134 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 135 | 136 | let optimization_locations = increment_decrement_optimization(source_unit); 137 | 138 | assert_eq!(optimization_locations.len(), 3) 139 | } 140 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/memory_to_calldata.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn memory_to_calldata_optimization(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each optimization target identified 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = 14 | ast::extract_target_from_node(Target::FunctionDefinition, source_unit.into()); 15 | 16 | //For each target node that was extracted, check for the optimization patterns 17 | for node in target_nodes { 18 | //extract the box function definition depending on if the node is a contract part or a source unit part 19 | let box_function_definition = if node.is_contract_part() { 20 | let contract_part = node.contract_part().unwrap(); 21 | 22 | if let pt::ContractPart::FunctionDefinition(box_function_definition) = contract_part { 23 | box_function_definition 24 | } else { 25 | continue; 26 | } 27 | } else { 28 | //if the Function definition is not a contract part, then it must be a source unit part 29 | let contract_part = node.source_unit_part().unwrap(); 30 | 31 | if let pt::SourceUnitPart::FunctionDefinition(box_function_definition) = contract_part { 32 | box_function_definition 33 | } else { 34 | continue; 35 | } 36 | }; 37 | 38 | let mut memory_args = get_function_definition_memory_args(box_function_definition.clone()); 39 | 40 | // Constructor can only use `memory` 41 | if box_function_definition.ty == pt::FunctionTy::Constructor { 42 | continue; 43 | } 44 | 45 | if box_function_definition.body.is_some() { 46 | let assign_nodes = ast::extract_target_from_node( 47 | Target::Assign, 48 | box_function_definition.body.unwrap().into(), 49 | ); 50 | 51 | for assign_node in assign_nodes { 52 | //Can unwrap because Target::Assign will always be an expression 53 | let expression = assign_node.expression().unwrap(); 54 | 55 | if let pt::Expression::Assign(_, box_expression, _) = expression { 56 | //check if the left hand side is a variable 57 | match *box_expression { 58 | //if assignment is to variable 59 | pt::Expression::Variable(identifier) => { 60 | memory_args.remove(&identifier.name); 61 | } 62 | 63 | //if assignment is array subscript 64 | pt::Expression::ArraySubscript(_, arr_subscript_box_expression, _) => { 65 | if let pt::Expression::Variable(identifier) = 66 | *arr_subscript_box_expression 67 | { 68 | //remove the variable name from the memory_args hashmap 69 | memory_args.remove(&identifier.name); 70 | } 71 | } 72 | 73 | _ => {} 74 | } 75 | } 76 | } 77 | 78 | //for each arg in memory args left, add it to the optimization locations 79 | for (_, loc) in memory_args { 80 | optimization_locations.insert(loc); 81 | } 82 | } 83 | } 84 | 85 | //Return the identified optimization locations 86 | optimization_locations 87 | } 88 | 89 | fn get_function_definition_memory_args( 90 | function_definition: Box, 91 | ) -> HashMap { 92 | let mut memory_args: HashMap = HashMap::new(); 93 | for option_param in function_definition.params { 94 | if option_param.1.is_some() { 95 | let param = option_param.1.unwrap(); 96 | 97 | if param.storage.is_some() { 98 | let storage_location = param.storage.unwrap(); 99 | 100 | if let pt::StorageLocation::Memory(loc) = storage_location { 101 | if param.name.is_some() { 102 | let name = param.name.unwrap(); 103 | 104 | memory_args.insert(name.name.clone(), loc); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | memory_args 112 | } 113 | 114 | #[test] 115 | fn test_memory_to_calldata_optimization() { 116 | let file_contents = r#" 117 | 118 | contract Contract1 { 119 | 120 | constructor(uint256[] memory arr){ 121 | uint256 j; 122 | for (uint256 i; i < arr.length; i++) { 123 | j = arr[i] + 10; 124 | } 125 | } 126 | 127 | //loop with i++ 128 | function memoryArray(uint256[] memory arr) public { 129 | uint256 j; 130 | for (uint256 i; i < arr.length; i++) { 131 | j = arr[i] + 10; 132 | } 133 | } 134 | 135 | //loop with i++ 136 | function calldataArray(uint256[] calldata arr) public { 137 | uint256 j; 138 | for (uint256 i; i < arr.length; i++) { 139 | j = arr[i] + 10; 140 | } 141 | } 142 | 143 | //loop with i++ 144 | function memoryArray2(uint256[] memory arr) public { 145 | uint256 j; 146 | for (uint256 i; i < arr.length; i++) { 147 | j = arr[i] + 10; 148 | arr[i] = j + 10; 149 | } 150 | } 151 | 152 | //loop with i++ 153 | function memoryBytes(bytes memory byteArr) public { 154 | bytes j; 155 | for (uint256 i; i < arr.length; i++) { 156 | j = byteArr; 157 | } 158 | } 159 | 160 | //loop with i++ 161 | function calldataBytes(bytes calldata byteArr) public { 162 | bytes j; 163 | for (uint256 i; i < arr.length; i++) { 164 | j = byteArr; 165 | } 166 | } 167 | 168 | 169 | //loop with i++ 170 | function memoryBytes1(bytes memory byteArr) public { 171 | bytes j; 172 | for (uint256 i; i < arr.length; i++) { 173 | byteArr = j; 174 | } 175 | } 176 | 177 | } 178 | "#; 179 | 180 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 181 | 182 | let optimization_locations = memory_to_calldata_optimization(source_unit); 183 | assert_eq!(optimization_locations.len(), 2) 184 | } 185 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/multiple_require.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{Expression, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | //Use multiple require statements instead of one single require statement with multiple conditions 9 | pub fn multiple_require_optimization(source_unit: SourceUnit) -> HashSet { 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | let target_nodes = ast::extract_target_from_node(Target::FunctionCall, source_unit.into()); 13 | 14 | for node in target_nodes { 15 | //We can use unwrap because Target::FunctionCall is an expression 16 | let expression = node.expression().unwrap(); 17 | 18 | if let Expression::FunctionCall(loc, function_identifier, function_call_expressions) = 19 | expression 20 | { 21 | //if the function call identifier is a variable 22 | if let Expression::Variable(identifier) = *function_identifier { 23 | //if the identifier name is "require" 24 | if identifier.name == "require".to_string() { 25 | //for each expression in the function call expressions 26 | for func_call_expression in function_call_expressions { 27 | //if there is an and expression (ie. &&) 28 | if let Expression::And(_, _, _) = func_call_expression { 29 | //add the location to the list of optimization locations 30 | optimization_locations.insert(loc); 31 | continue; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | optimization_locations 40 | } 41 | 42 | #[test] 43 | fn test_multiple_require_optimization() { 44 | let file_contents = r#" 45 | contract Contract0 { 46 | function addressInternalBalance() public returns (uint256) { 47 | 48 | uint256 a = 100; 49 | uint256 b = 100; 50 | uint256 c = 100; 51 | 52 | require(true, "some message"); 53 | 54 | require(true && a==b, "some message"); 55 | require(true && a==b && b==c, "thing"); 56 | 57 | return address(this).balance; 58 | 59 | 60 | } 61 | } 62 | "#; 63 | 64 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 65 | 66 | let optimization_locations = multiple_require_optimization(source_unit); 67 | 68 | assert_eq!(optimization_locations.len(), 2) 69 | } 70 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/optimal_comparison.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn optimal_comparison_optimization(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each optimization target identified 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_targets_from_node( 14 | vec![Target::MoreEqual, Target::LessEqual], 15 | source_unit.into(), 16 | ); 17 | 18 | //For each target node that was extracted, check for the optimization patterns 19 | for node in target_nodes { 20 | //We can use unwrap because Target::MemberAccess is an expression 21 | let expression = node.expression().unwrap(); 22 | 23 | match expression { 24 | // >= operator 25 | pt::Expression::MoreEqual(loc, _box_expression_0, _box_expression_1) => { 26 | optimization_locations.insert(loc); 27 | } 28 | 29 | // <= operator 30 | pt::Expression::LessEqual(loc, _box_expression_0, _box_expression_1) => { 31 | optimization_locations.insert(loc); 32 | } 33 | 34 | _ => {} 35 | } 36 | } 37 | 38 | //Return the identified optimization locations 39 | optimization_locations 40 | } 41 | 42 | #[test] 43 | fn test_optimal_comparison_optimization() { 44 | let file_contents = r#" 45 | 46 | contract Contract0 { 47 | function greaterThanOrEqualTo(uint256 a, uint256 b) public pure { 48 | return a >= b; 49 | } 50 | 51 | function lessThanOrEqualTo(uint256 a, uint256 b) public pure { 52 | return a <= b; 53 | } 54 | } 55 | "#; 56 | 57 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 58 | 59 | let optimization_locations = optimal_comparison_optimization(source_unit); 60 | assert_eq!(optimization_locations.len(), 2) 61 | } 62 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/pack_storage_variables.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | use crate::analyzer::utils; 8 | 9 | pub fn pack_storage_variables_optimization(source_unit: SourceUnit) -> HashSet { 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | let target_nodes = 13 | ast::extract_target_from_node(Target::ContractDefinition, source_unit.into()); 14 | 15 | for node in target_nodes { 16 | let source_unit_part = node.source_unit_part().unwrap(); 17 | 18 | if let pt::SourceUnitPart::ContractDefinition(contract_definition) = source_unit_part { 19 | let mut variable_sizes: Vec = vec![]; 20 | 21 | for part in contract_definition.clone().parts { 22 | if let pt::ContractPart::VariableDefinition(box_variable_definition) = part { 23 | variable_sizes.push(utils::get_type_size(box_variable_definition.ty)); 24 | } 25 | } 26 | 27 | //Cache a version of variable sizes that is unordered 28 | let unordered_variable_sizes = variable_sizes.clone(); 29 | 30 | //Sort the variable sizes 31 | variable_sizes.sort(); 32 | 33 | //If the ordered version is smaller than the unordered, add loc 34 | if utils::storage_slots_used(unordered_variable_sizes) 35 | > utils::storage_slots_used(variable_sizes) 36 | { 37 | optimization_locations.insert(contract_definition.loc); 38 | } 39 | } 40 | } 41 | 42 | optimization_locations 43 | } 44 | 45 | #[test] 46 | fn test_pack_storage_variables_optimization() { 47 | // Optimal packing 48 | let contract = r#" 49 | contract Contract { 50 | uint256 num0; 51 | uint256 num1; 52 | uint256 num2; 53 | bool bool0; 54 | bool bool1; 55 | } 56 | "#; 57 | 58 | let source_unit = solang_parser::parse(contract, 0).unwrap().0; 59 | let optimization_locations = pack_storage_variables_optimization(source_unit); 60 | assert_eq!(optimization_locations.len(), 0); 61 | 62 | // Cannot pack better, 2x bytes24 don't fit in a slot 63 | let contract = r#" 64 | contract Contract { 65 | bytes24 b0; 66 | uint256 num0; 67 | bytes24 b1; 68 | } 69 | "#; 70 | 71 | let source_unit = solang_parser::parse(contract, 0).unwrap().0; 72 | let optimization_locations = pack_storage_variables_optimization(source_unit); 73 | assert_eq!(optimization_locations.len(), 0); 74 | 75 | // Cannot pack better, bool are stored with uint8 so cannot move bo1 76 | let contract = r#" 77 | contract Contract { 78 | bytes28 b0; 79 | uint8 num0; 80 | uint8 num1; 81 | uint8 num2; 82 | bool bo0; 83 | uint256 num3; 84 | bool bo1; 85 | } 86 | "#; 87 | 88 | let source_unit = solang_parser::parse(contract, 0).unwrap().0; 89 | let optimization_locations = pack_storage_variables_optimization(source_unit); 90 | assert_eq!(optimization_locations.len(), 0); 91 | 92 | // Suboptimal, can be packed better 93 | let contract = r#" 94 | contract Contract { 95 | uint256 num0; 96 | uint256 num1; 97 | bool bool0; 98 | uint256 num2; 99 | bool bool1; 100 | } 101 | "#; 102 | 103 | let source_unit = solang_parser::parse(contract, 0).unwrap().0; 104 | let optimization_locations = pack_storage_variables_optimization(source_unit); 105 | assert_eq!(optimization_locations.len(), 1); 106 | 107 | // Suboptimal, can be packed better (owner,bool,num0); 108 | let contract = r#" 109 | contract Contract { 110 | address owner; 111 | uint256 num0; 112 | bool bool0; 113 | } 114 | "#; 115 | 116 | let source_unit = solang_parser::parse(contract, 0).unwrap().0; 117 | let optimization_locations = pack_storage_variables_optimization(source_unit); 118 | assert_eq!(optimization_locations.len(), 1); 119 | 120 | // Suboptimal, can be packed better (owner,num1,b0,num0) 121 | let contract = r#" 122 | contract Contract { 123 | address owner; // 160 bits 124 | uint256 num0; // 256 bits 125 | bytes4 b0; // 32 bits 126 | uint64 num1; // 64 bits 127 | } 128 | "#; 129 | 130 | let source_unit = solang_parser::parse(contract, 0).unwrap().0; 131 | let optimization_locations = pack_storage_variables_optimization(source_unit); 132 | assert_eq!(optimization_locations.len(), 1); 133 | } 134 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/pack_struct_variables.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{ContractPart, Loc, StructDefinition}; 4 | use solang_parser::{self, pt::SourceUnit, pt::SourceUnitPart}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | use crate::analyzer::utils; 8 | 9 | ///Identifiy opportunities to pack structs to save gas 10 | pub fn pack_struct_variables_optimization(source_unit: SourceUnit) -> HashSet { 11 | let mut optimization_locations: HashSet = HashSet::new(); 12 | 13 | let target_nodes = ast::extract_target_from_node(Target::StructDefinition, source_unit.into()); 14 | 15 | for node in target_nodes { 16 | if node.is_source_unit_part() { 17 | if let SourceUnitPart::StructDefinition(struct_definition) = 18 | node.source_unit_part().unwrap() 19 | { 20 | let struct_location = struct_definition.loc; 21 | if struct_can_be_packed(*struct_definition) { 22 | optimization_locations.insert(struct_location); 23 | } 24 | } 25 | } else if node.is_contract_part() { 26 | if let ContractPart::StructDefinition(struct_definition) = node.contract_part().unwrap() 27 | { 28 | let struct_location = struct_definition.loc; 29 | if struct_can_be_packed(*struct_definition) { 30 | optimization_locations.insert(struct_location); 31 | } 32 | } 33 | } 34 | } 35 | optimization_locations 36 | } 37 | 38 | fn struct_can_be_packed(struct_definition: StructDefinition) -> bool { 39 | let mut variable_sizes: Vec = vec![]; 40 | 41 | for variable_declaration in struct_definition.fields { 42 | variable_sizes.push(utils::get_type_size(variable_declaration.ty)); 43 | } 44 | 45 | //create an unordered list of variable sizes 46 | let unordered_variable_sizes = variable_sizes.clone(); 47 | 48 | //Sort the variable sizes 49 | variable_sizes.sort(); 50 | 51 | //If the ordered version is smaller than the unordered 52 | utils::storage_slots_used(unordered_variable_sizes) > utils::storage_slots_used(variable_sizes) 53 | } 54 | 55 | #[test] 56 | fn test_pack_struct_variables_optimization() { 57 | let file_contents = r#" 58 | 59 | //should not match 60 | struct Ex { 61 | uint256 spotPrice; 62 | uint128 res0; 63 | uint128 res1; 64 | } 65 | 66 | //should match 67 | struct Ex1 { 68 | bool isUniV2; 69 | bytes32 salt; 70 | bytes16 initBytecode; 71 | } 72 | 73 | 74 | contract OrderRouter { 75 | 76 | 77 | //should not match 78 | struct Ex2 { 79 | bool isUniV2; 80 | address factoryAddress; 81 | bytes16 initBytecode; 82 | } 83 | 84 | //should match 85 | struct Ex3 { 86 | bool isUniV2; 87 | bytes32 salt; 88 | bytes16 initBytecode; 89 | } 90 | 91 | //should not match 92 | struct Ex4 { 93 | bytes16 initBytecode; 94 | bool isUniV2; 95 | address factoryAddress; 96 | } 97 | 98 | //should not match 99 | struct Ex5 { 100 | bool isUniV2; 101 | bytes16 initBytecode; 102 | address factoryAddress; 103 | } 104 | 105 | //should match 106 | struct Ex6 { 107 | uint128 thing3; 108 | uint256 thing1; 109 | uint128 thing2; 110 | } 111 | 112 | // Should match 113 | struct Ex7 { 114 | address owner; // 160 bits 115 | uint256 num0; // 256 bits 116 | bytes4 b0; // 32 bits 117 | uint64 num1; // 64 bits 118 | } 119 | } 120 | "#; 121 | 122 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 123 | 124 | let optimization_locations = pack_struct_variables_optimization(source_unit); 125 | 126 | assert_eq!(optimization_locations.len(), 4) 127 | } 128 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/payable_function.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, ContractPart, Expression, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn payable_function_optimization(source_unit: SourceUnit) -> HashSet { 9 | let mut optimization_locations: HashSet = HashSet::new(); 10 | 11 | let contract_definition_nodes = 12 | ast::extract_target_from_node(Target::ContractDefinition, source_unit.clone().into()); 13 | 14 | for contract_definition_node in contract_definition_nodes { 15 | let target_nodes = ast::extract_target_from_node( 16 | Target::FunctionDefinition, 17 | contract_definition_node.clone().into(), 18 | ); 19 | 20 | for node in target_nodes { 21 | //We can use unwrap because Target::FunctionDefinition is a contract_part 22 | let contract_part = node.contract_part().unwrap(); 23 | 24 | if let pt::ContractPart::FunctionDefinition(box_function_definition) = contract_part { 25 | //if there is function body 26 | if box_function_definition.body.is_some() { 27 | if box_function_definition.attributes.len() > 0 { 28 | let mut payable = false; 29 | let mut public_or_external = false; 30 | 31 | for attr in box_function_definition.attributes { 32 | match attr { 33 | // Visi 34 | pt::FunctionAttribute::Visibility(visibility) => match visibility { 35 | pt::Visibility::External(_) => { 36 | public_or_external = true; 37 | } 38 | pt::Visibility::Public(_) => { 39 | public_or_external = true; 40 | } 41 | _ => {} 42 | }, 43 | pt::FunctionAttribute::Mutability(mutability) => { 44 | if let pt::Mutability::Payable(_) = mutability { 45 | payable = true; 46 | } 47 | } 48 | _ => {} 49 | } 50 | } 51 | 52 | //if the function is public or external, and it is not marked as payable 53 | if public_or_external && !payable { 54 | //insert the loc of the function definition into optimization locations 55 | optimization_locations.insert(box_function_definition.loc); 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | optimization_locations 64 | } 65 | 66 | #[test] 67 | fn test_payable_function_optimization() { 68 | let file_contents = r#" 69 | 70 | 71 | contract Contract0 { 72 | 73 | function div2(uint256 a, uint256 b) public pure { 74 | 75 | } 76 | 77 | function mul2(uint256 a, uint256 b) external view { 78 | 79 | } 80 | } 81 | "#; 82 | 83 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 84 | 85 | let optimization_locations = payable_function_optimization(source_unit); 86 | assert_eq!(optimization_locations.len(), 2) 87 | } 88 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/private_constant.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc, SourceUnit}; 4 | 5 | use crate::analyzer::utils; 6 | 7 | pub fn private_constant_optimization(source_unit: SourceUnit) -> HashSet { 8 | let mut optimization_locations: HashSet = HashSet::new(); 9 | 10 | let storage_variables = utils::get_32_byte_storage_variables(source_unit.clone(), false, true); 11 | 12 | for (_, variable_data) in storage_variables { 13 | let (option_variable_attributes, loc) = variable_data; 14 | 15 | if option_variable_attributes.is_some() { 16 | let variable_attributes = option_variable_attributes.unwrap(); 17 | 18 | let mut is_constant = false; 19 | let mut is_private = false; 20 | 21 | for variable_attribute in variable_attributes { 22 | match variable_attribute { 23 | pt::VariableAttribute::Constant(_) => { 24 | is_constant = true; 25 | } 26 | 27 | pt::VariableAttribute::Visibility(visibility) => match visibility { 28 | pt::Visibility::Private(_) => is_private = true, 29 | _ => {} 30 | }, 31 | 32 | _ => {} 33 | } 34 | } 35 | 36 | if is_constant && !is_private { 37 | optimization_locations.insert(loc); 38 | } 39 | } 40 | } 41 | 42 | optimization_locations 43 | } 44 | 45 | #[test] 46 | fn test_private_constant_optimization() { 47 | let file_contents = r#" 48 | 49 | contract Contract0 { 50 | 51 | uint256 constant public x = 100; 52 | uint256 constant private y = 100; 53 | uint256 constant z = 100; 54 | 55 | 56 | function addPublicConstant(uint256 a) external pure returns (uint256) { 57 | return a + x; 58 | } 59 | 60 | 61 | function addPrivateConstant(uint256 a) external pure returns (uint256) { 62 | return a +x; 63 | } 64 | } 65 | 66 | "#; 67 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 68 | let optimization_locations = private_constant_optimization(source_unit); 69 | assert_eq!(optimization_locations.len(), 2) 70 | } 71 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/safe_math.rs: -------------------------------------------------------------------------------- 1 | use solang_parser::pt::{self, Expression, Loc, SourceUnit, SourceUnitPart}; 2 | use std::collections::HashSet; 3 | 4 | use crate::analyzer::{ 5 | ast::{self, Node, Target}, 6 | utils, 7 | }; 8 | 9 | pub fn safe_math_pre_080_optimization(source_unit: SourceUnit) -> HashSet { 10 | safe_math_optimization(source_unit, true) 11 | } 12 | 13 | pub fn safe_math_post_080_optimization(source_unit: SourceUnit) -> HashSet { 14 | safe_math_optimization(source_unit, false) 15 | } 16 | 17 | pub fn safe_math_optimization(source_unit: SourceUnit, pre_080: bool) -> HashSet { 18 | let mut optimization_locations: HashSet = HashSet::new(); 19 | 20 | let solidity_version = utils::get_solidity_version_from_source_unit(source_unit.clone()) 21 | .expect("Could not extract solidity version from source unit"); 22 | 23 | if (pre_080 && solidity_version.1 < 8) || (!pre_080 && solidity_version.1 >= 8) { 24 | //if using safe math 25 | if check_if_using_safe_math(source_unit.clone()) { 26 | //get all locations that safe math functions are used 27 | optimization_locations.extend(parse_contract_for_safe_math_functions(source_unit)); 28 | } 29 | } 30 | 31 | optimization_locations 32 | } 33 | 34 | fn check_if_using_safe_math(source_unit: SourceUnit) -> bool { 35 | let mut using_safe_math: bool = false; 36 | 37 | let target_nodes = ast::extract_target_from_node(Target::Using, source_unit.into()); 38 | 39 | for node in target_nodes { 40 | match node { 41 | Node::SourceUnitPart(source_unit_part) => { 42 | if let pt::SourceUnitPart::Using(box_using) = source_unit_part { 43 | if let pt::UsingList::Library(identifier_path) = box_using.list { 44 | for identifier in identifier_path.identifiers { 45 | if identifier.name == "SafeMath" { 46 | using_safe_math = true; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | Node::ContractPart(contract_part) => { 54 | if let pt::ContractPart::Using(box_using) = contract_part { 55 | if let pt::UsingList::Library(identifier_path) = box_using.list { 56 | for identifier in identifier_path.identifiers { 57 | if identifier.name == "SafeMath" { 58 | using_safe_math = true; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | _ => {} 66 | } 67 | } 68 | 69 | using_safe_math 70 | } 71 | 72 | fn parse_contract_for_safe_math_functions(source_unit: SourceUnit) -> HashSet { 73 | let mut optimization_locations: HashSet = HashSet::new(); 74 | 75 | let target_nodes = ast::extract_target_from_node(Target::FunctionCall, source_unit.into()); 76 | 77 | for node in target_nodes { 78 | //Can use unwrap because Target::FunctionCall.expression() will always be Some(expression) 79 | let expression = node.expression().unwrap(); 80 | 81 | //if the expression is a function call 82 | if let Expression::FunctionCall(_, function_identifier, _) = expression { 83 | //if the function call identifier is a variable 84 | if let Expression::MemberAccess(loc, _, identifier) = *function_identifier { 85 | //if the identifier name is add, sub, mul or div 86 | if identifier.name == "add".to_string() { 87 | optimization_locations.insert(loc); 88 | } else if identifier.name == "sub".to_string() { 89 | optimization_locations.insert(loc); 90 | } else if identifier.name == "mul".to_string() { 91 | optimization_locations.insert(loc); 92 | } else if identifier.name == "div".to_string() { 93 | optimization_locations.insert(loc); 94 | } 95 | } 96 | } 97 | } 98 | 99 | optimization_locations 100 | } 101 | 102 | #[test] 103 | fn test_analyze_for_safe_math_pre_080() { 104 | let file_contents = r#" 105 | 106 | pragma solidity >= 0.7.0; 107 | contract Contract { 108 | /// *** Libraries *** 109 | using SafeMath for uint256; 110 | using SafeMath for uint16; 111 | 112 | 113 | function testFunction(){ 114 | uint256 something = 190092340923434; 115 | uint256 somethingElse = 1; 116 | 117 | uint256 thing = something.add(somethingElse); 118 | uint256 thing1 = something.sub(somethingElse); 119 | uint256 thing2 = something.mul(somethingElse); 120 | uint256 thing3 = something.div(somethingElse); 121 | 122 | } 123 | } 124 | 125 | "#; 126 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 127 | 128 | let optimization_locations = safe_math_pre_080_optimization(source_unit); 129 | 130 | assert_eq!(optimization_locations.len(), 4); 131 | } 132 | 133 | #[test] 134 | fn test_analyze_for_safe_math_post_080() { 135 | let file_contents = r#" 136 | 137 | pragma solidity >= 0.8.13; 138 | contract Contract { 139 | /// *** Libraries *** 140 | using SafeMath for uint256; 141 | using SafeMath for uint16; 142 | 143 | 144 | function testFunction(){ 145 | uint256 something = 190092340923434; 146 | uint256 somethingElse = 1; 147 | 148 | uint256 thing = something.add(somethingElse); 149 | uint256 thing1 = something.sub(somethingElse); 150 | uint256 thing2 = something.mul(somethingElse); 151 | uint256 thing3 = something.div(somethingElse); 152 | 153 | } 154 | } 155 | 156 | "#; 157 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 158 | 159 | let optimization_locations = safe_math_post_080_optimization(source_unit); 160 | 161 | assert_eq!(optimization_locations.len(), 4); 162 | } 163 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/shift_math.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::u32; 3 | 4 | use solang_parser::pt::{Expression, Loc}; 5 | use solang_parser::{self, pt::SourceUnit}; 6 | 7 | use crate::analyzer::ast::{self, Target}; 8 | 9 | pub fn shift_math_optimization(source_unit: SourceUnit) -> HashSet { 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | let target_nodes = 13 | ast::extract_targets_from_node(vec![Target::Multiply, Target::Divide], source_unit.into()); 14 | 15 | for node in target_nodes { 16 | //We can use expect because both Target::Multiply and Target::Divide are expressions 17 | let expression = node.expression().expect("Node is not an expression"); 18 | 19 | match expression { 20 | Expression::Multiply(loc, box_expression, box_expression_1) => { 21 | if check_if_inputs_are_power_of_two(box_expression, box_expression_1) { 22 | optimization_locations.insert(loc); 23 | } 24 | } 25 | 26 | Expression::Divide(loc, box_expression, box_expression_1) => { 27 | if check_if_inputs_are_power_of_two(box_expression, box_expression_1) { 28 | optimization_locations.insert(loc); 29 | } 30 | } 31 | 32 | _ => {} 33 | } 34 | } 35 | optimization_locations 36 | } 37 | 38 | fn check_if_inputs_are_power_of_two( 39 | box_expression: Box, 40 | box_expression_1: Box, 41 | ) -> bool { 42 | //create a boolean to determine if either of the inputs are a power of two 43 | let mut is_even: bool = false; 44 | 45 | //if the first expression is a number literal that is a power of 2 46 | if let Expression::NumberLiteral(_, val_string, _) = *box_expression { 47 | let value = val_string 48 | .parse::() 49 | .expect("Could not parse NumberLiteral value from string to u32"); 50 | 51 | if (value != 0) && ((value & (value - 1)) == 0) { 52 | is_even = true; 53 | } 54 | } 55 | 56 | //if the first expression is a number literal that is a power of 2 57 | if let Expression::NumberLiteral(_, val_string, _) = *box_expression_1 { 58 | let value = val_string 59 | .parse::() 60 | .expect("Could not parse NumberLiteral value from string to u32"); 61 | 62 | if (value != 0) && ((value & (value - 1)) == 0) { 63 | is_even = true; 64 | } 65 | } 66 | 67 | is_even 68 | } 69 | 70 | #[test] 71 | fn test_shift_math_optimization() { 72 | let file_contents = r#" 73 | 74 | contract Contract0 { 75 | 76 | function mul2(uint256 a, uint256 b) public pure { 77 | uint256 a = 10 * 2; 78 | 79 | uint256 b = 2 * a; 80 | uint256 c = a * b; 81 | 82 | uint256 d = (a * b) * 2; 83 | } 84 | } 85 | "#; 86 | 87 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 88 | 89 | let optimization_locations = shift_math_optimization(source_unit); 90 | 91 | assert_eq!(optimization_locations.len(), 3) 92 | } 93 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/short_revert_string.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::{ 7 | ast::{self, Target}, 8 | utils, 9 | }; 10 | 11 | pub fn short_revert_string_optimization(source_unit: SourceUnit) -> HashSet { 12 | //Create a new hashset that stores the location of each optimization target identified 13 | let mut optimization_locations = HashSet::::new(); 14 | 15 | let solidity_version = utils::get_solidity_version_from_source_unit(source_unit.clone()) 16 | .expect("Could not extract Solidity version from source unit."); 17 | 18 | if !(solidity_version.1 >= 8 && solidity_version.2 >= 4) { 19 | let target_nodes = ast::extract_target_from_node(Target::FunctionCall, source_unit.into()); 20 | 21 | for node in target_nodes { 22 | let expression = node.expression().unwrap(); 23 | 24 | if let pt::Expression::FunctionCall(_, ident, expressions) = expression { 25 | match (*ident, expressions.last()) { 26 | ( 27 | // identifier is variable 28 | pt::Expression::Variable(identifier), 29 | // last expression is string literal 30 | Some(pt::Expression::StringLiteral(literals)), 31 | ) if identifier.name.eq("require") => { 32 | if let Some(literal) = literals.get(0) { 33 | if literal.string.len() >= 32 { 34 | optimization_locations.insert(literal.loc); 35 | } 36 | } 37 | } 38 | _ => (), 39 | }; 40 | } 41 | } 42 | } 43 | 44 | //Return the identified optimization locations 45 | optimization_locations 46 | } 47 | 48 | #[test] 49 | fn test_short_revert_string() { 50 | let file_contents = r#" 51 | pragma solidity 0.8.0; 52 | 53 | contract Contract0 { 54 | function expensiveRevertStrings() { 55 | require(a < b, "long revert string over 32 bytes"); 56 | } 57 | 58 | function cheapRevertStrings() { 59 | require(a < b, "a"); 60 | } 61 | 62 | function noRevertMessage() { 63 | require(a < b); 64 | } 65 | } 66 | "#; 67 | 68 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 69 | 70 | let optimization_locations = short_revert_string_optimization(source_unit); 71 | assert_eq!(optimization_locations.len(), 1); 72 | 73 | let invalid_version_content = r#" 74 | pragma solidity 0.8.14; 75 | 76 | contract Contract0 { 77 | function expensiveRevertStrings() { 78 | require(a < b, "long revert string over 32 bytes"); 79 | } 80 | } 81 | "#; 82 | 83 | let source_unit = solang_parser::parse(invalid_version_content, 0).unwrap().0; 84 | 85 | let optimization_locations = short_revert_string_optimization(source_unit); 86 | assert_eq!(optimization_locations.len(), 0); 87 | } 88 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/solidity_keccak256.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | //Use assembly to hash instead of keccak256 9 | pub fn solidity_keccak256_optimization(source_unit: SourceUnit) -> HashSet { 10 | //Create a new hashset that stores the location of each optimization target identified 11 | let mut optimization_locations: HashSet = HashSet::new(); 12 | 13 | //Extract the target nodes from the source_unit 14 | let target_nodes = ast::extract_target_from_node(Target::FunctionCall, source_unit.into()); 15 | 16 | //For each target node that was extracted, check for the optimization patterns 17 | for node in target_nodes { 18 | //Can unwrap because FunctionCall is an expression 19 | let expression = node.expression().unwrap(); 20 | 21 | if let pt::Expression::FunctionCall(_, box_expression, _) = expression { 22 | if let pt::Expression::Variable(variable) = *box_expression { 23 | if variable.name == "keccak256" { 24 | optimization_locations.insert(variable.loc); 25 | } 26 | } 27 | } 28 | } 29 | 30 | //Return the identified optimization locations 31 | optimization_locations 32 | } 33 | 34 | #[test] 35 | fn test_template_optimization() { 36 | let file_contents = r#" 37 | 38 | contract Contract0 { 39 | 40 | constructor(uint256 a, uint256 b){ 41 | keccak256(abi.encodePacked(a, b)); 42 | 43 | } 44 | 45 | function solidityHash(uint256 a, uint256 b) public view { 46 | //unoptimized 47 | keccak256(abi.encodePacked(a, b)); 48 | } 49 | 50 | 51 | function assemblyHash(uint256 a, uint256 b) public view { 52 | //optimized 53 | assembly { 54 | mstore(0x00, a) 55 | mstore(0x20, b) 56 | let hashedVal := keccak256(0x00, 0x40) 57 | } 58 | } 59 | } 60 | "#; 61 | 62 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 63 | 64 | let optimization_locations = solidity_keccak256_optimization(source_unit); 65 | 66 | assert_eq!(optimization_locations.len(), 2) 67 | } 68 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/solidity_math.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn solidity_math_optimization(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each optimization target identified 10 | let mut optimization_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_targets_from_node( 14 | vec![ 15 | Target::Add, 16 | Target::Subtract, 17 | Target::Multiply, 18 | Target::Divide, 19 | ], 20 | source_unit.into(), 21 | ); 22 | 23 | //For each target node that was extracted, check for the optimization patterns 24 | for node in target_nodes { 25 | //Can unwrap because all targets are expressions 26 | let expression = node.expression().unwrap(); 27 | match expression { 28 | pt::Expression::Add(loc, _, _) => { 29 | optimization_locations.insert(loc); 30 | } 31 | pt::Expression::Subtract(loc, _, _) => { 32 | optimization_locations.insert(loc); 33 | } 34 | pt::Expression::Multiply(loc, _, _) => { 35 | optimization_locations.insert(loc); 36 | } 37 | pt::Expression::Divide(loc, _, _) => { 38 | optimization_locations.insert(loc); 39 | } 40 | 41 | _ => {} 42 | } 43 | } 44 | 45 | //Return the identified optimization locations 46 | optimization_locations 47 | } 48 | 49 | #[test] 50 | fn test_analyze_for_math_optimization() { 51 | let file_contents = r#" 52 | 53 | 54 | contract Contract0 { 55 | 56 | //addition in Solidity 57 | function addTest(uint256 a, uint256 b) public pure { 58 | uint256 c = a + b; 59 | } 60 | 61 | //addition in assembly 62 | function addAssemblyTest(uint256 a, uint256 b) public pure { 63 | assembly { 64 | let c := add(a, b) 65 | 66 | if lt(c, a) { 67 | mstore(0x00, "overflow") 68 | revert(0x00, 0x20) 69 | } 70 | } 71 | } 72 | 73 | //subtraction in Solidity 74 | function subTest(uint256 a, uint256 b) public pure { 75 | uint256 c = a - b; 76 | } 77 | } 78 | 79 | contract Contract3 { 80 | //subtraction in assembly 81 | function subAssemblyTest(uint256 a, uint256 b) public pure { 82 | assembly { 83 | let c := sub(a, b) 84 | 85 | if gt(c, a) { 86 | mstore(0x00, "underflow") 87 | revert(0x00, 0x20) 88 | } 89 | } 90 | } 91 | 92 | //multiplication in Solidity 93 | function mulTest(uint256 a, uint256 b) public pure { 94 | uint256 c = a * b; 95 | } 96 | //multiplication in assembly 97 | function mulAssemblyTest(uint256 a, uint256 b) public pure { 98 | assembly { 99 | let c := mul(a, b) 100 | 101 | if lt(c, a) { 102 | mstore(0x00, "overflow") 103 | revert(0x00, 0x20) 104 | } 105 | } 106 | } 107 | 108 | //division in Solidity 109 | function divTest(uint256 a, uint256 b) public pure { 110 | uint256 c = a * b; 111 | } 112 | 113 | function divAssemblyTest(uint256 a, uint256 b) public pure { 114 | assembly { 115 | let c := div(a, b) 116 | 117 | if gt(c, a) { 118 | mstore(0x00, "underflow") 119 | revert(0x00, 0x20) 120 | } 121 | } 122 | } 123 | } 124 | 125 | "#; 126 | 127 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 128 | 129 | let optimization_locations = solidity_math_optimization(source_unit); 130 | 131 | assert_eq!(optimization_locations.len(), 4) 132 | } 133 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/sstore.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | use crate::analyzer::utils; 8 | 9 | pub fn sstore_optimization(source_unit: SourceUnit) -> HashSet { 10 | //Create a new hashset that stores the location of each optimization target identified 11 | let mut optimization_locations: HashSet = HashSet::new(); 12 | 13 | //Get all storage variables 14 | let storage_variables = utils::get_32_byte_storage_variables(source_unit.clone(), true, true); 15 | 16 | //Extract the target nodes from the source_unit 17 | let target_nodes = ast::extract_target_from_node(Target::Assign, source_unit.into()); 18 | 19 | for node in target_nodes { 20 | //We can use unwrap because Target::Assign is an expression 21 | let expression = node.expression().unwrap(); 22 | 23 | //if the expression is an Assign 24 | if let pt::Expression::Assign(loc, box_expression, _) = expression { 25 | //if the first expr in the assign expr is a variable 26 | if let pt::Expression::Variable(identifier) = *box_expression { 27 | //if the variable name exists in the storage variable hashmap 28 | if storage_variables.contains_key(&identifier.name) { 29 | //add the location to the optimization locations 30 | optimization_locations.insert(loc); 31 | } 32 | } 33 | } 34 | } 35 | //Return the identified optimization locations 36 | optimization_locations 37 | } 38 | #[test] 39 | fn test_sstore_optimization() { 40 | let file_contents = r#" 41 | 42 | pragma solidity >= 0.8.0; 43 | contract Contract { 44 | 45 | uint256 thing = 100; 46 | address someAddress = address(0); 47 | bytes someBytes; 48 | 49 | 50 | 51 | function testFunction() public { 52 | thing = 1+2; 53 | someAddress = msg.sender; 54 | someBytes = bytes(0); 55 | 56 | 57 | } 58 | } 59 | 60 | "#; 61 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 62 | 63 | let optimization_locations = sstore_optimization(source_unit); 64 | 65 | assert_eq!(optimization_locations.len(), 3); 66 | } 67 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/string_errors.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | use crate::analyzer::utils; 8 | 9 | pub fn string_error_optimization(source_unit: SourceUnit) -> HashSet { 10 | //Create a new hashset that stores the location of each optimization target identified 11 | let mut optimization_locations: HashSet = HashSet::new(); 12 | 13 | let solidity_version = utils::get_solidity_version_from_source_unit(source_unit.clone()) 14 | .expect("Could not extract Solidity version from source unit."); 15 | 16 | if solidity_version.1 >= 8 && solidity_version.2 >= 4 { 17 | //Extract the target nodes from the source_unit 18 | let target_nodes = ast::extract_target_from_node(Target::FunctionCall, source_unit.into()); 19 | 20 | for node in target_nodes { 21 | //We can use unwrap because Target::FunctionCall is an expression 22 | let expression = node.expression().unwrap(); 23 | 24 | if let pt::Expression::FunctionCall(_, function_identifier, func_call_expressions) = 25 | expression 26 | { 27 | //if the function call identifier is a variable 28 | if let pt::Expression::Variable(identifier) = *function_identifier { 29 | //if the identifier name is "require" 30 | if identifier.name == "require".to_string() { 31 | //If the require statement contains strings 32 | if let Some(pt::Expression::StringLiteral(vec_string_literal)) = 33 | func_call_expressions.last() 34 | { 35 | optimization_locations.insert(vec_string_literal[0].loc); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | //Return the identified optimization locations 43 | optimization_locations 44 | } 45 | #[test] 46 | fn test_string_error_optimization() { 47 | //test when base solidiy version is > than 0.8.4 48 | let file_contents = r#" 49 | pragma solidity >=0.8.13; 50 | 51 | contract Contract0 { 52 | function addressInternalBalance() public returns (uint256) { 53 | 54 | require(true, "some message"); 55 | 56 | require(true && a==b, "some message"); 57 | require(true && a==b && b==c, "thing"); 58 | 59 | return address(this).balance; 60 | } 61 | } 62 | "#; 63 | 64 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 65 | 66 | let optimization_locations = string_error_optimization(source_unit); 67 | 68 | assert_eq!(optimization_locations.len(), 3); 69 | 70 | //test when base solidiy version is < than 0.8.4 71 | let file_contents_1 = r#" 72 | pragma solidity <= 0.8.3; 73 | 74 | contract Contract0 { 75 | function addressInternalBalance() public returns (uint256) { 76 | 77 | require(true, "some message"); 78 | 79 | require(true && a==b, "some message"); 80 | require(true && a==b && b==c, "thing"); 81 | 82 | return address(this).balance; 83 | } 84 | } 85 | "#; 86 | 87 | let source_unit_1 = solang_parser::parse(file_contents_1, 0).unwrap().0; 88 | 89 | let optimization_locations_1 = string_error_optimization(source_unit_1); 90 | 91 | assert_eq!(optimization_locations_1.len(), 0); 92 | } 93 | -------------------------------------------------------------------------------- /src/analyzer/optimizations/template.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::Loc; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn _template_optimization(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each optimization target identified 10 | let optimization_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_target_from_node(Target::None, source_unit.into()); 14 | //If searching for multiple target nodes, use the following function instead and pass a vec of Target 15 | // let target_nodes = ast::extract_targets_from_node(vec![Target::Target1, Target::Target2], source_unit.into()); 16 | 17 | //For each target node that was extracted, check for the optimization patterns 18 | for _node in target_nodes {} 19 | 20 | //Return the identified optimization locations 21 | optimization_locations 22 | } 23 | 24 | #[test] 25 | fn test_template_optimization() { 26 | let file_contents = r#" 27 | 28 | contract Contract0 { 29 | 30 | } 31 | "#; 32 | 33 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 34 | 35 | let optimization_locations = _template_optimization(source_unit); 36 | assert_eq!(optimization_locations.len(), 0) 37 | } 38 | -------------------------------------------------------------------------------- /src/analyzer/qa/constructor_order.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | // Constructor must be placed before any other function 9 | pub fn constructor_order_qa(source_unit: SourceUnit) -> HashSet { 10 | //Create a new hashset that stores the location of each qa target identified 11 | let mut qa_locations: HashSet = HashSet::new(); 12 | 13 | //Extract the target nodes from the source_unit 14 | let target_nodes = 15 | ast::extract_target_from_node(Target::FunctionDefinition, source_unit.into()); 16 | 17 | let mut fn_counter: u8 = 0; // up to 256 function definitions before reaching the constructor function 18 | 19 | //For each target node that was extracted, check for the qa patterns 20 | for _node in target_nodes { 21 | let contract_part = _node.contract_part().unwrap(); 22 | 23 | if let pt::ContractPart::FunctionDefinition(box_fn_definition) = contract_part { 24 | match box_fn_definition.ty { 25 | pt::FunctionTy::Constructor => { 26 | if fn_counter > 0 { 27 | qa_locations.insert(box_fn_definition.loc); 28 | break; 29 | } 30 | } 31 | // Modifiers must be placed before constructor 32 | pt::FunctionTy::Modifier => continue, 33 | _ => { 34 | fn_counter += 1; 35 | } 36 | } 37 | } 38 | } 39 | 40 | //Return the identified qa locations 41 | qa_locations 42 | } 43 | 44 | #[test] 45 | fn test_constructor_order_qa() { 46 | let test_contracts = vec![ 47 | r#" 48 | contract Contract0 { 49 | address public owner; 50 | function test() public { 51 | owner = address(0); 52 | } 53 | constructor() { 54 | owner = address(1); 55 | } 56 | } 57 | "#, 58 | r#" 59 | contract Contract0 { 60 | address public owner; 61 | receive() external payable {} 62 | constructor() { 63 | owner = address(1); 64 | } 65 | } 66 | "#, 67 | r#" 68 | contract Contract0 { 69 | address public owner; 70 | modifier onlyOwner { 71 | require( 72 | msg.sender == owner, 73 | "Only owner can call this function." 74 | ); 75 | _; 76 | } 77 | constructor() { 78 | owner = address(1); 79 | } 80 | } 81 | "#, 82 | r#" 83 | contract Contract0 { 84 | address public owner; 85 | function test() public { 86 | owner = address(0); 87 | } 88 | } 89 | "#, 90 | ]; 91 | 92 | let assertions = vec![1, 1, 0, 0]; 93 | 94 | assert_eq!(test_contracts.len(), assertions.len()); 95 | 96 | if assertions.len() > 0 { 97 | for i in 0..assertions.len() - 1 { 98 | let source_unit = solang_parser::parse(test_contracts[i], 0).unwrap().0; 99 | 100 | let qa_locations = constructor_order_qa(source_unit); 101 | assert_eq!(qa_locations.len(), assertions[i]); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/analyzer/qa/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constructor_order; 2 | pub mod private_func_leading_underscore; 3 | pub mod private_vars_leading_underscore; 4 | pub mod template; 5 | 6 | use std::{ 7 | collections::{BTreeSet, HashMap, HashSet}, 8 | fs, 9 | path::PathBuf, 10 | str::FromStr, 11 | }; 12 | 13 | use self::{ 14 | constructor_order::constructor_order_qa, 15 | private_func_leading_underscore::private_func_leading_underscore, 16 | private_vars_leading_underscore::private_vars_leading_underscore, 17 | }; 18 | 19 | use super::utils::{self, LineNumber}; 20 | 21 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 22 | pub enum QualityAssurance { 23 | ConstructorOrder, 24 | PrivateVarsLeadingUnderscore, 25 | PrivateFuncLeadingUnderscore, 26 | } 27 | 28 | pub fn get_all_qa() -> Vec { 29 | vec![ 30 | QualityAssurance::ConstructorOrder, 31 | QualityAssurance::PrivateVarsLeadingUnderscore, 32 | QualityAssurance::PrivateFuncLeadingUnderscore, 33 | ] 34 | } 35 | 36 | pub fn str_to_qa(qa: &str) -> QualityAssurance { 37 | match qa.to_lowercase().as_str() { 38 | "constructor_order" => QualityAssurance::ConstructorOrder, 39 | "private_vars_leading_underscore" => QualityAssurance::PrivateVarsLeadingUnderscore, 40 | "private_func_leading_underscore" => QualityAssurance::PrivateFuncLeadingUnderscore, 41 | other => { 42 | panic!("Unrecgonized qa: {}", other) 43 | } 44 | } 45 | } 46 | 47 | pub fn analyze_dir( 48 | target_dir: &str, 49 | qa: Vec, 50 | ) -> HashMap)>> { 51 | //Initialize a new hashmap to keep track of all the optimizations across the target dir 52 | let mut qa_locations: HashMap)>> = 53 | HashMap::new(); 54 | 55 | //For each file in the target dir 56 | for (i, path) in fs::read_dir(target_dir) 57 | .expect(format!("Could not read contracts from directory: {:?}", target_dir).as_str()) 58 | .into_iter() 59 | .enumerate() 60 | { 61 | //Get the file path, name and contents 62 | let file_path = path 63 | .expect(format!("Could not unwrap file path: {}", i).as_str()) 64 | .path(); 65 | 66 | if file_path.is_dir() { 67 | qa_locations.extend(analyze_dir( 68 | file_path 69 | .as_os_str() 70 | .to_str() 71 | .expect("Could not get nested dir"), 72 | qa.clone(), 73 | )) 74 | } else { 75 | let file_name = file_path 76 | .file_name() 77 | .expect(format!("Could not unwrap file name to OsStr: {}", i).as_str()) 78 | .to_str() 79 | .expect("Could not convert file name from OsStr to &str") 80 | .to_string(); 81 | 82 | if file_name.ends_with(".sol") && !file_name.to_lowercase().contains(".t.sol") { 83 | let file_contents = fs::read_to_string(&file_path).expect("Unable to read file"); 84 | 85 | //For each active optimization 86 | for target in &qa { 87 | let line_numbers = analyze_for_qa(&file_contents, i, *target); 88 | 89 | if line_numbers.len() > 0 { 90 | let file_optimizations = 91 | qa_locations.entry(target.clone()).or_insert(vec![]); 92 | 93 | file_optimizations.push((file_name.clone(), line_numbers)); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | qa_locations 101 | } 102 | 103 | pub fn analyze_for_qa( 104 | file_contents: &str, 105 | file_number: usize, 106 | qa: QualityAssurance, 107 | ) -> BTreeSet { 108 | let mut line_numbers: BTreeSet = BTreeSet::new(); 109 | 110 | //Parse the file into a the ast 111 | let source_unit = solang_parser::parse(&file_contents, file_number).unwrap().0; 112 | 113 | let locations = match qa { 114 | QualityAssurance::ConstructorOrder => constructor_order_qa(source_unit), 115 | QualityAssurance::PrivateVarsLeadingUnderscore => { 116 | private_vars_leading_underscore(source_unit) 117 | } 118 | QualityAssurance::PrivateFuncLeadingUnderscore => { 119 | private_func_leading_underscore(source_unit) 120 | } 121 | _ => panic!("Location dont recognized"), 122 | }; 123 | 124 | for loc in locations { 125 | line_numbers.insert(utils::get_line_number(loc.start(), file_contents)); 126 | } 127 | 128 | line_numbers 129 | } 130 | -------------------------------------------------------------------------------- /src/analyzer/qa/private_func_leading_underscore.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, FunctionTy, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn private_func_leading_underscore(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each qa target identified 10 | let mut qa_locations: HashSet = HashSet::new(); 11 | //Extract the target nodes from the source_unit 12 | let target_nodes = 13 | ast::extract_target_from_node(Target::FunctionDefinition, source_unit.into()); 14 | 15 | for node in target_nodes { 16 | let contract_part = node.contract_part().unwrap(); 17 | 18 | if let pt::ContractPart::FunctionDefinition(box_fn_definition) = contract_part { 19 | if FunctionTy::Function != box_fn_definition.ty { 20 | continue; 21 | } 22 | 23 | for attr in box_fn_definition.attributes { 24 | if let pt::FunctionAttribute::Visibility(v) = attr { 25 | let fn_def_data = box_fn_definition.name.clone(); 26 | if fn_def_data.is_some() { 27 | let fn_data = fn_def_data.unwrap(); 28 | match v { 29 | pt::Visibility::Public(_) | pt::Visibility::External(_) => { 30 | if fn_data.name.starts_with('_') { 31 | qa_locations.insert(fn_data.loc); 32 | } 33 | } 34 | // Private or Internal functions 35 | _ => { 36 | if !fn_data.name.starts_with('_') { 37 | qa_locations.insert(fn_data.loc); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | //Return the identified qa locations 48 | qa_locations 49 | } 50 | 51 | #[test] 52 | fn test_private_func_leading_underscore() { 53 | let file_contents = r#" 54 | 55 | contract Contract0 { 56 | 57 | function msgSender() internal view returns(address) { 58 | return msg.sender; 59 | } 60 | 61 | function _msgSender() internal view returns(address) { 62 | return msg.sender; 63 | } 64 | 65 | function _msgData() private view returns(bytes calldata) { 66 | return msg.data; 67 | } 68 | 69 | function msgData() private view returns(bytes calldata) { 70 | return msg.data; 71 | } 72 | } 73 | "#; 74 | 75 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 76 | 77 | let qa_locations = private_func_leading_underscore(source_unit); 78 | assert_eq!(qa_locations.len(), 2) 79 | } 80 | -------------------------------------------------------------------------------- /src/analyzer/qa/private_vars_leading_underscore.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, FunctionTy, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | use crate::analyzer::utils::get_32_byte_storage_variables; 8 | 9 | pub fn private_vars_leading_underscore(source_unit: SourceUnit) -> HashSet { 10 | //Create a new hashset that stores the location of each qa target identified 11 | let mut qa_locations: HashSet = HashSet::new(); 12 | 13 | let storage_variables = get_32_byte_storage_variables(source_unit.clone(), true, false); 14 | 15 | for (variable_name, variable_attribute) in storage_variables { 16 | let (option_variable_attributes, loc) = variable_attribute; 17 | 18 | if option_variable_attributes.is_some() { 19 | let variable_attributes = option_variable_attributes.unwrap(); 20 | 21 | for attr in variable_attributes { 22 | if let pt::VariableAttribute::Visibility(v) = attr { 23 | match v { 24 | pt::Visibility::Private(_) | pt::Visibility::Internal(_) => { 25 | if !variable_name.starts_with('_') { 26 | qa_locations.insert(loc); 27 | } 28 | } 29 | // Public variables 30 | _ => { 31 | if variable_name.starts_with('_') { 32 | qa_locations.insert(loc); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | //Return the identified qa locations 42 | qa_locations 43 | } 44 | 45 | #[test] 46 | fn test_private_vars_leading_underscore() { 47 | let file_contents = r#" 48 | 49 | contract Contract0 { 50 | address public addr1; 51 | address public _addr2; 52 | address private _addr3; 53 | address private addr4; 54 | address internal _addr5; 55 | address internal addr6; 56 | } 57 | "#; 58 | 59 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 60 | 61 | let qa_locations = private_vars_leading_underscore(source_unit); 62 | assert_eq!(qa_locations.len(), 3) 63 | } 64 | -------------------------------------------------------------------------------- /src/analyzer/qa/template.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::Loc; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn _template_qa(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each qa target identified 10 | let qa_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_target_from_node(Target::None, source_unit.into()); 14 | //If searching for multiple target nodes, use the following function instead and pass a vec of Target 15 | // let target_nodes = ast::extract_targets_from_node(vec![Target::Target1, Target::Target2], source_unit.into()); 16 | 17 | //For each target node that was extracted, check for the qa patterns 18 | for _node in target_nodes {} 19 | 20 | //Return the identified qa locations 21 | qa_locations 22 | } 23 | 24 | #[test] 25 | fn test_template_qa() { 26 | let file_contents = r#" 27 | 28 | contract Contract0 { 29 | 30 | } 31 | "#; 32 | 33 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 34 | 35 | let qa_locations = _template_qa(source_unit); 36 | assert_eq!(qa_locations.len(), 0) 37 | } 38 | -------------------------------------------------------------------------------- /src/analyzer/vulnerabilities/divide_before_multiply.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{extract_targets_from_node, Target}; 7 | 8 | pub fn divide_before_multiply_vulnerability(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each vulnerability target identified 10 | let mut vulnerability_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = extract_targets_from_node( 14 | vec![Target::Multiply, Target::AssignDivide], 15 | source_unit.into(), 16 | ); 17 | 18 | //For each target node that was extracted, check for the vulnerability patterns 19 | for node in target_nodes { 20 | //We can use unwrap because Target::Multiply is an expression 21 | let expression = node.expression().unwrap(); 22 | 23 | match expression { 24 | pt::Expression::Multiply(loc, box_expression, _) => { 25 | let mut curr_expression = *box_expression; 26 | loop { 27 | match curr_expression { 28 | pt::Expression::Divide(_, _, _) => { 29 | //Found case where division occurs before multiplication 30 | vulnerability_locations.insert(loc); 31 | break; 32 | } 33 | pt::Expression::Multiply(_, next_expression, _) 34 | | pt::Expression::Parenthesis(_, next_expression) => { 35 | //Continue to check the next expression for division 36 | curr_expression = *next_expression; 37 | } 38 | _ => { 39 | break; 40 | } 41 | } 42 | } 43 | } 44 | pt::Expression::AssignDivide(loc, _, box_expression) => { 45 | let mut curr_expression = *box_expression; 46 | loop { 47 | match curr_expression { 48 | pt::Expression::Multiply(_, _, _) => { 49 | //Found case where multiplication occurs before division 50 | vulnerability_locations.insert(loc); 51 | break; 52 | } 53 | pt::Expression::Divide(_, next_expression, _) 54 | | pt::Expression::Add(_, next_expression, _) 55 | | pt::Expression::Subtract(_, next_expression, _) 56 | | pt::Expression::Modulo(_, next_expression, _) 57 | | pt::Expression::BitwiseAnd(_, next_expression, _) 58 | | pt::Expression::BitwiseOr(_, next_expression, _) 59 | | pt::Expression::BitwiseXor(_, next_expression, _) 60 | | pt::Expression::ShiftLeft(_, next_expression, _) 61 | | pt::Expression::ShiftRight(_, next_expression, _) 62 | | pt::Expression::Parenthesis(_, next_expression) => { 63 | //Continue to check the next expression for multiplication 64 | curr_expression = *next_expression; 65 | } 66 | _ => { 67 | break; 68 | } 69 | } 70 | } 71 | } 72 | _ => {} 73 | } 74 | } 75 | 76 | //Return the identified vulnerability locations 77 | vulnerability_locations 78 | } 79 | 80 | #[test] 81 | fn test_divide_before_multiply_vulnerability() { 82 | let file_contents = r#" 83 | 84 | contract Contract0 { 85 | 86 | function arithmetic_operations() public { 87 | 1 / 2 * 3; // Unsafe 88 | 1 * 2 / 3; // Safe 89 | (1 / 2) * 3; // Unsafe 90 | (1 * 2) / 3; // Safe 91 | (1 / 2 * 3) * 4; // Unsafe (x2) 92 | (1 * 2 / 3) * 4; // Unsafe 93 | (1 / 2 / 3) * 4; // Unsafe 94 | 1 / (2 + 3) * 4; // Unsafe 95 | (1 / 2 + 3) * 4; // Safe 96 | (1 / 2 - 3) * 4; // Safe 97 | (1 + 2 / 3) * 4; // Safe 98 | (1 / 2 - 3) * 4; // Safe 99 | (1 / 2 % 3) * 4; // Safe 100 | (1 / 2 | 3) * 4; // Safe 101 | (1 / 2 & 3) * 4; // Safe 102 | (1 / 2 ^ 3) * 4; // Safe 103 | (1 / 2 << 3) * 4; // Safe 104 | (1 / 2 >> 3) * 4; // Safe 105 | 1 / (2 * 3 + 3); // Safe 106 | 1 / ((2 / 3) * 3); // Unsafe 107 | 1 / ((2 * 3) + 3); // Safe 108 | 109 | uint256 x = 5; 110 | x /= 2 * 3; // Unsafe 111 | x /= 2 / 3; // Safe 112 | x /= (2 * 3); // Unsafe 113 | x /= (1 / 2) * 3; // Unsafe (x2) 114 | x /= (1 * 2) * 3; // Unsafe 115 | x /= (2 * 3) / 4; // Unsafe 116 | x /= 2 * 3 / 4; // Unsafe 117 | x /= 2 * 3 - 4; // Unsafe 118 | x /= 2 * 3 % 4; // Unsafe 119 | x /= 2 * 3 | 4; // Unsafe 120 | x /= 2 * 3 & 4; // Unsafe 121 | x /= 2 * 3 ^ 4; // Unsafe 122 | x /= 2 * 3 << 4; // Unsafe 123 | x /= 2 * 3 >> 4; // Unsafe 124 | x /= 3 / 4; // Safe 125 | x /= 3 - 4; // Safe 126 | x /= 3 % 4; // Safe 127 | x /= 3 | 4; // Safe 128 | x /= 3 & 4; // Safe 129 | x /= 3 ^ 4; // Safe 130 | x /= 3 << 4; // Safe 131 | x /= 3 >> 4; // Safe 132 | } 133 | 134 | } 135 | "#; 136 | 137 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 138 | 139 | let vulnerability_locations = divide_before_multiply_vulnerability(source_unit); 140 | assert_eq!(vulnerability_locations.len(), 22) 141 | } 142 | -------------------------------------------------------------------------------- /src/analyzer/vulnerabilities/floating_pragma.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{extract_target_from_node, Target}; 7 | 8 | pub fn floating_pragma_vulnerability(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each vulnerability target identified 10 | let mut vulnerability_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = extract_target_from_node(Target::PragmaDirective, source_unit.into()); 14 | 15 | //For each target node that was extracted, check for the vulnerability patterns 16 | for node in target_nodes { 17 | //We can use unwrap because Target::PragmaDirective is a source unit part 18 | let source_unit_part = node.source_unit_part().unwrap(); 19 | 20 | if let pt::SourceUnitPart::PragmaDirective(loc, _, pragma) = source_unit_part { 21 | if pragma.string.contains('^') { 22 | vulnerability_locations.insert(loc); 23 | } 24 | } 25 | } 26 | 27 | //Return the identified vulnerability locations 28 | vulnerability_locations 29 | } 30 | 31 | #[test] 32 | fn test_floating_pragma_vulnerability() { 33 | let file_contents = r#" 34 | 35 | pragma solidity ^0.8.16; 36 | 37 | contract Contract0 { 38 | 39 | } 40 | "#; 41 | 42 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 43 | 44 | let vulnerability_locations = floating_pragma_vulnerability(source_unit); 45 | assert_eq!(vulnerability_locations.len(), 1) 46 | } 47 | -------------------------------------------------------------------------------- /src/analyzer/vulnerabilities/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod divide_before_multiply; 2 | pub mod floating_pragma; 3 | pub mod template; 4 | pub mod unprotected_selfdestruct; 5 | pub mod unsafe_erc20_operation; 6 | 7 | use std::{ 8 | collections::{BTreeSet, HashMap, HashSet}, 9 | env, fs, 10 | path::PathBuf, 11 | str::FromStr, 12 | }; 13 | 14 | use solang_parser::pt::SourceUnit; 15 | 16 | use super::utils::{self, LineNumber}; 17 | 18 | use self::{ 19 | divide_before_multiply::divide_before_multiply_vulnerability, 20 | floating_pragma::floating_pragma_vulnerability, 21 | unprotected_selfdestruct::unprotected_selfdestruct_vulnerability, 22 | unsafe_erc20_operation::unsafe_erc20_operation_vulnerability, 23 | }; 24 | 25 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 26 | 27 | pub enum Vulnerability { 28 | FloatingPragma, 29 | UnsafeERC20Operation, 30 | UnprotectedSelfdestruct, 31 | DivideBeforeMultiply, 32 | } 33 | 34 | pub fn get_all_vulnerabilities() -> Vec { 35 | vec![ 36 | Vulnerability::UnsafeERC20Operation, 37 | Vulnerability::UnprotectedSelfdestruct, 38 | Vulnerability::DivideBeforeMultiply, 39 | Vulnerability::FloatingPragma, 40 | ] 41 | } 42 | 43 | pub fn str_to_vulnerability(vuln: &str) -> Vulnerability { 44 | match vuln.to_lowercase().as_str() { 45 | "floating_pragma" => Vulnerability::FloatingPragma, 46 | "unsafe_erc20_operation" => Vulnerability::UnsafeERC20Operation, 47 | "unprotected_selfdestruct" => Vulnerability::UnprotectedSelfdestruct, 48 | "divide_before_multiply" => Vulnerability::DivideBeforeMultiply, 49 | other => { 50 | panic!("Unrecgonized vulnerability: {}", other) 51 | } 52 | } 53 | } 54 | 55 | pub fn analyze_dir( 56 | target_dir: &str, 57 | vulnerabilities: Vec, 58 | ) -> HashMap)>> { 59 | //Initialize a new hashmap to keep track of all the optimizations across the target dir 60 | let mut vulnerability_locations: HashMap)>> = 61 | HashMap::new(); 62 | 63 | //For each file in the target dir 64 | for (i, path) in fs::read_dir(target_dir) 65 | .expect(format!("Could not read contracts from directory: {:?}", target_dir).as_str()) 66 | .into_iter() 67 | .enumerate() 68 | { 69 | //Get the file path, name and contents 70 | let file_path = path 71 | .expect(format!("Could not file unwrap path").as_str()) 72 | .path(); 73 | 74 | if file_path.is_dir() { 75 | vulnerability_locations.extend(analyze_dir( 76 | file_path 77 | .as_os_str() 78 | .to_str() 79 | .expect("Could not get nested dir"), 80 | vulnerabilities.clone(), 81 | )) 82 | } else { 83 | let file_name = file_path 84 | .file_name() 85 | .expect("Could not unwrap file name to OsStr") 86 | .to_str() 87 | .expect("Could not convert file name from OsStr to &str") 88 | .to_string(); 89 | 90 | if file_name.ends_with(".sol") && !file_name.to_lowercase().contains(".t.sol") { 91 | let file_contents = fs::read_to_string(&file_path).expect("Unable to read file"); 92 | 93 | //For each active optimization 94 | for vulnerability in &vulnerabilities { 95 | let line_numbers = analyze_for_vulnerability(&file_contents, i, *vulnerability); 96 | 97 | if line_numbers.len() > 0 { 98 | let file_optimizations = vulnerability_locations 99 | .entry(vulnerability.clone()) 100 | .or_insert(vec![]); 101 | 102 | file_optimizations.push((file_name.clone(), line_numbers)); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | vulnerability_locations 110 | } 111 | 112 | pub fn analyze_for_vulnerability( 113 | file_contents: &str, 114 | file_number: usize, 115 | vulnerability: Vulnerability, 116 | ) -> BTreeSet { 117 | let mut line_numbers: BTreeSet = BTreeSet::new(); 118 | 119 | //Parse the file into a the ast 120 | let source_unit = solang_parser::parse(&file_contents, file_number).unwrap().0; 121 | 122 | let locations = match vulnerability { 123 | Vulnerability::FloatingPragma => floating_pragma_vulnerability(source_unit), 124 | Vulnerability::UnsafeERC20Operation => unsafe_erc20_operation_vulnerability(source_unit), 125 | Vulnerability::UnprotectedSelfdestruct => { 126 | unprotected_selfdestruct_vulnerability(source_unit) 127 | } 128 | Vulnerability::DivideBeforeMultiply => divide_before_multiply_vulnerability(source_unit), 129 | }; 130 | 131 | for loc in locations { 132 | line_numbers.insert(utils::get_line_number(loc.start(), file_contents)); 133 | } 134 | 135 | line_numbers 136 | } 137 | -------------------------------------------------------------------------------- /src/analyzer/vulnerabilities/template.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::Loc; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn _template_vulnerability(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each vulnerability target identified 10 | let vulnerability_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_target_from_node(Target::None, source_unit.into()); 14 | //If searching for multiple target nodes, use the following function instead and pass a vec of Target 15 | // let target_nodes = ast::extract_targets_from_node(vec![Target::Target1, Target::Target2], source_unit.into()); 16 | 17 | //For each target node that was extracted, check for the vulnerability patterns 18 | for _node in target_nodes {} 19 | 20 | //Return the identified vulnerability locations 21 | vulnerability_locations 22 | } 23 | 24 | #[test] 25 | fn test_template_vulnerability() { 26 | let file_contents = r#" 27 | 28 | contract Contract0 { 29 | 30 | } 31 | "#; 32 | 33 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 34 | 35 | let vulnerability_locations = _template_vulnerability(source_unit); 36 | assert_eq!(vulnerability_locations.len(), 0) 37 | } 38 | -------------------------------------------------------------------------------- /src/analyzer/vulnerabilities/unsafe_erc20_operation.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use solang_parser::pt::{self, Loc}; 4 | use solang_parser::{self, pt::SourceUnit}; 5 | 6 | use crate::analyzer::ast::{self, Target}; 7 | 8 | pub fn unsafe_erc20_operation_vulnerability(source_unit: SourceUnit) -> HashSet { 9 | //Create a new hashset that stores the location of each vulnerability target identified 10 | let mut vulnerability_locations: HashSet = HashSet::new(); 11 | 12 | //Extract the target nodes from the source_unit 13 | let target_nodes = ast::extract_target_from_node(Target::MemberAccess, source_unit.into()); 14 | 15 | //For each target node that was extracted, check for the vulnerability patterns 16 | 17 | for node in target_nodes { 18 | //We can use unwrap because Target::MemberAccess is an expression 19 | let expression = node.expression().unwrap(); 20 | 21 | if let pt::Expression::MemberAccess(loc, _, identifier) = expression { 22 | if identifier.name == "transfer" 23 | || identifier.name == "transferFrom" 24 | || identifier.name == "approve" 25 | { 26 | vulnerability_locations.insert(loc); 27 | } 28 | } 29 | } 30 | 31 | //Return the identified vulnerability locations 32 | vulnerability_locations 33 | } 34 | 35 | #[test] 36 | fn test_unsafe_erc20_operation_vulnerability() { 37 | let file_contents = r#" 38 | 39 | contract Contract0 { 40 | 41 | IERC20 e; 42 | 43 | constructor(){ 44 | e = IERC20(0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984); 45 | } 46 | 47 | function unsafe_erc20_operations() public { 48 | e.approve(address(0), 200); 49 | e.transfer(address(0), 100); 50 | e.transferFrom(address(0), 100); 51 | } 52 | 53 | } 54 | "#; 55 | 56 | let source_unit = solang_parser::parse(file_contents, 0).unwrap().0; 57 | 58 | let vulnerability_locations = unsafe_erc20_operation_vulnerability(source_unit); 59 | assert_eq!(vulnerability_locations.len(), 3) 60 | } 61 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analyzer; 2 | pub mod report; 3 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod analyzer; 2 | mod opts; 3 | mod report; 4 | 5 | use analyzer::*; 6 | use opts::Opts; 7 | use report::generation::generate_report; 8 | 9 | #[macro_use] 10 | extern crate colour; 11 | 12 | fn main() { 13 | let opts = Opts::new(); 14 | 15 | let vulnerabilities = vulnerabilities::analyze_dir(&opts.path, opts.vulnerabilities); 16 | let optimizations = optimizations::analyze_dir(&opts.path, opts.optimizations); 17 | let qa = qa::analyze_dir(&opts.path, opts.qa); 18 | 19 | generate_report(vulnerabilities, optimizations, qa); 20 | } 21 | -------------------------------------------------------------------------------- /src/opts.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process}; 2 | 3 | use crate::analyzer::{optimizations, vulnerabilities}; 4 | use crate::analyzer::{ 5 | optimizations::{str_to_optimization, Optimization}, 6 | qa::{self, str_to_qa, QualityAssurance}, 7 | vulnerabilities::{str_to_vulnerability, Vulnerability}, 8 | }; 9 | use clap::Parser; 10 | 11 | #[derive(Parser, Debug)] 12 | #[clap( 13 | name = "Solstat", 14 | about = "A Solidity static analyzer to identify contract vulnerabilities and gas efficiencies." 15 | )] 16 | 17 | pub struct Args { 18 | #[clap( 19 | short, 20 | long, 21 | help = "Path to the directory containing the files Solstat will analyze. The default directory is `./contracts`" 22 | )] 23 | pub path: Option, 24 | 25 | #[clap( 26 | short, 27 | long, 28 | help = "Path to the toml file containing the Solstat configuration when not using the default settings." 29 | )] 30 | pub toml: Option, 31 | } 32 | 33 | pub struct Opts { 34 | pub path: String, 35 | pub optimizations: Vec, 36 | pub vulnerabilities: Vec, 37 | pub qa: Vec, 38 | } 39 | 40 | #[derive(serde::Deserialize, Debug)] 41 | pub struct SolstatToml { 42 | pub path: String, 43 | pub optimizations: Vec, 44 | pub vulnerabilities: Vec, 45 | pub qa: Vec, 46 | } 47 | 48 | impl Opts { 49 | pub fn new() -> Opts { 50 | let args = Args::parse(); 51 | 52 | let (optimizations, vulnerabilities, qa) = if args.toml.is_some() { 53 | let toml_path = args.toml.unwrap(); 54 | 55 | let toml_str = 56 | fs::read_to_string(toml_path).expect("Could not read toml file to string"); 57 | 58 | let solstat_toml: SolstatToml = 59 | toml::from_str(&toml_str).expect("Could not convert toml contents to SolstatToml"); 60 | 61 | ( 62 | solstat_toml 63 | .optimizations 64 | .iter() 65 | .map(|f| str_to_optimization(f)) 66 | .collect::>(), 67 | solstat_toml 68 | .vulnerabilities 69 | .iter() 70 | .map(|f| str_to_vulnerability(f)) 71 | .collect::>(), 72 | solstat_toml 73 | .qa 74 | .iter() 75 | .map(|f| str_to_qa(f)) 76 | .collect::>(), 77 | ) 78 | } else { 79 | ( 80 | optimizations::get_all_optimizations(), 81 | vulnerabilities::get_all_vulnerabilities(), 82 | qa::get_all_qa(), 83 | ) 84 | }; 85 | 86 | let path = if args.path.is_some() { 87 | args.path.unwrap() 88 | } else { 89 | match fs::read_dir("./contracts") { 90 | Ok(_) => {} 91 | 92 | Err(_) => { 93 | yellow!( 94 | "Error when reading the target contracts directory. 95 | If the `--path` flag is not passed, Solstat will look for `./contracts` by default. 96 | To fix this, either add a `./contracts` directory or provide `--path \n" 97 | ); 98 | process::exit(1) 99 | } 100 | } 101 | 102 | String::from("./contracts") 103 | }; 104 | 105 | Opts { 106 | path, 107 | optimizations, 108 | vulnerabilities, 109 | qa, 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/report/generation.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashMap, HashSet}, 3 | fs, 4 | }; 5 | 6 | use crate::analyzer::{ 7 | optimizations::Optimization, qa::QualityAssurance, utils::LineNumber, 8 | vulnerabilities::Vulnerability, 9 | }; 10 | 11 | use super::{ 12 | optimization_report::generate_optimization_report, qa_report::generate_qa_report, 13 | vulnerability_report::generate_vulnerability_report, 14 | }; 15 | 16 | pub fn generate_report( 17 | vulnerabilities: HashMap)>>, 18 | optimizations: HashMap)>>, 19 | qa: HashMap)>>, 20 | ) { 21 | let mut solstat_report = String::from(""); 22 | 23 | if vulnerabilities.len() > 0 { 24 | solstat_report.push_str(&generate_vulnerability_report(vulnerabilities)); 25 | solstat_report.push_str("\n\n"); 26 | } 27 | 28 | if optimizations.len() > 0 { 29 | solstat_report.push_str(&generate_optimization_report(optimizations)); 30 | solstat_report.push_str("\n\n"); 31 | } 32 | 33 | if qa.len() > 0 { 34 | solstat_report.push_str(&generate_qa_report(qa)); 35 | solstat_report.push_str("\n\n"); 36 | } 37 | 38 | fs::write("solstat_report.md", solstat_report).expect("Unable to solstat_report to file"); 39 | } 40 | -------------------------------------------------------------------------------- /src/report/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod generation; 2 | pub mod optimization_report; 3 | pub mod qa_report; 4 | pub mod report_sections; 5 | pub mod vulnerability_report; 6 | -------------------------------------------------------------------------------- /src/report/optimization_report.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeSet, HashSet}; 2 | use std::{collections::HashMap, fs}; 3 | 4 | use crate::analyzer::optimizations::Optimization; 5 | 6 | use crate::analyzer::utils::LineNumber; 7 | use crate::report::report_sections::optimizations::{ 8 | address_balance, address_zero, assign_update_array_value, bool_equals_bool, cache_array_length, 9 | constant_variable, immutable_variable, increment_decrement, memory_to_calldata, 10 | multiple_require, optimal_comparison, overview, pack_storage_variables, pack_struct_variables, 11 | payable_function, private_constant, safe_math_post_080, safe_math_pre_080, shift_math, 12 | short_revert_string, solidity_keccak256, solidity_math, sstore, string_errors, 13 | }; 14 | 15 | pub fn generate_optimization_report( 16 | optimizations: HashMap)>>, 17 | ) -> String { 18 | let mut optimization_report = String::from(""); 19 | 20 | let mut total_optimizations_found = 0; 21 | 22 | for optimization in optimizations { 23 | if optimization.1.len() > 0 { 24 | let optimization_target = optimization.0; 25 | let matches = optimization.1; 26 | 27 | let report_section = get_optimization_report_section(optimization_target); 28 | 29 | let mut matches_section = String::from("### Lines\n"); 30 | 31 | for (file_name, mut lines) in matches { 32 | for line in lines { 33 | //- file_name:line_number\n 34 | matches_section 35 | .push_str(&(String::from("- ") + &file_name + ":" + &line.to_string())); 36 | matches_section.push_str("\n"); 37 | 38 | total_optimizations_found += 1; 39 | } 40 | } 41 | 42 | matches_section.push_str("\n\n"); 43 | 44 | let completed_report_section = report_section + "\n" + matches_section.as_str(); 45 | optimization_report.push_str(completed_report_section.as_str()); 46 | } 47 | } 48 | 49 | //Add overview to optimization report 50 | let mut completed_optimization_report = 51 | overview::report_section_content(total_optimizations_found); 52 | 53 | completed_optimization_report.push_str(optimization_report.as_str()); 54 | 55 | completed_optimization_report 56 | } 57 | 58 | pub fn get_optimization_report_section(optimization: Optimization) -> String { 59 | match optimization { 60 | Optimization::AddressBalance => address_balance::report_section_content(), 61 | Optimization::AddressZero => address_zero::report_section_content(), 62 | Optimization::AssignUpdateArrayValue => assign_update_array_value::report_section_content(), 63 | Optimization::BoolEqualsBool => bool_equals_bool::report_section_content(), 64 | Optimization::CacheArrayLength => cache_array_length::report_section_content(), 65 | Optimization::ConstantVariables => constant_variable::report_section_content(), 66 | Optimization::ImmutableVarialbes => immutable_variable::report_section_content(), 67 | Optimization::IncrementDecrement => increment_decrement::report_section_content(), 68 | Optimization::MemoryToCalldata => memory_to_calldata::report_section_content(), 69 | Optimization::MultipleRequire => multiple_require::report_section_content(), 70 | Optimization::PackStorageVariables => pack_storage_variables::report_section_content(), 71 | Optimization::PackStructVariables => pack_struct_variables::report_section_content(), 72 | Optimization::PayableFunction => payable_function::report_section_content(), 73 | Optimization::PrivateConstant => private_constant::report_section_content(), 74 | Optimization::SafeMathPre080 => safe_math_pre_080::report_section_content(), 75 | Optimization::SafeMathPost080 => safe_math_post_080::report_section_content(), 76 | Optimization::ShiftMath => shift_math::report_section_content(), 77 | Optimization::SolidityKeccak256 => solidity_keccak256::report_section_content(), 78 | Optimization::SolidityMath => solidity_math::report_section_content(), 79 | Optimization::Sstore => sstore::report_section_content(), 80 | Optimization::StringErrors => string_errors::report_section_content(), 81 | Optimization::OptimalComparison => optimal_comparison::report_section_content(), 82 | Optimization::ShortRevertString => short_revert_string::report_section_content(), 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/report/qa_report.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | use std::collections::HashMap; 3 | 4 | use crate::analyzer::qa::QualityAssurance; 5 | use crate::analyzer::utils::LineNumber; 6 | use crate::report::report_sections::qa::overview; 7 | 8 | use super::report_sections::qa::{ 9 | constructor_order, private_func_leading_underscore, private_vars_leading_underscore, 10 | }; 11 | 12 | pub fn generate_qa_report( 13 | qa_items: HashMap)>>, 14 | ) -> String { 15 | let mut qa_report = String::from(""); 16 | 17 | //Add optimization report overview 18 | let overview_section = overview::report_section_content(); 19 | 20 | qa_report.push_str((overview_section + "\n").as_str()); 21 | 22 | for item in qa_items { 23 | if item.1.len() > 0 { 24 | let qa_target = item.0; 25 | let matches = item.1; 26 | 27 | let report_section = get_qa_report_section(qa_target); 28 | 29 | let mut matches_section = String::from("### Lines\n"); 30 | 31 | for (file_name, lines) in matches { 32 | for line in lines { 33 | //- file_name:line_number\n 34 | matches_section 35 | .push_str(&(String::from("- ") + &file_name + ":" + &line.to_string())); 36 | matches_section.push_str("\n"); 37 | } 38 | } 39 | 40 | matches_section.push_str("\n\n"); 41 | 42 | let completed_report_section = report_section + "\n" + matches_section.as_str(); 43 | qa_report.push_str(completed_report_section.as_str()); 44 | } 45 | } 46 | 47 | qa_report 48 | } 49 | 50 | pub fn get_qa_report_section(qa: QualityAssurance) -> String { 51 | match qa { 52 | QualityAssurance::ConstructorOrder => constructor_order::report_section_content(), 53 | QualityAssurance::PrivateVarsLeadingUnderscore => { 54 | private_vars_leading_underscore::report_section_content() 55 | } 56 | QualityAssurance::PrivateFuncLeadingUnderscore => { 57 | private_func_leading_underscore::report_section_content() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/report/report_sections/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod optimizations; 2 | pub mod qa; 3 | pub mod vulnerabilities; 4 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/address_balance.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Use assembly when getting a contract's balance of ETH. 5 | 6 | You can use `selfbalance()` instead of `address(this).balance` when getting your contract's balance of ETH to save gas. Additionally, you can use `balance(address)` instead of `address.balance()` when getting an external contract's balance of ETH. 7 | 8 | ```js 9 | 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | Contract2 c2; 14 | Contract3 c3; 15 | 16 | function setUp() public { 17 | c0 = new Contract0(); 18 | c1 = new Contract1(); 19 | c2 = new Contract2(); 20 | c3 = new Contract3(); 21 | } 22 | 23 | function testGas() public { 24 | c0.addressInternalBalance(); 25 | c1.assemblyInternalBalance(); 26 | c2.addressExternalBalance(address(this)); 27 | c3.assemblyExternalBalance(address(this)); 28 | } 29 | } 30 | 31 | contract Contract0 { 32 | function addressInternalBalance() public returns (uint256) { 33 | return address(this).balance; 34 | } 35 | } 36 | 37 | contract Contract1 { 38 | function assemblyInternalBalance() public returns (uint256) { 39 | assembly { 40 | let c := selfbalance() 41 | mstore(0x00, c) 42 | return(0x00, 0x20) 43 | } 44 | } 45 | } 46 | 47 | contract Contract2 { 48 | function addressExternalBalance(address addr) public { 49 | uint256 bal = address(addr).balance; 50 | bal++; 51 | } 52 | } 53 | 54 | contract Contract3 { 55 | function assemblyExternalBalance(address addr) public { 56 | uint256 bal; 57 | assembly { 58 | bal := balance(addr) 59 | } 60 | bal++; 61 | } 62 | } 63 | ``` 64 | 65 | ### Gas Report 66 | 67 | ```js 68 | ╭────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 69 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 70 | ╞════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 71 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 72 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 73 | │ 23675 ┆ 147 ┆ ┆ ┆ ┆ │ 74 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 75 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 76 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 77 | │ addressInternalBalance ┆ 148 ┆ 148 ┆ 148 ┆ 148 ┆ 1 │ 78 | ╰────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 79 | ╭─────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 80 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 81 | ╞═════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 82 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 83 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 84 | │ 27081 ┆ 165 ┆ ┆ ┆ ┆ │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 87 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 88 | │ assemblyInternalBalance ┆ 133 ┆ 133 ┆ 133 ┆ 133 ┆ 1 │ 89 | ╰─────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 90 | ╭────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 91 | │ Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 92 | ╞════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 93 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 94 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 95 | │ 61511 ┆ 339 ┆ ┆ ┆ ┆ │ 96 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 97 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 98 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 99 | │ addressExternalBalance ┆ 417 ┆ 417 ┆ 417 ┆ 417 ┆ 1 │ 100 | ╰────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 101 | ╭─────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 102 | │ Contract3 contract ┆ ┆ ┆ ┆ ┆ │ 103 | ╞═════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 104 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 105 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 106 | │ 57105 ┆ 317 ┆ ┆ ┆ ┆ │ 107 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 108 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 109 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 110 | │ assemblyExternalBalance ┆ 411 ┆ 411 ┆ 411 ┆ 411 ┆ 1 │ 111 | ╰─────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 112 | 113 | ``` 114 | 115 | 116 | "##, 117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/address_zero.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Use assembly to check for address(0) 5 | 6 | ```js 7 | 8 | 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | 13 | function setUp() public { 14 | c0 = new Contract0(); 15 | c1 = new Contract1(); 16 | } 17 | 18 | function testGas() public view { 19 | c0.ownerNotZero(address(this)); 20 | c1.assemblyOwnerNotZero(address(this)); 21 | } 22 | } 23 | 24 | contract Contract0 { 25 | function ownerNotZero(address _addr) public pure { 26 | require(_addr != address(0), "zero address)"); 27 | } 28 | } 29 | 30 | contract Contract1 { 31 | function assemblyOwnerNotZero(address _addr) public pure { 32 | assembly { 33 | if iszero(_addr) { 34 | mstore(0x00, "zero address") 35 | revert(0x00, 0x20) 36 | } 37 | } 38 | } 39 | } 40 | 41 | 42 | ``` 43 | 44 | ### Gas Report 45 | 46 | ```js 47 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 48 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 49 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 50 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 51 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 52 | │ 61311 ┆ 338 ┆ ┆ ┆ ┆ │ 53 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 54 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 55 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 56 | │ ownerNotZero ┆ 258 ┆ 258 ┆ 258 ┆ 258 ┆ 1 │ 57 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 58 | ╭──────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 59 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 60 | ╞══════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 61 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 62 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 63 | │ 44893 ┆ 255 ┆ ┆ ┆ ┆ │ 64 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 65 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 66 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 67 | │ assemblyOwnerNotZero ┆ 252 ┆ 252 ┆ 252 ┆ 252 ┆ 1 │ 68 | ╰──────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 69 | ``` 70 | "##, 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/assign_update_array_value.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## `array[index] += amount` is cheaper than `array[index] = array[index] + amount` (or related variants) 5 | When updating a value in an array with arithmetic, using `array[index] += amount` is cheaper than `array[index] = array[index] + amount`. This is because you avoid an additonal `mload` when the array is stored in memory, and an `sload` when the array is stored in storage. This can be applied for any arithmetic operation including `+=`, `-=`,`/=`,`*=`,`^=`,`&=`, `%=`, `<<=`,`>>=`, and `>>>=`. This optimization can be particularly significant if the pattern occurs during a loop. 6 | 7 | ```js 8 | 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | Contract2 c2; 13 | Contract3 c3; 14 | 15 | function setUp() public { 16 | c0 = new Contract0(); 17 | c1 = new Contract1(); 18 | c2 = new Contract2(); 19 | c3 = new Contract3(); 20 | } 21 | 22 | function testGas() public { 23 | uint256[] memory data = new uint256[](10); 24 | 25 | uint256 newAmount = 100000; 26 | c0.assignMemory(data, newAmount); 27 | c1.assignAddMemory(data, newAmount); 28 | 29 | c2.assignStorage(newAmount); 30 | c3.assignAddStorage(newAmount); 31 | } 32 | } 33 | 34 | contract Contract0 { 35 | function assignMemory(uint256[] memory amounts, uint256 newAmount) public { 36 | amounts[0] = amounts[0] + newAmount; 37 | } 38 | } 39 | 40 | contract Contract1 { 41 | function assignAddMemory(uint256[] memory amounts, uint256 newAmount) 42 | public 43 | { 44 | amounts[0] += newAmount; 45 | } 46 | } 47 | 48 | contract Contract2 { 49 | uint256[] amounts; 50 | 51 | constructor() { 52 | amounts = new uint256[](10); 53 | } 54 | 55 | function assignStorage(uint256 newAmount) public { 56 | amounts[0] = amounts[0] + newAmount; 57 | } 58 | } 59 | 60 | contract Contract3 { 61 | uint256[] amounts; 62 | 63 | constructor() { 64 | amounts = new uint256[](10); 65 | } 66 | 67 | function assignAddStorage(uint256 newAmount) public { 68 | amounts[0] += newAmount; 69 | newAmount++; 70 | } 71 | } 72 | ``` 73 | 74 | ### Gas Report 75 | 76 | ```js 77 | ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 78 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 79 | ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 80 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 81 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 82 | │ 179420 ┆ 928 ┆ ┆ ┆ ┆ │ 83 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 84 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ assignMemory ┆ 3611 ┆ 3611 ┆ 3611 ┆ 3611 ┆ 1 │ 87 | ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 88 | ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 89 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 90 | ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 91 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 92 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 93 | │ 174820 ┆ 905 ┆ ┆ ┆ ┆ │ 94 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 95 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 96 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 97 | │ assignAddMemory ┆ 3573 ┆ 3573 ┆ 3573 ┆ 3573 ┆ 1 │ 98 | ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 99 | ╭───────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 100 | │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 101 | ╞═══════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 102 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 103 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 104 | │ 144011 ┆ 779 ┆ ┆ ┆ ┆ │ 105 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 106 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 107 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 108 | │ assignStorage ┆ 25052 ┆ 25052 ┆ 25052 ┆ 25052 ┆ 1 │ 109 | ╰───────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 110 | ╭───────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 111 | │ src/test/GasTest.t.sol:Contract3 contract ┆ ┆ ┆ ┆ ┆ │ 112 | ╞═══════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 113 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 114 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 115 | │ 156623 ┆ 842 ┆ ┆ ┆ ┆ │ 116 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 117 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 118 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 119 | │ assignAddStorage ┆ 25024 ┆ 25024 ┆ 25024 ┆ 25024 ┆ 1 │ 120 | ╰───────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 121 | 122 | ``` 123 | 124 | "##, 125 | ) 126 | } 127 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/bool_equals_bool.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Instead of `if (x == bool)`, use `if(x)` or when applicable, use assembly with `iszero(iszero(x))`. 6 | It is redundant to check `if(x == true)` or any form of boolean comparison. You can slightly reduce gas consumption by using `if (x)` instead. When applicable, you can also use assembly to save more gas by using `iszeroiszero(x)` instead of `if (x)` and `iszero(x)` for `if (!x)` 7 | 8 | ```js 9 | 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | Contract2 c2; 14 | 15 | function setUp() public { 16 | c0 = new Contract0(); 17 | c1 = new Contract1(); 18 | c2 = new Contract2(); 19 | } 20 | 21 | function testGas() public view { 22 | bool check = true; 23 | c0.ifBoolEqualsBool(check); 24 | c1.ifBool(check); 25 | c2.iszeroIszero(check); 26 | } 27 | } 28 | 29 | contract Contract0 { 30 | function ifBoolEqualsBool(bool check) public pure { 31 | if (check == true) { 32 | return; 33 | } 34 | } 35 | } 36 | 37 | contract Contract1 { 38 | function ifBool(bool check) public pure { 39 | if (check) { 40 | return; 41 | } 42 | } 43 | } 44 | 45 | contract Contract2 { 46 | function iszeroIszero(bool check) public pure { 47 | assembly { 48 | if iszero(iszero(check)) { 49 | revert(0x00, 0x00) 50 | } 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | ### Gas Report 57 | ```js 58 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 59 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 60 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 61 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 62 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 63 | │ 34087 ┆ 200 ┆ ┆ ┆ ┆ │ 64 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 65 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 66 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 67 | │ ifBoolEqualsBool ┆ 263 ┆ 263 ┆ 263 ┆ 263 ┆ 1 │ 68 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 69 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 70 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 71 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 72 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 73 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 74 | │ 33287 ┆ 196 ┆ ┆ ┆ ┆ │ 75 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 76 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 77 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 78 | │ ifBool ┆ 254 ┆ 254 ┆ 254 ┆ 254 ┆ 1 │ 79 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 80 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 81 | │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 82 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 83 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 84 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 85 | │ 33687 ┆ 198 ┆ ┆ ┆ ┆ │ 86 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 87 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 88 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 89 | │ iszeroIszero ┆ 249 ┆ 249 ┆ 249 ┆ 249 ┆ 1 │ 90 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 91 | 92 | ``` 93 | 94 | "##, 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/constant_variable.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Mark storage variables as `constant` if they never change. 5 | 6 | State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time. 7 | 8 | The compiler does not reserve a storage slot for these variables, and every occurrence is inlined by the respective value. 9 | 10 | Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values. 11 | 12 | 13 | ```js 14 | 15 | contract GasTest is DSTest { 16 | Contract0 c0; 17 | Contract1 c1; 18 | Contract2 c2; 19 | 20 | function setUp() public { 21 | c0 = new Contract0(); 22 | c1 = new Contract1(); 23 | c2 = new Contract2(); 24 | 25 | } 26 | 27 | function testGas() public view { 28 | c0.addValue(); 29 | c1.addImmutableValue(); 30 | c2.addConstantValue(); 31 | } 32 | } 33 | 34 | contract Contract0 { 35 | uint256 val; 36 | 37 | constructor() { 38 | val = 10000; 39 | } 40 | 41 | function addValue() public view { 42 | uint256 newVal = val + 1000; 43 | } 44 | } 45 | 46 | contract Contract1 { 47 | uint256 immutable val; 48 | 49 | constructor() { 50 | val = 10000; 51 | } 52 | 53 | function addImmutableValue() public view { 54 | uint256 newVal = val + 1000; 55 | } 56 | } 57 | 58 | contract Contract2 { 59 | uint256 constant val = 10; 60 | 61 | function addConstantValue() public view { 62 | uint256 newVal = val + 1000; 63 | } 64 | } 65 | 66 | ``` 67 | 68 | ### Gas Report 69 | ```js 70 | ╭────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 71 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 72 | ╞════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 73 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 74 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 75 | │ 54593 ┆ 198 ┆ ┆ ┆ ┆ │ 76 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 77 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 78 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 79 | │ addValue ┆ 2302 ┆ 2302 ┆ 2302 ┆ 2302 ┆ 1 │ 80 | ╰────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 81 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 82 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 83 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 84 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ 38514 ┆ 239 ┆ ┆ ┆ ┆ │ 87 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 88 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 89 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 90 | │ addImmutableValue ┆ 199 ┆ 199 ┆ 199 ┆ 199 ┆ 1 │ 91 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 92 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 93 | │ Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 94 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 95 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 96 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 97 | │ 32287 ┆ 191 ┆ ┆ ┆ ┆ │ 98 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 99 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 100 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 101 | │ addConstantValue ┆ 199 ┆ 199 ┆ 199 ┆ 199 ┆ 1 │ 102 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 103 | ``` 104 | 105 | 106 | "##, 107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/immutable_variable.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Mark storage variables as `immutable` if they never change after contract initialization. 5 | 6 | State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time. 7 | 8 | The compiler does not reserve a storage slot for these variables, and every occurrence is inlined by the respective value. 9 | 10 | Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values. 11 | 12 | 13 | ```js 14 | 15 | contract GasTest is DSTest { 16 | Contract0 c0; 17 | Contract1 c1; 18 | Contract2 c2; 19 | 20 | function setUp() public { 21 | c0 = new Contract0(); 22 | c1 = new Contract1(); 23 | c2 = new Contract2(); 24 | 25 | } 26 | 27 | function testGas() public view { 28 | c0.addValue(); 29 | c1.addImmutableValue(); 30 | c2.addConstantValue(); 31 | } 32 | } 33 | 34 | contract Contract0 { 35 | uint256 val; 36 | 37 | constructor() { 38 | val = 10000; 39 | } 40 | 41 | function addValue() public view { 42 | uint256 newVal = val + 1000; 43 | } 44 | } 45 | 46 | contract Contract1 { 47 | uint256 immutable val; 48 | 49 | constructor() { 50 | val = 10000; 51 | } 52 | 53 | function addImmutableValue() public view { 54 | uint256 newVal = val + 1000; 55 | } 56 | } 57 | 58 | contract Contract2 { 59 | uint256 constant val = 10; 60 | 61 | function addConstantValue() public view { 62 | uint256 newVal = val + 1000; 63 | } 64 | } 65 | 66 | ``` 67 | 68 | ### Gas Report 69 | ```js 70 | ╭────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 71 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 72 | ╞════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 73 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 74 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 75 | │ 54593 ┆ 198 ┆ ┆ ┆ ┆ │ 76 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 77 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 78 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 79 | │ addValue ┆ 2302 ┆ 2302 ┆ 2302 ┆ 2302 ┆ 1 │ 80 | ╰────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 81 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 82 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 83 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 84 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ 38514 ┆ 239 ┆ ┆ ┆ ┆ │ 87 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 88 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 89 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 90 | │ addImmutableValue ┆ 199 ┆ 199 ┆ 199 ┆ 199 ┆ 1 │ 91 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 92 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 93 | │ Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 94 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 95 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 96 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 97 | │ 32287 ┆ 191 ┆ ┆ ┆ ┆ │ 98 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 99 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 100 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 101 | │ addConstantValue ┆ 199 ┆ 199 ┆ 199 ┆ 199 ┆ 1 │ 102 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 103 | ``` 104 | 105 | "##, 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/memory_to_calldata.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Use `calldata` instead of `memory` for function arguments that do not get mutated. 5 | Mark data types as `calldata` instead of `memory` where possible. This makes it so that the data is not automatically loaded into memory. If the data passed into the function does not need to be changed (like updating values in an array), it can be passed in as `calldata`. The one exception to this is if the argument must later be passed into another function that takes an argument that specifies `memory` storage. 6 | 7 | 8 | ```js 9 | 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | Contract2 c2; 14 | Contract3 c3; 15 | 16 | function setUp() public { 17 | c0 = new Contract0(); 18 | c1 = new Contract1(); 19 | c2 = new Contract2(); 20 | c3 = new Contract3(); 21 | } 22 | 23 | function testGas() public { 24 | uint256[] memory arr = new uint256[](10); 25 | c0.calldataArray(arr); 26 | c1.memoryArray(arr); 27 | 28 | bytes memory data = abi.encode("someText"); 29 | c2.calldataBytes(data); 30 | c3.memoryBytes(data); 31 | } 32 | } 33 | 34 | contract Contract0 { 35 | function calldataArray(uint256[] calldata arr) public { 36 | uint256 j; 37 | for (uint256 i; i < arr.length; i++) { 38 | j = arr[i] + 10; 39 | } 40 | } 41 | } 42 | 43 | contract Contract1 { 44 | function memoryArray(uint256[] memory arr) public { 45 | uint256 j; 46 | for (uint256 i; i < arr.length; i++) { 47 | j = arr[i] + 10; 48 | } 49 | } 50 | } 51 | 52 | contract Contract2 { 53 | function calldataBytes(bytes calldata data) public { 54 | bytes32 val; 55 | for (uint256 i; i < 10; i++) { 56 | val = keccak256(abi.encode(data, i)); 57 | } 58 | } 59 | } 60 | 61 | contract Contract3 { 62 | function memoryBytes(bytes memory data) public { 63 | bytes32 val; 64 | for (uint256 i; i < 10; i++) { 65 | val = keccak256(abi.encode(data, i)); 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ### Gas Report 72 | ```js 73 | ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 74 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 75 | ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 76 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 77 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 78 | │ 97947 ┆ 521 ┆ ┆ ┆ ┆ │ 79 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 80 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 81 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 82 | │ calldataArray ┆ 2824 ┆ 2824 ┆ 2824 ┆ 2824 ┆ 1 │ 83 | ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 84 | ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 85 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 86 | ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 87 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 88 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 89 | │ 128171 ┆ 672 ┆ ┆ ┆ ┆ │ 90 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 91 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 92 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 93 | │ memoryArray ┆ 3755 ┆ 3755 ┆ 3755 ┆ 3755 ┆ 1 │ 94 | ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 95 | ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 96 | │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 97 | ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 98 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 99 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 100 | │ 100547 ┆ 534 ┆ ┆ ┆ ┆ │ 101 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 102 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 103 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 104 | │ calldataBytes ┆ 4934 ┆ 4934 ┆ 4934 ┆ 4934 ┆ 1 │ 105 | ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 106 | ╭───────────────────────────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 107 | │ src/test/GasTest.t.sol:Contract3 contract ┆ ┆ ┆ ┆ ┆ │ 108 | ╞═══════════════════════════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 109 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 110 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 111 | │ 135183 ┆ 707 ┆ ┆ ┆ ┆ │ 112 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 113 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 114 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 115 | │ memoryBytes ┆ 7551 ┆ 7551 ┆ 7551 ┆ 7551 ┆ 1 │ 116 | ╰───────────────────────────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 117 | 118 | ``` 119 | "##, 120 | ) 121 | } 122 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address_balance; 2 | pub mod address_zero; 3 | pub mod assign_update_array_value; 4 | pub mod bool_equals_bool; 5 | pub mod cache_array_length; 6 | pub mod constant_variable; 7 | pub mod immutable_variable; 8 | pub mod increment_decrement; 9 | pub mod memory_to_calldata; 10 | pub mod multiple_require; 11 | pub mod optimal_comparison; 12 | pub mod overview; 13 | pub mod pack_storage_variables; 14 | pub mod pack_struct_variables; 15 | pub mod payable_function; 16 | pub mod private_constant; 17 | pub mod safe_math_post_080; 18 | pub mod safe_math_pre_080; 19 | pub mod shift_math; 20 | pub mod short_revert_string; 21 | pub mod solidity_keccak256; 22 | pub mod solidity_math; 23 | pub mod sstore; 24 | pub mod string_errors; 25 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/multiple_require.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Use multiple require() statments insted of require(expression && expression && ...) 6 | You can safe gas by breaking up a require statement with multiple conditions, into multiple require statements with a single condition. 7 | 8 | ```js 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | 13 | function setUp() public { 14 | c0 = new Contract0(); 15 | c1 = new Contract1(); 16 | } 17 | 18 | function testGas() public { 19 | c0.singleRequire(3); 20 | c1.multipleRequire(3); 21 | } 22 | } 23 | 24 | contract Contract0 { 25 | function singleRequire(uint256 num) public { 26 | require(num > 1 && num < 10 && num == 3); 27 | } 28 | } 29 | 30 | contract Contract1 { 31 | function multipleRequire(uint256 num) public { 32 | require(num > 1); 33 | require(num < 10); 34 | require(num == 3); 35 | } 36 | } 37 | ``` 38 | 39 | ### Gas Report 40 | 41 | ```js 42 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 43 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 44 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 45 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 46 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 47 | │ 35487 ┆ 208 ┆ ┆ ┆ ┆ │ 48 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 49 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 50 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 51 | │ singleRequire ┆ 286 ┆ 286 ┆ 286 ┆ 286 ┆ 1 │ 52 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 53 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 54 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 55 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 56 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 57 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 58 | │ 35887 ┆ 210 ┆ ┆ ┆ ┆ │ 59 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 60 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 61 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 62 | │ multipleRequire ┆ 270 ┆ 270 ┆ 270 ┆ 270 ┆ 1 │ 63 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 64 | 65 | ``` 66 | 67 | "##, 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/optimal_comparison.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Optimal Comparison 5 | 6 | When comparing integers, it is cheaper to use strict `>` & `<` operators over `>=` & `<=` operators, even if you must increment or decrement one of the operands. Note: before using this technique, it's important to consider whether incrementing/decrementing one of the operators could result in an over/underflow. 7 | This optimization is applicable when the optimizer is turned off. 8 | 9 | ```js 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | Contract2 c2; 14 | Contract3 c3; 15 | 16 | function setUp() public { 17 | c0 = new Contract0(); 18 | c1 = new Contract1(); 19 | c2 = new Contract2(); 20 | c3 = new Contract3(); 21 | } 22 | 23 | function testGas() public view { 24 | c0.gte(); 25 | c1.gtPlusMinusOne(); 26 | c2.lte(); 27 | c3.ltPlusOne(); 28 | } 29 | } 30 | 31 | contract Contract0 { 32 | function gte() external pure returns (bool) { 33 | return 2 >= 2; 34 | } 35 | } 36 | 37 | contract Contract1 { 38 | function gtPlusMinusOne() external pure returns (bool) { 39 | return 2 > 2 - 1; 40 | } 41 | } 42 | 43 | contract Contract2 { 44 | function lte() external pure returns (bool) { 45 | return 2 <= 2; 46 | } 47 | } 48 | 49 | contract Contract3 { 50 | function ltPlusOne() external pure returns (bool) { 51 | return 2 < 2 + 1; 52 | } 53 | } 54 | 55 | ``` 56 | 57 | ### Gas Report 58 | 59 | ```js 60 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 61 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 62 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 63 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 64 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 65 | │ 37487 ┆ 218 ┆ ┆ ┆ ┆ │ 66 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 67 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 68 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 69 | │ gte ┆ 330 ┆ 330 ┆ 330 ┆ 330 ┆ 1 │ 70 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 71 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 72 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 73 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 74 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 75 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 76 | │ 37487 ┆ 218 ┆ ┆ ┆ ┆ │ 77 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 78 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 79 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 80 | │ gtPlusMinusOne ┆ 327 ┆ 327 ┆ 327 ┆ 327 ┆ 1 │ 81 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 82 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 83 | │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 84 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 85 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 86 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 87 | │ 37487 ┆ 218 ┆ ┆ ┆ ┆ │ 88 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 89 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 90 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 91 | │ lte ┆ 330 ┆ 330 ┆ 330 ┆ 330 ┆ 1 │ 92 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 93 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 94 | │ src/test/GasTest.t.sol:Contract3 contract ┆ ┆ ┆ ┆ ┆ │ 95 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 96 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 97 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 98 | │ 37487 ┆ 218 ┆ ┆ ┆ ┆ │ 99 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 100 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 101 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 102 | │ ltPlusOne ┆ 327 ┆ 327 ┆ 327 ┆ 327 ┆ 1 │ 103 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 104 | ``` 105 | 106 | 107 | "##, 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/overview.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content(total_optimizations: usize) -> String { 2 | String::from( 3 | format!("# Gas Optimizations - (Total Optimizations {})\n 4 | The following sections detail the gas optimizations found throughout the codebase. 5 | Each optimization is documented with the setup, an explainer for the optimization, a gas report and line identifiers for each optimization across the codebase. For each section's gas report, the optimizer was turned on and set to 10000 runs. 6 | You can replicate any tests/gas reports by heading to [0xKitsune/gas-lab](https://github.com/0xKitsune/gas-lab) and cloning the repo. Then, simply copy/paste the contract examples from any section and run `forge test --gas-report`. 7 | You can also easily update the optimizer runs in the `foundry.toml`.\n\n
\n 8 | ", total_optimizations) 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/pack_storage_variables.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Tightly pack storage variables 5 | When defining storage variables, make sure to declare them in ascending order, according to size. When multiple variables are able to fit into one 256 bit slot, this will save storage size and gas during runtime. For example, if you have a `bool`, `uint256` and a `bool`, instead of defining the variables in the previously mentioned order, defining the two boolean variables first will pack them both into one storage slot since they only take up one byte of storage. 6 | 7 | ```js 8 | 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | 13 | function setUp() public { 14 | c0 = new Contract0(); 15 | c1 = new Contract1(); 16 | } 17 | 18 | function testGas() public { 19 | bool bool0 = true; 20 | bool bool1 = false; 21 | uint256 num0 = 200; 22 | uint256 num1 = 100; 23 | c0.accessNonTightlyPacked(bool0, bool1, num0, num1); 24 | c1.accessTightlyPacked(bool0, bool1, num0, num1); 25 | } 26 | } 27 | 28 | contract Contract0 { 29 | uint256 num0 = 100; 30 | bool bool0 = false; 31 | uint256 num1 = 200; 32 | bool bool1 = true; 33 | 34 | function accessNonTightlyPacked( 35 | bool _bool0, 36 | bool _bool1, 37 | uint256 _num0, 38 | uint256 _num1 39 | ) public { 40 | bool0 = _bool0; 41 | bool1 = _bool1; 42 | num0 = _num0; 43 | num1 = _num1; 44 | } 45 | } 46 | 47 | contract Contract1 { 48 | bool bool0 = false; 49 | bool bool1 = true; 50 | uint256 num0 = 100; 51 | uint256 num1 = 200; 52 | 53 | function accessTightlyPacked( 54 | bool _bool0, 55 | bool _bool1, 56 | uint256 _num0, 57 | uint256 _num1 58 | ) public { 59 | bool0 = _bool0; 60 | bool1 = _bool1; 61 | num0 = _num0; 62 | num1 = _num1; 63 | } 64 | } 65 | 66 | ``` 67 | 68 | ### Gas Report 69 | ```js 70 | ╭───────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 71 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 72 | ╞═══════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 73 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 74 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 75 | │ 122268 ┆ 334 ┆ ┆ ┆ ┆ │ 76 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 77 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 78 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 79 | │ accessNonTightlyPacked ┆ 32774 ┆ 32774 ┆ 32774 ┆ 32774 ┆ 1 │ 80 | ╰───────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 81 | ╭───────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 82 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 83 | ╞═══════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 84 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ 126247 ┆ 356 ┆ ┆ ┆ ┆ │ 87 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 88 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 89 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 90 | │ accessTightlyPacked ┆ 15476 ┆ 15476 ┆ 15476 ┆ 15476 ┆ 1 │ 91 | ╰───────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 92 | 93 | ``` 94 | "##, 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/pack_struct_variables.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Pack structs 5 | When creating structs, make sure that the variables are listed in ascending order by data type. The compiler will pack the variables that can fit into one 32 byte slot. If the variables are not listed in ascending order, the compiler may not pack the data into one slot, causing additional `sload` and `sstore` instructions when reading/storing the struct into the contract's storage. 6 | 7 | ```js 8 | 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | 13 | function setUp() public { 14 | c0 = new Contract0(); 15 | c1 = new Contract1(); 16 | } 17 | 18 | function testGas() public { 19 | c0.storeUnoptimizedStruct(); 20 | c1.storeOptimizedStruct(); 21 | } 22 | } 23 | 24 | contract Contract0 { 25 | struct UnoptimizedStruct { 26 | uint128 a; 27 | uint256 b; 28 | uint128 c; 29 | } 30 | 31 | mapping(uint256 => UnoptimizedStruct) idToUnoptimizedStruct; 32 | 33 | function storeUnoptimizedStruct() public { 34 | idToUnoptimizedStruct[0] = UnoptimizedStruct( 35 | 23409234, 36 | 23489234982349, 37 | 234923093 38 | ); 39 | } 40 | } 41 | 42 | contract Contract1 { 43 | struct OptimizedStruct { 44 | uint128 a; 45 | uint128 b; 46 | uint256 c; 47 | } 48 | 49 | mapping(uint256 => OptimizedStruct) idToOptimizedStruct; 50 | 51 | function storeOptimizedStruct() public { 52 | idToOptimizedStruct[0] = OptimizedStruct( 53 | 23409234, 54 | 23489234982349, 55 | 234923093 56 | ); 57 | } 58 | } 59 | ``` 60 | 61 | ### Gas Report 62 | ```js 63 | ╭────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 64 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 65 | ╞════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 66 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 67 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 68 | │ 67717 ┆ 370 ┆ ┆ ┆ ┆ │ 69 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 70 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 71 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 72 | │ storeUnoptimizedStruct ┆ 66608 ┆ 66608 ┆ 66608 ┆ 66608 ┆ 1 │ 73 | ╰────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 74 | ╭──────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 75 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 76 | ╞══════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 77 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 78 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 79 | │ 54105 ┆ 302 ┆ ┆ ┆ ┆ │ 80 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 81 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 82 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 83 | │ storeOptimizedStruct ┆ 44468 ┆ 44468 ┆ 44468 ┆ 44468 ┆ 1 │ 84 | ╰──────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 85 | 86 | ``` 87 | 88 | "##, 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/payable_function.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Mark functions as payable (with discretion) 5 | You can mark public or external functions as payable to save gas. Functions that are not payable have additional logic to check if there was a value sent with a call, however, making a function payable eliminates this check. This optimization should be carefully considered due to potentially unwanted behavior when a function does not need to accept ether. 6 | 7 | ```js 8 | contract GasTest is DSTest { 9 | Contract0 c0; 10 | Contract1 c1; 11 | 12 | function setUp() public { 13 | c0 = new Contract0(); 14 | c1 = new Contract1(); 15 | } 16 | 17 | function testGas() public { 18 | c0.isNotPayable(); 19 | c1.isPayable(); 20 | } 21 | } 22 | 23 | contract Contract0 { 24 | function isNotPayable() public view { 25 | uint256 val = 0; 26 | val++; 27 | } 28 | } 29 | 30 | contract Contract1 { 31 | function isPayable() public payable { 32 | uint256 val = 0; 33 | val++; 34 | } 35 | } 36 | ``` 37 | 38 | ### Gas Report 39 | ```js 40 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 41 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 42 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 43 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 44 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 45 | │ 32081 ┆ 190 ┆ ┆ ┆ ┆ │ 46 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 47 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 48 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 49 | │ isNotPayable ┆ 198 ┆ 198 ┆ 198 ┆ 198 ┆ 1 │ 50 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 51 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 52 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 53 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 54 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 55 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 56 | │ 29681 ┆ 178 ┆ ┆ ┆ ┆ │ 57 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 58 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 59 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 60 | │ isPayable ┆ 174 ┆ 174 ┆ 174 ┆ 174 ┆ 1 │ 61 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 62 | ``` 63 | 64 | 65 | "##, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/private_constant.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Consider marking constants as private 6 | 7 | Consider marking constant variables in storage as private to save gas (unless a constant variable should be easily accessible by another protocol or offchain logic). 8 | 9 | ```js 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | 14 | function setUp() public { 15 | c0 = new Contract0(); 16 | c1 = new Contract1(); 17 | 18 | } 19 | function testGas() public view { 20 | uint256 a = 100; 21 | c0.addPublicConstant(a); 22 | c1.addPrivateConstant(a); 23 | 24 | } 25 | } 26 | contract Contract0 { 27 | 28 | uint256 constant public x = 100; 29 | 30 | function addPublicConstant(uint256 a) external pure returns (uint256) { 31 | return a + x; 32 | } 33 | } 34 | 35 | contract Contract1 { 36 | 37 | uint256 constant private x = 100; 38 | 39 | function addPrivateConstant(uint256 a) external pure returns (uint256) { 40 | return a +x; 41 | } 42 | } 43 | ``` 44 | 45 | ### Gas Report 46 | 47 | ```js 48 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 49 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 50 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 51 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 52 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 53 | │ 92741 ┆ 495 ┆ ┆ ┆ ┆ │ 54 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 55 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 56 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 57 | │ addPublicConstant ┆ 790 ┆ 790 ┆ 790 ┆ 790 ┆ 1 │ 58 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 59 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 60 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 61 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 62 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 63 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 64 | │ 83535 ┆ 449 ┆ ┆ ┆ ┆ │ 65 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 66 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 67 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 68 | │ addPrivateConstant ┆ 768 ┆ 768 ┆ 768 ┆ 768 ┆ 1 │ 69 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 70 | ``` 71 | 72 | "##, 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/safe_math_post_080.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Don't use SafeMath when using solidity >= 0.8.0 6 | 7 | Solidity >= 0.8.0 checks for overflow/underflow by default. Using Safemath when using version >= 0.8.0 is redundant and will incur additional gas costs. Instead of safemath, you can simply use Solidity's built in arithmetic. For further gas savings, you can also use assembly and check for overflow/underflow as seen below. 8 | 9 | ```js 10 | 11 | contract GasTest is DSTest { 12 | Contract0 c0; 13 | Contract1 c1; 14 | Contract2 c2; 15 | 16 | function setUp() public { 17 | c0 = new Contract0(); 18 | c1 = new Contract1(); 19 | c2 = new Contract2(); 20 | } 21 | 22 | function testGas() public { 23 | uint256 a = 109230923590; 24 | uint256 b = 928359823498234; 25 | c0.safeMathAdd(a, b); 26 | c1.standardAdd(a, b); 27 | c2.assemblyAdd(a, b); 28 | } 29 | } 30 | 31 | contract Contract0 { 32 | using SafeMath for uint256; 33 | 34 | function safeMathAdd(uint256 a, uint256 b) public { 35 | uint256 c = a.add(b); 36 | } 37 | } 38 | 39 | contract Contract1 { 40 | function standardAdd(uint256 a, uint256 b) public { 41 | uint256 c = a + b; 42 | } 43 | } 44 | 45 | contract Contract2 { 46 | function assemblyAdd(uint256 a, uint256 b) public { 47 | assembly { 48 | let c := add(a, b) 49 | } 50 | } 51 | } 52 | 53 | 54 | ``` 55 | 56 | ### Gas report 57 | 58 | ```js 59 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 60 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 61 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 62 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 63 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 64 | │ 48899 ┆ 275 ┆ ┆ ┆ ┆ │ 65 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 66 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 67 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 68 | │ safeMathAdd ┆ 348 ┆ 348 ┆ 348 ┆ 348 ┆ 1 │ 69 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 70 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 71 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 72 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 73 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 74 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 75 | │ 45499 ┆ 258 ┆ ┆ ┆ ┆ │ 76 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 77 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 78 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 79 | │ standardAdd ┆ 303 ┆ 303 ┆ 303 ┆ 303 ┆ 1 │ 80 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 81 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 82 | │ Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 83 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 84 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ 41293 ┆ 237 ┆ ┆ ┆ ┆ │ 87 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 88 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 89 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 90 | │ assemblyAdd ┆ 263 ┆ 263 ┆ 263 ┆ 263 ┆ 1 │ 91 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 92 | ``` 93 | 94 | "##, 95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/shift_math.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Right shift or Left shift instead of dividing or multiplying by powers of two 6 | 7 | 8 | ```js 9 | 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | Contract2 c2; 14 | Contract3 c3; 15 | 16 | function setUp() public { 17 | c0 = new Contract0(); 18 | c1 = new Contract1(); 19 | c2 = new Contract2(); 20 | c3 = new Contract3(); 21 | } 22 | 23 | function testGas() public view { 24 | c0.mul2(); 25 | c1.shl2(); 26 | c2.div2(); 27 | c3.shr2(); 28 | } 29 | } 30 | 31 | contract Contract0 { 32 | function mul2() public pure { 33 | uint256 val = 10; 34 | uint256 valMulTwo = val * 2; 35 | valMulTwo++; 36 | } 37 | } 38 | 39 | contract Contract1 { 40 | function shl2() public pure { 41 | uint256 val = 10; 42 | uint256 valMulTwo = val << 1; 43 | valMulTwo++; 44 | } 45 | } 46 | 47 | contract Contract2 { 48 | function div2() public pure { 49 | uint256 val = 10; 50 | uint256 valDivTwo = val / 2; 51 | valDivTwo++; 52 | } 53 | } 54 | 55 | contract Contract3 { 56 | function shr2() public pure { 57 | uint256 val = 10; 58 | uint256 valDivTwo = val >> 1; 59 | valDivTwo++; 60 | } 61 | } 62 | 63 | 64 | ``` 65 | 66 | ### Gas Report 67 | 68 | ```js 69 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 70 | │ src/test/GasTest.t.sol:Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 71 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 72 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 73 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 74 | │ 58911 ┆ 326 ┆ ┆ ┆ ┆ │ 75 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 76 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 77 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 78 | │ mul2 ┆ 297 ┆ 297 ┆ 297 ┆ 297 ┆ 1 │ 79 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 80 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 81 | │ src/test/GasTest.t.sol:Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 82 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 83 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 84 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 85 | │ 43893 ┆ 250 ┆ ┆ ┆ ┆ │ 86 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 87 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 88 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 89 | │ shl2 ┆ 203 ┆ 203 ┆ 203 ┆ 203 ┆ 1 │ 90 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 91 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 92 | │ src/test/GasTest.t.sol:Contract2 contract ┆ ┆ ┆ ┆ ┆ │ 93 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 94 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 95 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 96 | │ 57705 ┆ 320 ┆ ┆ ┆ ┆ │ 97 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 98 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 99 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 100 | │ div2 ┆ 268 ┆ 268 ┆ 268 ┆ 268 ┆ 1 │ 101 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 102 | ╭───────────────────────────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 103 | │ src/test/GasTest.t.sol:Contract3 contract ┆ ┆ ┆ ┆ ┆ │ 104 | ╞═══════════════════════════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 105 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 106 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 107 | │ 43893 ┆ 250 ┆ ┆ ┆ ┆ │ 108 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 109 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 110 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 111 | │ shr2 ┆ 203 ┆ 203 ┆ 203 ┆ 203 ┆ 1 │ 112 | ╰───────────────────────────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 113 | 114 | ``` 115 | 116 | 117 | "##, 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/short_revert_string.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Short Revert Strings 5 | 6 | Keeping revert strings under 32-bytes prevents the string from being stored in more than one memory slot. 7 | 8 | ```js 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | 13 | function setUp() public { 14 | c0 = new Contract0(); 15 | c1 = new Contract1(); 16 | } 17 | 18 | function testGas() public { 19 | try c0.callRevertExpensive() {} catch {} 20 | try c1.callRevertCheap() {} catch {} 21 | } 22 | } 23 | 24 | contract Contract0 { 25 | function callRevertExpensive() external { 26 | require(false, "long revert string over 32 bytes"); 27 | } 28 | } 29 | 30 | contract Contract1 { 31 | function callRevertCheap() external { 32 | require(false, "revert string under 32 bytes"); 33 | } 34 | } 35 | 36 | ``` 37 | 38 | ### Gas Report 39 | 40 | ```js 41 | ╭─────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 42 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 43 | ╞═════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 44 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 45 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 46 | │ 27487 ┆ 164 ┆ ┆ ┆ ┆ │ 47 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 48 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 49 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 50 | │ callRevertExpensive ┆ 213 ┆ 213 ┆ 213 ┆ 213 ┆ 1 │ 51 | ╰─────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 52 | ╭─────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 53 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 54 | ╞═════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 55 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 56 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 57 | │ 27487 ┆ 164 ┆ ┆ ┆ ┆ │ 58 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 59 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 60 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 61 | │ callRevertCheap ┆ 210 ┆ 210 ┆ 210 ┆ 210 ┆ 1 │ 62 | ╰─────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 63 | ``` 64 | 65 | 66 | "##, 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/solidity_keccak256.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Use assembly to hash instead of Solidity 6 | 7 | ```js 8 | 9 | contract GasTest is DSTest { 10 | Contract0 c0; 11 | Contract1 c1; 12 | 13 | function setUp() public { 14 | c0 = new Contract0(); 15 | c1 = new Contract1(); 16 | } 17 | 18 | function testGas() public view { 19 | c0.solidityHash(2309349, 2304923409); 20 | c1.assemblyHash(2309349, 2304923409); 21 | } 22 | } 23 | 24 | contract Contract0 { 25 | function solidityHash(uint256 a, uint256 b) public view { 26 | //unoptimized 27 | keccak256(abi.encodePacked(a, b)); 28 | } 29 | } 30 | 31 | contract Contract1 { 32 | function assemblyHash(uint256 a, uint256 b) public view { 33 | //optimized 34 | assembly { 35 | mstore(0x00, a) 36 | mstore(0x20, b) 37 | let hashedVal := keccak256(0x00, 0x40) 38 | } 39 | } 40 | } 41 | ``` 42 | 43 | ### Gas Report 44 | 45 | ```js 46 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 47 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 48 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 49 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 50 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 51 | │ 36687 ┆ 214 ┆ ┆ ┆ ┆ │ 52 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 53 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 54 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 55 | │ solidityHash ┆ 313 ┆ 313 ┆ 313 ┆ 313 ┆ 1 │ 56 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 57 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 58 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 59 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 60 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 61 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 62 | │ 31281 ┆ 186 ┆ ┆ ┆ ┆ │ 63 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 64 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 65 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 66 | │ assemblyHash ┆ 231 ┆ 231 ┆ 231 ┆ 231 ┆ 1 │ 67 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 68 | ``` 69 | 70 | "##, 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/sstore.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## Use assembly to write storage values 5 | 6 | ```js 7 | 8 | contract GasTest is DSTest { 9 | Contract0 c0; 10 | Contract1 c1; 11 | 12 | function setUp() public { 13 | c0 = new Contract0(); 14 | c1 = new Contract1(); 15 | } 16 | 17 | function testGas() public { 18 | c0.updateOwner(0x158B28A1b1CB1BE12C6bD8f5a646a0e3B2024734); 19 | c1.assemblyUpdateOwner(0x158B28A1b1CB1BE12C6bD8f5a646a0e3B2024734); 20 | } 21 | } 22 | 23 | contract Contract0 { 24 | address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84; 25 | 26 | function updateOwner(address newOwner) public { 27 | owner = newOwner; 28 | } 29 | } 30 | 31 | contract Contract1 { 32 | address owner = 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84; 33 | 34 | function assemblyUpdateOwner(address newOwner) public { 35 | assembly { 36 | sstore(owner.slot, newOwner) 37 | } 38 | } 39 | } 40 | 41 | ``` 42 | 43 | ### Gas Report 44 | ```js 45 | ╭────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 46 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 47 | ╞════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 48 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 49 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 50 | │ 60623 ┆ 261 ┆ ┆ ┆ ┆ │ 51 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 52 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 53 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 54 | │ updateOwner ┆ 5302 ┆ 5302 ┆ 5302 ┆ 5302 ┆ 1 │ 55 | ╰────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 56 | ╭────────────────────┬─────────────────┬──────┬────────┬──────┬─────────╮ 57 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 58 | ╞════════════════════╪═════════════════╪══════╪════════╪══════╪═════════╡ 59 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 60 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 61 | │ 54823 ┆ 232 ┆ ┆ ┆ ┆ │ 62 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 63 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 64 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 65 | │ assemblyUpdateOwner┆ 5236 ┆ 5236 ┆ 5236 ┆ 5236 ┆ 1 │ 66 | ╰────────────────────┴─────────────────┴──────┴────────┴──────┴─────────╯ 67 | ``` 68 | 69 | "##, 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/string_errors.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Use custom errors instead of string error messages 6 | 7 | ```js 8 | contract GasTest is DSTest { 9 | Contract0 c0; 10 | Contract1 c1; 11 | 12 | function setUp() public { 13 | c0 = new Contract0(); 14 | c1 = new Contract1(); 15 | } 16 | 17 | function testFailGas() public { 18 | c0.stringErrorMessage(); 19 | c1.customErrorMessage(); 20 | } 21 | } 22 | 23 | contract Contract0 { 24 | function stringErrorMessage() public { 25 | bool check = false; 26 | require(check, "error message"); 27 | } 28 | } 29 | 30 | contract Contract1 { 31 | error CustomError(); 32 | 33 | function customErrorMessage() public { 34 | bool check = false; 35 | if (!check) { 36 | revert CustomError(); 37 | } 38 | } 39 | } 40 | 41 | ``` 42 | 43 | ### Gas Report 44 | 45 | ```js 46 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 47 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 48 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 49 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 50 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 51 | │ 34087 ┆ 200 ┆ ┆ ┆ ┆ │ 52 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 53 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 54 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 55 | │ stringErrorMessage ┆ 218 ┆ 218 ┆ 218 ┆ 218 ┆ 1 │ 56 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 57 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 58 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 59 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 60 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 61 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 62 | │ 26881 ┆ 164 ┆ ┆ ┆ ┆ │ 63 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 64 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 65 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 66 | │ customErrorMessage ┆ 161 ┆ 161 ┆ 161 ┆ 161 ┆ 1 │ 67 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 68 | ``` 69 | "##, 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /src/report/report_sections/optimizations/template.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | 5 | ## Title Of Optimization 6 | 7 | Short description of the optimization 8 | 9 | ```js 10 | contract GasTest is DSTest { 11 | Contract0 c0; 12 | Contract1 c1; 13 | 14 | function setUp() public { 15 | c0 = new Contract0(); 16 | c1 = new Contract1(); 17 | } 18 | 19 | function testFunction() public { 20 | c0.variant1(); 21 | c1.variant2(); 22 | } 23 | } 24 | 25 | contract Contract0 { 26 | function variant1() public { 27 | } 28 | } 29 | 30 | contract Contract1 { 31 | 32 | function variant2() public { 33 | } 34 | } 35 | 36 | ``` 37 | 38 | ### Gas Report 39 | 40 | ```js 41 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 42 | │ Contract0 contract ┆ ┆ ┆ ┆ ┆ │ 43 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 44 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 45 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 46 | │ 34087 ┆ 200 ┆ ┆ ┆ ┆ │ 47 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 48 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 49 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 50 | │ variant1 ┆ 218 ┆ 218 ┆ 218 ┆ 218 ┆ 1 │ 51 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 52 | ╭────────────────────┬─────────────────┬─────┬────────┬─────┬─────────╮ 53 | │ Contract1 contract ┆ ┆ ┆ ┆ ┆ │ 54 | ╞════════════════════╪═════════════════╪═════╪════════╪═════╪═════════╡ 55 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 56 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 57 | │ 26881 ┆ 164 ┆ ┆ ┆ ┆ │ 58 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 59 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 60 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 61 | │ variant2 ┆ 161 ┆ 161 ┆ 161 ┆ 161 ┆ 1 │ 62 | ╰────────────────────┴─────────────────┴─────┴────────┴─────┴─────────╯ 63 | ``` 64 | 65 | "##, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /src/report/report_sections/qa/constructor_order.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ### Constructor is placed after other functions 5 | 6 | Constructor definition must be placed after Modifiers definitions and before any other function definitions in order to follow `Style Guide Rules`. 7 | 8 | Ex: 9 | 10 | Bad 11 | ```js 12 | contract A { 13 | function () public {} 14 | constructor() {} 15 | } 16 | ``` 17 | 18 | Good 19 | ```js 20 | contract A { 21 | constructor() {} 22 | function () public {} 23 | } 24 | ``` 25 | "##, 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/report/report_sections/qa/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constructor_order; 2 | pub mod overview; 3 | pub mod private_func_leading_underscore; 4 | pub mod private_vars_leading_underscore; 5 | -------------------------------------------------------------------------------- /src/report/report_sections/qa/overview.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from(r##" "##) 3 | } 4 | -------------------------------------------------------------------------------- /src/report/report_sections/qa/private_func_leading_underscore.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## No use of underscore for internal and private function names | Don't use the underscore prefix for public and external function names 5 | 6 | Prefix `internal` and `private` function names with an underscore in order to follow `Style Guides Rules` (ref: https://github.com/protofire/solhint/blob/master/docs/rules/naming/private-vars-leading-underscore.md). 7 | In the other hand, public and external function names must not have an underscore prefix. 8 | 9 | ```js 10 | // Bad 11 | contract Contract0 { 12 | 13 | function msgSender() private view returns (address) { 14 | return msg.sender; 15 | } 16 | 17 | function msgData() internal view returns (bytes calldata) { 18 | return msg.data; 19 | } 20 | 21 | function _currentTimestamp() public view returns(uint256) { 22 | return block.timestamp; 23 | } 24 | 25 | function _currentBlockhash() external view returns(uint256) { 26 | return block.blockhash; 27 | } 28 | } 29 | 30 | // Good 31 | contract Contract1 { 32 | 33 | function _msgSender() private view returns (address) { 34 | return msg.sender; 35 | } 36 | 37 | function _msgData() internal view returns (bytes calldata) { 38 | return msg.data; 39 | } 40 | 41 | function currentTimestamp() public view returns(uint256) { 42 | return block.timestamp; 43 | } 44 | 45 | function currentBlockhash() external view returns(uint256) { 46 | return block.blockhash; 47 | } 48 | } 49 | ``` 50 | "##, 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /src/report/report_sections/qa/private_vars_leading_underscore.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ## No use of underscore for internal and private variable names | Don't use the underscore prefix for public variable names 5 | 6 | Prefix `internal` and `private` variable names with an underscore in order to follow `Style Guides Rules` (ref: https://github.com/protofire/solhint/blob/master/docs/rules/naming/private-vars-leading-underscore.md). 7 | In the other hand, public variable names must not have an underscore prefix. 8 | 9 | ```js 10 | // Bad 11 | contract Contract0 { 12 | address public _owner; 13 | uint256 public _num1; 14 | } 15 | 16 | // Good 17 | contract Contract1 { 18 | address public owner; 19 | uint256 public num1; 20 | } 21 | ``` 22 | "##, 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/report/report_sections/qa/template.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from(r##" "##) 3 | } 4 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/divide_before_multiply.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | Consider ordering multiplication before division to avoid loss of precision because integer division might truncate. Loss of precision in Solidity can lead to vulnerabilities because it can result in unexpected behavior in smart contracts. This can be particularly problematic in financial applications, where even small errors in calculations can have significant consequences. For example, if a contract uses integer division to calculate a result and the division operation truncates the fractional part of the result, it could lead to incorrect pricing or loss of funds due to miscalculated balances. 5 | 6 | #### Unsafe Division 7 | ```js 8 | n = 5 / 2 * 4; // n = 8 because 5 / 2 == 2 since division truncates. 9 | ``` 10 | #### Safe Division 11 | ```js 12 | n = 5 * 4 / 2; // n = 10 13 | ``` 14 | 15 | "##, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/floating_pragma.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | Floating pragma is a vulnerability in smart contract code that can cause unexpected behavior by allowing the compiler to use a specified range of versions. This can lead to issues such as using an older compiler version with known vulnerabilities, using a newer compiler version with undiscovered vulnerabilities, inconsistency across files using different versions, or unpredictable behavior because the compiler can use any version within the specified range. It is recommended to use a locked pragma version in order to avoid these potential vulnerabilities. 5 | 6 | In some cases it may be acceptable to use a floating pragma, such as when a contract is intended for consumption by other developers and needs to be compatible with a range of compiler versions. 7 | 8 | #### Bad 9 | ```js 10 | pragma solidity ^0.8.0; 11 | ``` 12 | #### Good 13 | ```js 14 | pragma solidity 0.8.15; 15 | ``` 16 | 17 | "##, 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod divide_before_multiply; 2 | pub mod floating_pragma; 3 | pub mod overview; 4 | pub mod unprotected_selfdestruct; 5 | pub mod unsafe_erc20_operation; 6 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/overview.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content(total_vulnerabilities: usize) -> String { 2 | String::from( 3 | format!("# Gas Optimizations - (Total Vulnerabilities {})\n 4 | The following sections detail the high, medium and low severity vulnerabilities found throughout the codebase.\n\n
\n 5 | ", total_vulnerabilities) 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/template.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from(r##" "##) 3 | } 4 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/unprotected_selfdestruct.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | Unprotected call to a function executing `selfdestruct` or `suicide`. 5 | 6 | #### Exploit scenario 7 | ```js 8 | contract Suicidal { 9 | function kill() public { 10 | selfdestruct(msg.sender); 11 | } 12 | } 13 | ``` 14 | Anyone can call kill() and destroy the contract. 15 | 16 | #### Recommendations 17 | Protect access to all affected functions. Consider one of the following solutions: 18 | 1. Restrict the visibility of the function to `internal` or `private`. 19 | 2. If the function must be public, either: 20 | 2.1. Add a modifier to allow only shortlisted EOAs to call this function (such as `onlyOwner`). 21 | 2.2. Add a check on the `msg.sender` directly inside the affected function. 22 | 23 | ```js 24 | // restrict visibility to internal or private 25 | function kill() internal { 26 | selfdestruct(msg.sender); 27 | } 28 | 29 | // add a modifier to allow only shortlisted EOAs to call this function 30 | function kill() public onlyOwner { 31 | selfdestruct(msg.sender); 32 | } 33 | 34 | // add a check on the msg.sender directly inside the affected function 35 | function kill() public { 36 | require(msg.sender == owner); 37 | selfdestruct(msg.sender); 38 | } 39 | ``` 40 | "##, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/report/report_sections/vulnerabilities/unsafe_erc20_operation.rs: -------------------------------------------------------------------------------- 1 | pub fn report_section_content() -> String { 2 | String::from( 3 | r##" 4 | ERC20 operations can be unsafe due to different implementations and vulnerabilities in the standard. To account for this, either use OpenZeppelin's SafeERC20 library or wrap each operation in a require statement. 5 | Additionally, ERC20's approve functions have a known race-condition vulnerability. To account for this, use OpenZeppelin's SafeERC20 library's `safeIncrease` or `safeDecrease` Allowance functions. 6 | 7 | #### Unsafe Transfer 8 | ```js 9 | IERC20(token).transfer(msg.sender, amount); 10 | ``` 11 | #### OpenZeppelin SafeTransfer 12 | ```js 13 | import {SafeERC20} from "openzeppelin/token/utils/SafeERC20.sol"; 14 | //--snip-- 15 | 16 | IERC20(token).safeTransfer(msg.sender, address(this), amount); 17 | ``` 18 | 19 | #### Safe Transfer with require statement. 20 | ```js 21 | bool success = IERC20(token).transfer(msg.sender, amount); 22 | require(success, "ERC20 transfer failed"); 23 | ``` 24 | 25 | #### Unsafe TransferFrom 26 | ```js 27 | IERC20(token).transferFrom(msg.sender, address(this), amount); 28 | ``` 29 | #### OpenZeppelin SafeTransferFrom 30 | ```js 31 | import {SafeERC20} from "openzeppelin/token/utils/SafeERC20.sol"; 32 | //--snip-- 33 | 34 | IERC20(token).safeTransferFrom(msg.sender, address(this), amount); 35 | ``` 36 | 37 | #### Safe TransferFrom with require statement. 38 | ```js 39 | bool success = IERC20(token).transferFrom(msg.sender, address(this), amount); 40 | require(success, "ERC20 transfer failed"); 41 | ``` 42 | 43 | "##, 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/report/vulnerability_report.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeSet, HashSet}; 2 | use std::{collections::HashMap, fs}; 3 | 4 | use crate::analyzer::utils::LineNumber; 5 | use crate::analyzer::vulnerabilities::Vulnerability; 6 | 7 | use crate::report::report_sections::vulnerabilities::{ 8 | divide_before_multiply, floating_pragma, overview, unprotected_selfdestruct, 9 | unsafe_erc20_operation, 10 | }; 11 | 12 | pub fn generate_vulnerability_report( 13 | vulnerabilities: HashMap)>>, 14 | ) -> String { 15 | let mut vulnerability_report = String::from(""); 16 | 17 | let mut total_vulnerabilities = 0; 18 | 19 | let mut high_severity_report_section = String::from("## High Risk\n"); 20 | let mut medium_severity_report_section = String::from("## Medium Risk\n"); 21 | //low or non critical 22 | let mut low_severity_report_section = String::from("## Low Risk\n"); 23 | 24 | for vulnerability in vulnerabilities { 25 | if vulnerability.1.len() > 0 { 26 | let vulnerability_target = vulnerability.0; 27 | let matches = vulnerability.1; 28 | 29 | let (report_section, vulnerability_severity) = 30 | get_vulnerability_report_section(vulnerability_target); 31 | 32 | let mut matches_section = String::from("### Lines\n"); 33 | 34 | for (file_name, mut lines) in matches { 35 | for line in lines { 36 | //- file_name:line_number\n 37 | matches_section 38 | .push_str(&(String::from("- ") + &file_name + ":" + &line.to_string())); 39 | matches_section.push_str("\n"); 40 | 41 | total_vulnerabilities += 1; 42 | } 43 | } 44 | 45 | matches_section.push_str("\n\n"); 46 | 47 | let completed_report_section = report_section + "\n" + matches_section.as_str(); 48 | 49 | match vulnerability_severity { 50 | VulnerabilitySeverity::High => { 51 | high_severity_report_section.push_str(completed_report_section.as_str()); 52 | } 53 | VulnerabilitySeverity::Medium => { 54 | medium_severity_report_section.push_str(completed_report_section.as_str()); 55 | } 56 | VulnerabilitySeverity::Low => { 57 | low_severity_report_section.push_str(completed_report_section.as_str()); 58 | } 59 | } 60 | } 61 | } 62 | 63 | //Add optimization report overview 64 | let mut completed_report = overview::report_section_content(total_vulnerabilities); 65 | 66 | if high_severity_report_section != String::from("## High Risk\n") { 67 | vulnerability_report.push_str(&high_severity_report_section); 68 | } 69 | 70 | if medium_severity_report_section != String::from("## Medium Risk\n") { 71 | vulnerability_report.push_str(&medium_severity_report_section); 72 | } 73 | 74 | if low_severity_report_section != String::from("## Low Risk") { 75 | vulnerability_report.push_str(&low_severity_report_section); 76 | } 77 | 78 | completed_report.push_str(&vulnerability_report); 79 | 80 | completed_report 81 | } 82 | 83 | pub enum VulnerabilitySeverity { 84 | High, 85 | Medium, 86 | Low, 87 | } 88 | 89 | pub fn get_vulnerability_report_section( 90 | vulnerability: Vulnerability, 91 | ) -> (String, VulnerabilitySeverity) { 92 | match vulnerability { 93 | Vulnerability::FloatingPragma => ( 94 | floating_pragma::report_section_content(), 95 | VulnerabilitySeverity::Low, 96 | ), 97 | Vulnerability::UnsafeERC20Operation => ( 98 | unsafe_erc20_operation::report_section_content(), 99 | VulnerabilitySeverity::Low, 100 | ), 101 | Vulnerability::UnprotectedSelfdestruct => ( 102 | unprotected_selfdestruct::report_section_content(), 103 | VulnerabilitySeverity::High, 104 | ), 105 | Vulnerability::DivideBeforeMultiply => ( 106 | divide_before_multiply::report_section_content(), 107 | VulnerabilitySeverity::Medium, 108 | ), 109 | } 110 | } 111 | --------------------------------------------------------------------------------