├── .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 |
--------------------------------------------------------------------------------