├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── config │ └── release_changelog.json ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── cd.yml │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md └── src ├── main.rs ├── optimizer.rs └── preprocessor.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "🐞 A crash ..." 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 📝 Description 11 | 12 | Describe your issue in as much detail as possible here. 13 | 14 | ### 🌍 Your environment 15 | 16 | * **OS and version**: 17 | * **Branch that causes this issue**: 18 | 19 | ### 🗂 Steps to reproduce 20 | 21 | * Tell us how to reproduce this issue
22 | * Where the issue is, if you know
23 | * Which commands triggered the issue, if any 24 | 25 | ### 🎯 Expected behaviour 26 | 27 | Tell us what should happen. 28 | 29 | ### 👻 Actual behaviour 30 | 31 | Tell us what happens instead. 32 | 33 | ### 👀 Logs 34 | 35 | Please paste any logs here that demonstrate the issue, if they exist. 36 | 37 | ### 🚀 Proposed solution 38 | 39 | If you have an idea of how to fix this issue, please write it down here, so we can begin discussing it. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "✨ Implement this" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### 🎯 Target 11 | 12 | How is affected by this feature ? 13 | 14 | ### 📝 Description 15 | 16 | Describe here what the feature is. 17 | 18 | ### 🧪 Consequence 19 | 20 | How this feature will improve the project. 21 | 22 | ### ⚠️ DoD 23 | 24 | Describe what are the steps required to complete this feature. 25 | 26 | - [ ] This. 27 | - [ ] And that. -------------------------------------------------------------------------------- /.github/config/release_changelog.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "title": "## 🚀 Features", 5 | "labels": [ 6 | "feature", 7 | "enhancement" 8 | ] 9 | }, 10 | { 11 | "title": "## 🐙 Fixes", 12 | "labels": [ 13 | "bug", 14 | "fix", 15 | "bugfix" 16 | ] 17 | }, 18 | { 19 | "title": "## 🧪 Tests", 20 | "labels": [ 21 | "tests" 22 | ] 23 | }, 24 | { 25 | "title": "## 📝 Documentation", 26 | "labels": [ 27 | "documentation" 28 | ] 29 | }, 30 | { 31 | "title": "## 🤖 CI/CD", 32 | "labels": [ 33 | "continuous-delivery", 34 | "continuous-integration" 35 | ] 36 | } 37 | ], 38 | "sort": "ASC", 39 | "pr_template": "- ${{TITLE}}\n\t- PR: [#${{NUMBER}}](https://github.com/${{OWNER}}/${{REPO}}/pull/${{NUMBER}})\n\t- Author:\n\t\t- [@${{AUTHOR}}](https://github.com/${{AUTHOR}})", 40 | "template": "${{CHANGELOG}}\n\n
\nUncategorized\n\n${{UNCATEGORIZED}}\n
\nCommits", 41 | "empty_template": "- No pull requests, check the commits", 42 | "max_tags_to_fetch": 200, 43 | "max_pull_requests": 200, 44 | "max_back_track_time_days": 365 45 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "daily" 10 | reviewers: 11 | - "0xpanoramix" 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please provide a detailed description of what was done in this PR. 4 | 5 | ## Changes include 6 | 7 | - [ ] Bugfix (non-breaking change that solves an issue). 8 | - [ ] Hotfix (change that solves an urgent issue, and requires immediate attention). 9 | - [ ] New feature (non-breaking change that adds functionality). 10 | - [ ] Breaking change (change that is not backwards-compatible and/or changes current functionality). 11 | - [ ] Documentation update (change added to any kind of documentation). 12 | 13 | ## Breaking changes 14 | 15 | Please complete this section if any breaking changes have been made, otherwise delete it. 16 | 17 | ## Checklist 18 | 19 | - [ ] I have assigned this PR to myself. 20 | - [ ] I have added at least 1 reviewer. 21 | - [ ] I have added the relevant labels. 22 | - [ ] I have updated the official documentation. 23 | - [ ] I have added sufficient documentation in code. 24 | 25 | ## Testing 26 | 27 | - [ ] I have tested this code with the official test suite. 28 | - [ ] I have tested this code manually. 29 | 30 | ### Manual tests 31 | 32 | Please complete this section if you ran manual tests for this functionality, otherwise delete it. 33 | 34 | ## Documentation update 35 | 36 | Please link the documentation update PR in this section if it's present, otherwise delete it. 37 | 38 | ## Additional comments 39 | 40 | Please post additional comments in this section if you have them, otherwise delete it. 41 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | jobs: 16 | prepare: 17 | name: "Prepares the release" 18 | runs-on: ubuntu-latest 19 | 20 | outputs: 21 | tag_name: ${{ steps.release_info.outputs.tag_name }} 22 | release_name: ${{ steps.release_info.outputs.release_name }} 23 | changelog: ${{ steps.build_changelog.outputs.changelog }} 24 | 25 | steps: 26 | - name: "Clones the repository" 27 | uses: actions/checkout@v3 28 | 29 | - name: "Computes release name and tag" 30 | id: release_info 31 | run: | 32 | echo "::set-output name=tag_name::${GITHUB_REF_NAME}" 33 | echo "::set-output name=release_name::${GITHUB_REF_NAME}" 34 | 35 | - name: "Builds the changelog" 36 | id: build_changelog 37 | uses: mikepenz/release-changelog-builder-action@v3.3.1 38 | with: 39 | configuration: "./.github/config/release_changelog.json" 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | release: 44 | needs: 45 | - "prepare" 46 | 47 | strategy: 48 | matrix: 49 | job: 50 | - os: ubuntu-latest 51 | platform: linux 52 | target: x86_64-unknown-linux-gnu 53 | arch: amd64 54 | 55 | - os: ubuntu-latest 56 | platform: linux 57 | target: aarch64-unknown-linux-gnu 58 | arch: arm64 59 | 60 | - os: macos-latest 61 | platform: darwin 62 | target: x86_64-apple-darwin 63 | arch: amd64 64 | 65 | - os: macos-latest 66 | platform: darwin 67 | target: aarch64-apple-darwin 68 | arch: arm64 69 | 70 | - os: windows-latest 71 | platform: win32 72 | target: x86_64-pc-windows-msvc 73 | arch: amd64 74 | 75 | name: "Release on ${{ matrix.job.os }} - ${{ matrix.job.target }}" 76 | runs-on: ${{ matrix.job.os }} 77 | 78 | steps: 79 | - name: "Clones the repository" 80 | uses: actions/checkout@v3 81 | 82 | - name: "Setup for Apple M1" 83 | if: ${{ matrix.job.target == 'aarch64-apple-darwin' }} 84 | run: | 85 | echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV 86 | echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV 87 | 88 | - name: "Setup for Linux ARM" 89 | if: ${{ matrix.job.target == 'aarch64-unknown-linux-gnu' }} 90 | run: | 91 | sudo apt-get update -y 92 | sudo apt-get install -y gcc-aarch64-linux-gnu 93 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV 94 | 95 | - name: "Install Rust toolchain" 96 | uses: actions-rs/toolchain@v1 97 | with: 98 | toolchain: stable 99 | target: ${{ matrix.job.target }} 100 | override: true 101 | profile: minimal 102 | 103 | - name: "Build binary" 104 | uses: actions-rs/cargo@v1 105 | with: 106 | command: build 107 | args: --release --bins --target ${{ matrix.job.target }} 108 | 109 | - name: "Archive binary" 110 | id: artifacts 111 | env: 112 | PLATFORM_NAME: ${{ matrix.job.platform }} 113 | TARGET: ${{ matrix.job.target }} 114 | ARCH: ${{ matrix.job.arch }} 115 | VERSION_NAME: ${{ needs.prepare.outputs.tag_name }} 116 | run: | 117 | if [ "$PLATFORM_NAME" == "linux" ]; then 118 | tar -czvf "sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C ./target/${TARGET}/release sigop 119 | echo "::set-output name=file_name::sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" 120 | elif [ "$PLATFORM_NAME" == "darwin" ]; then 121 | # We need to use gtar here otherwise the archive is corrupt. 122 | # See: https://github.com/actions/virtual-environments/issues/2619 123 | gtar -czvf "sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C ./target/${TARGET}/release sigop 124 | echo "::set-output name=file_name::sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" 125 | else 126 | cd ./target/${TARGET}/release 127 | 7z a -tzip "sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" sigop.exe 128 | mv "sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../ 129 | echo "::set-output name=file_name::sigop_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" 130 | fi 131 | 132 | - name: "Creates the release" 133 | uses: softprops/action-gh-release@v1 134 | with: 135 | name: ${{ needs.prepare.outputs.release_name }} 136 | tag_name: ${{ needs.prepare.outputs.tag_name }} 137 | body: ${{ needs.prepare.outputs.changelog }} 138 | files: | 139 | ${{ steps.artifacts.outputs.file_name }} 140 | 141 | publish: 142 | needs: 143 | - "release" 144 | 145 | name: "Publish binary to crates.io" 146 | runs-on: ubuntu-latest 147 | 148 | steps: 149 | - name: "Clones the repository" 150 | uses: actions/checkout@v3 151 | 152 | - name: "Setup Rust toolchain" 153 | uses: actions-rs/toolchain@v1 154 | with: 155 | toolchain: stable 156 | override: true 157 | 158 | - name: "Publish to crates.io" 159 | uses: katyo/publish-crates@v1 160 | with: 161 | registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 162 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | # Run this workflow every time there's a push on a PR targeting the "main" branch. 5 | pull_request: 6 | branches: 7 | - main 8 | paths-ignore: 9 | - "README.md" 10 | - "LICENSE" 11 | 12 | # Run this workflow every time there's a push on the "main" branch. 13 | push: 14 | branches: 15 | - main 16 | 17 | defaults: 18 | run: 19 | shell: bash 20 | 21 | jobs: 22 | lint: 23 | strategy: 24 | matrix: 25 | os: [ ubuntu-latest ] 26 | 27 | name: "Runs linter on ${{ matrix.os }}" 28 | runs-on: ${{ matrix.os }} 29 | 30 | steps: 31 | - name: "Clones the repository" 32 | uses: actions/checkout@v3 33 | 34 | - name: "Installs rustfmt and clippy" 35 | uses: actions-rs/toolchain@v1 36 | with: 37 | profile: minimal 38 | toolchain: stable 39 | components: rustfmt, clippy 40 | 41 | - name: "Runs the code formatter" 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: fmt 45 | args: --all --check 46 | 47 | - name: "Runs the code analysis" 48 | uses: actions-rs/clippy-check@v1 49 | with: 50 | args: --all --all-features -- -D warnings 51 | token: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | test: 54 | needs: lint 55 | 56 | strategy: 57 | matrix: 58 | os: [ ubuntu-latest, macos-latest, windows-latest ] 59 | 60 | name: "Executes the tests on ${{ matrix.os }}" 61 | runs-on: ${{ matrix.os }} 62 | 63 | steps: 64 | - name: "Clones the repository" 65 | uses: actions/checkout@v3 66 | 67 | - name: "Configures the Rust toolchain" 68 | uses: actions-rs/toolchain@v1 69 | with: 70 | profile: minimal 71 | toolchain: stable 72 | 73 | - name: "Executes the unit tests" 74 | uses: actions-rs/cargo@v1 75 | with: 76 | command: test 77 | 78 | build: 79 | needs: test 80 | 81 | strategy: 82 | matrix: 83 | os: [ ubuntu-latest, macos-latest, windows-latest ] 84 | 85 | name: "Builds the project on ${{ matrix.os }}" 86 | runs-on: ${{ matrix.os }} 87 | 88 | steps: 89 | - name: "Clones the repository" 90 | uses: actions/checkout@v3 91 | 92 | - name: "Configures the Rust toolchain" 93 | uses: actions-rs/toolchain@v1 94 | with: 95 | profile: minimal 96 | toolchain: stable 97 | 98 | - name: "Builds the project in release mode" 99 | uses: actions-rs/cargo@v1 100 | with: 101 | command: build 102 | args: --release --all-features 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # IDE configuration files. 13 | .idea/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sigop" 3 | version = "0.1.0" 4 | authors = ["Luca Georges François "] 5 | description = "A A tool to optimize your Solidity function signatures." 6 | repository = "https://github.com/quartz-technology/sigop" 7 | license-file = "LICENSE" 8 | keywords = ["solidity", "function-signature", "optimizer"] 9 | categories = ["command-line-utilities", "development-tools"] 10 | edition = "2021" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | itertools = "0.10.5" 16 | rayon = "1.5.3" 17 | sha3 = "0.10.5" 18 | error-stack = "0.2.1" 19 | regex = "1.6.0" 20 | log = "0.4.17" 21 | env_logger = "0.9.0" 22 | clap = { version = "3.2.22", features = ["derive"] } 23 | indicatif = "0.17.1" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Quartz Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: lint unit-tests build-release 2 | 3 | build-debug: 4 | cargo build 5 | 6 | build-release: 7 | cargo build --release --all-features 8 | 9 | unit-tests: 10 | cargo test -- --nocapture 11 | 12 | lint: 13 | cargo fmt --all -- --check && cargo clippy -- -D warnings 14 | 15 | .PHONY: all build-debug build-release unit-tests lint -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sigop 2 | A CLI tool to optimize your Solidity function signatures. I wanted to create this after seeing 3 | [transmissions11](https://github.com/transmissions11)'s comment about this optimization. 4 | 5 | [Inspired by the great work of emn178](https://github.com/emn178/solidity-optimize-name). 6 | 7 | ## 🧪 How does it work ? 8 | 9 | The optimizer takes a function signature such as `myFunction(address)` and tries to combine it with 10 | a suffix generated from a dictionary. 11 | 12 | For each combination, the 4-bytes function selector is computed and verified : if it contains a 13 | specified number of zeros at the beginning, the optimization has been found. 14 | 15 | ## 🚀 Getting started ! 16 | 17 | ### ⚙️ Installation 18 | 19 | Installing from cargo: 20 | ```shell 21 | cargo install sigop 22 | ``` 23 | 24 | Or building locally from source: 25 | ```shell 26 | make build-release 27 | ``` 28 | 29 | ### 🏁 Quickstart 30 | 31 | ```shell 32 | ./target/release/sigop -s "myFunction(address)" 33 | ``` 34 | 35 | Which should print: 36 | ```shell 37 | [2022-09-23T04:06:03Z INFO sigop::optimizer] Found this optimization: myFunction_6mI(address) 38 | ``` 39 | 40 | Using `cast`, we can see the optimized function selector: 41 | ```shell 42 | $ cast sig "myFunction_6mI(address)" 43 | 0x00001926 44 | ``` 45 | 46 | ### ✏️ Custom parameters 47 | 48 | You can specify custom parameters used by the optimizer: 49 | 1. `length`: The maximum size of the suffix following the original function name. 50 | 2. `target`: The number of zero-bytes you want to have at the beginning of the optimized function 51 | selector. 52 | 53 | Example: 54 | ```shell 55 | $ sigop -s "myFunction(address)" --length=4 --target=3 56 | [2022-09-23T04:06:26Z INFO sigop::optimizer] Found this optimization: myFunction_LYq3(address) 57 | 58 | $ cast sig "myFunction_LYq3(address)" 59 | 0x0000006d 60 | ``` 61 | 62 | ### Results 63 | 64 | Using Remix, we can track the gas cost of calling these functions: 65 | ```shell 66 | // SPDX-License-Identifier: UNLICENSED 67 | pragma solidity ^0.8.14; 68 | 69 | contract Test { 70 | // Execution cost : 22132 71 | function myFunction(address a) public pure returns (address) { 72 | return a; 73 | } 74 | 75 | // Execution cost : 22074 76 | function myFunction_LYq3(address a) public pure returns (address) { 77 | return a; 78 | } 79 | } 80 | ``` 81 | 82 | ## 🤖 Author 83 | 84 | Made with ❤️ by 🤖 [Luca Georges François](https://github.com/0xpanoramix) 🤖 -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod optimizer; 2 | mod preprocessor; 3 | 4 | use crate::optimizer::{build_combinations, find_optimization}; 5 | use crate::preprocessor::{try_preprocess, PreProcessError}; 6 | use clap::Parser; 7 | use env_logger::Builder; 8 | use error_stack::Report; 9 | use log::{error, LevelFilter}; 10 | 11 | #[derive(Parser)] 12 | #[clap(author, version, about, long_about = None)] 13 | struct Cli { 14 | /// The function signature to optimize. 15 | #[clap(short, long)] 16 | signature: String, 17 | 18 | /// The maximum size of the suffix following the original function name. 19 | #[clap(short, long, default_value_t = 3)] 20 | length: u8, 21 | 22 | /// The number of zero-bytes you want to have at the beginning of the optimized function. 23 | #[clap(short, long, default_value_t = 2)] 24 | target: u8, 25 | } 26 | 27 | fn run( 28 | function_signature: &str, 29 | suffix_length: u8, 30 | optimization_target: u8, 31 | ) -> Result<(), Report> { 32 | let mut function = try_preprocess(function_signature)?; 33 | let combinations = build_combinations(suffix_length); 34 | 35 | function.name.push('_'); 36 | find_optimization( 37 | function.name.as_str(), 38 | function.params.as_str(), 39 | &combinations, 40 | suffix_length, 41 | optimization_target, 42 | ); 43 | 44 | Ok(()) 45 | } 46 | 47 | fn main() { 48 | let mut builder = Builder::new(); 49 | 50 | builder.filter_level(LevelFilter::Info); 51 | builder.parse_default_env(); 52 | builder.init(); 53 | 54 | let cli = Cli::parse(); 55 | 56 | match run(cli.signature.as_str(), cli.length, cli.target) { 57 | Ok(_) => {} 58 | Err(err) => match err.current_context() { 59 | PreProcessError::InvalidFunctionSignatureParenthesis(msg) => { 60 | error!("{msg}") 61 | } 62 | PreProcessError::InvalidFunctionSignatureParsing(msg) => { 63 | error!("{msg}") 64 | } 65 | PreProcessError::ErrorRegexParsing(msg) => { 66 | error!("{msg}") 67 | } 68 | }, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/optimizer.rs: -------------------------------------------------------------------------------- 1 | use indicatif::ProgressBar; 2 | use itertools::Itertools; 3 | use log::{error, info}; 4 | use rayon::prelude::*; 5 | use sha3::{Digest, Keccak256}; 6 | 7 | /// Creates a vector containing all possible combinations of elements in a dictionary, stopping when 8 | /// the elements length reach the provided suffix_length variable. 9 | pub fn build_combinations(suffix_length: u8) -> Vec { 10 | let dictionary = vec![ 11 | "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", 12 | "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 13 | "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", 14 | "S", "T", "U", "V", "W", "X", "Y", "Z", "$", "_", 15 | ]; 16 | 17 | let mut combinations: Vec = dictionary.iter().map(|&s| s.into()).collect(); 18 | 19 | for n in 1..suffix_length { 20 | let mut new_combinations: Vec = (1..n).fold( 21 | dictionary 22 | .iter() 23 | .cartesian_product(dictionary.iter()) 24 | .map(|(&a, &b)| a.to_owned() + b) 25 | .collect(), 26 | |acc, _| { 27 | acc.into_iter() 28 | .cartesian_product(dictionary.iter()) 29 | .map(|(a, b)| a + b) 30 | .collect() 31 | }, 32 | ); 33 | combinations.append(&mut new_combinations); 34 | } 35 | 36 | combinations 37 | } 38 | 39 | /// Finds a way to name the initial function that will produce a function signature with a specific 40 | /// count (the optimization_target) of leading zeros. 41 | /// Using the combinations, reconstruct a function name by emplacing each one of them right before 42 | /// the function parameters, computes the new function signature and verifies if it match the target. 43 | pub fn find_optimization( 44 | initial_function_name: &str, 45 | initial_function_parameters: &str, 46 | combinations: &Vec, 47 | suffix_length: u8, 48 | optimization_target: u8, 49 | ) { 50 | let pb = ProgressBar::new(compute_maximum_number_of_combinations(64, suffix_length)); 51 | 52 | // We use rayon to iterate in the combination in parallel. 53 | let suffix = combinations.par_iter().find_first(|&x| { 54 | pb.inc(1); 55 | 56 | // Reconstructs the function's signature using the current combination. 57 | let mut new_function_signature = String::from(initial_function_name); 58 | new_function_signature.push_str(x); 59 | new_function_signature.push_str(initial_function_parameters); 60 | 61 | // Encodes the new function signature. 62 | let data = new_function_signature.into_bytes(); 63 | let mut encoded_new_function_signature = [0u8; 4]; 64 | encoded_new_function_signature.copy_from_slice(&Keccak256::digest(&data)[..4]); 65 | 66 | // Verifies if the new encoded function signature matches the optimization target. 67 | let mut found = true; 68 | for i in 0..optimization_target { 69 | if encoded_new_function_signature[i as usize] != 0 { 70 | found = false; 71 | break; 72 | } 73 | } 74 | 75 | found 76 | }); 77 | 78 | pb.finish_and_clear(); 79 | 80 | match suffix { 81 | None => { 82 | error!("No optimization was found :(") 83 | } 84 | Some(opt) => { 85 | info!( 86 | "Found this optimization: {}{}{}", 87 | initial_function_name, opt, initial_function_parameters 88 | ) 89 | } 90 | } 91 | } 92 | 93 | fn compute_maximum_number_of_combinations(dictionary_length: u64, suffix_length: u8) -> u64 { 94 | (1..=suffix_length) 95 | .into_iter() 96 | .map(|n| dictionary_length.pow(n as u32) as u64) 97 | .sum() 98 | } 99 | 100 | /* 101 | fn compute_total_bytes_used(dictionary_length: u64, suffix_length: u8) -> u64 { 102 | (1..=suffix_length) 103 | .into_iter() 104 | .map(|n| dictionary_length.pow(n as u32) as u64 * (n as u64)) 105 | .sum() 106 | } 107 | */ 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | #[test] 112 | fn it_finds_an_optimization() {} 113 | } 114 | -------------------------------------------------------------------------------- /src/preprocessor.rs: -------------------------------------------------------------------------------- 1 | use error_stack::{Report, Result}; 2 | use regex::Regex; 3 | use std::error::Error; 4 | use std::fmt::{Display, Formatter}; 5 | 6 | #[derive(Default)] 7 | pub struct FunctionSignature { 8 | pub name: String, 9 | pub params: String, 10 | } 11 | 12 | #[derive(Debug)] 13 | pub enum PreProcessError { 14 | InvalidFunctionSignatureParenthesis(String), 15 | InvalidFunctionSignatureParsing(String), 16 | ErrorRegexParsing(String), 17 | } 18 | 19 | impl Display for PreProcessError { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 21 | f.write_str("PreProcessor error: could not preprocess initial function signature") 22 | } 23 | } 24 | 25 | impl Error for PreProcessError {} 26 | 27 | pub fn try_preprocess(function_signature: &str) -> Result { 28 | let cleaned_function_signature = remove_whitespaces(function_signature); 29 | try_validate(cleaned_function_signature.as_str())?; 30 | let function = try_parse(cleaned_function_signature.as_str())?; 31 | 32 | Ok(function) 33 | } 34 | 35 | // Tries to parse the function signature and splits the name from the parameters. 36 | fn try_parse(function_signature: &str) -> Result { 37 | let re = Regex::new(r"(.*?)\((.*)").unwrap(); 38 | let caps = re.captures(function_signature); 39 | 40 | match caps { 41 | None => { 42 | Err(Report::new( 43 | PreProcessError:: 44 | ErrorRegexParsing( 45 | "could not parse function signature correctly".to_string(), 46 | ) 47 | )) 48 | } 49 | Some(captures) => { 50 | match captures.len() == 3 { 51 | true => Ok(FunctionSignature { 52 | name: captures.get(1).unwrap().as_str().to_string(), 53 | params: format!("({}", captures.get(2).unwrap().as_str()), 54 | }), 55 | false => Err(Report::new( 56 | PreProcessError::InvalidFunctionSignatureParsing( 57 | "function signature doesn't have a valid structure (should be function_name(function_parameters))".to_string(), 58 | ) 59 | )), 60 | } 61 | } 62 | } 63 | } 64 | 65 | /// Used to remove whitespaces from the given function signature. 66 | fn remove_whitespaces(function_sig: &str) -> String { 67 | function_sig 68 | .chars() 69 | .filter(|c| !c.is_whitespace()) 70 | .collect() 71 | } 72 | 73 | /// Tries to validate that the initial function signature is valid for further usage. 74 | fn try_validate(function_sig: &str) -> Result<(), PreProcessError> { 75 | verify_parenthesis(function_sig)?; 76 | 77 | Ok(()) 78 | } 79 | 80 | /// Makes sure there are no issues with parenthesis in the initial function signature. 81 | fn verify_parenthesis(function_sig: &str) -> Result<(), PreProcessError> { 82 | if !function_sig.ends_with(')') { 83 | return Err(Report::new( 84 | PreProcessError::InvalidFunctionSignatureParenthesis( 85 | "function signature doesn't end with a closing parenthesis".to_string(), 86 | ), 87 | )); 88 | } 89 | 90 | if function_sig.matches('(').count() != function_sig.matches(')').count() { 91 | return Err(Report::new( 92 | PreProcessError::InvalidFunctionSignatureParenthesis( 93 | "function signature doesn't have the same number of opening and closing parenthesis".to_string(), 94 | ), 95 | )); 96 | } 97 | 98 | Ok(()) 99 | } 100 | --------------------------------------------------------------------------------