diff --git a/.gitignore b/.gitignore index 6a291ad7f..1a01649a4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /*bench bin corpus -.vscode \ No newline at end of file +.vscode +node_modules \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index f56141af5..129e48859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ reqwest = { version = "0.11", features = ["blocking", "json"] } once_cell = "1.8.0" permutator = "0.4.3" either = "1.8.0" +regex = "1" # external fuzzing-based abi decompiler heimdall = { path = "./externals/heimdall-rs/heimdall" } diff --git a/README.md b/README.md index 16f8e7826..251486c17 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # ItyFuzz 🍦 -Fast hybrid fuzzer for EVM & MoveVM (WIP) smart contracts. + +Fast hybrid fuzzer for EVM & MoveVM (WIP) smart contracts. You can generate exploits **instantly** by just providing the contract address: ![](https://ityfuzz.assets.fuzz.land/demo2.gif) -[中文版README](https://github.com/fuzzland/ityfuzz/blob/master/README_CN.md) / [Research Paper](https://scf.so/ityfuzz.pdf) / [Development Info](#development) +[中文版 README](https://github.com/fuzzland/ityfuzz/blob/master/README_CN.md) / [Research Paper](https://scf.so/ityfuzz.pdf) / [Development Info](#development) ### Run ItyFuzz with UI + Install [Docker](https://www.docker.com/) and run docker image suitable for your system architecture: ``` @@ -20,35 +22,34 @@ Then, you can visit the interface at http://localhost:8000 ### Statistics & Comparison -Time taken for finding vulnerabilities / generating exploits: +Time taken for finding vulnerabilities / generating exploits: -| Project Name | Vulnerability | **Mythril** | **SMARTIAN** | **Slither** | **ItyFuzz** | -|---------------|-------------------------|---------|-------------|---------|---------| -| AES | Business Logic | Inf | Unsupported | No | 4hrs | -| Carrot | Arbitrary External Call | 17s | 11s | Yes | 1s | -| Olympus | Access Control | 36s | Inf | Yes | 1s | -| MUMUG | Price Manipulation | Inf | Unsupported | No | 18hrs | -| Omni | Reentrancy | Inf | Unsupported | Yes* | 22hrs | -| Verilog CTF-2 | Reentrancy | Inf | Unsupported | Yes* | 3s | +| Project Name | Vulnerability | **Mythril** | **SMARTIAN** | **Slither** | **ItyFuzz** | +| ------------- | ----------------------- | ----------- | ------------ | ----------- | ----------- | +| AES | Business Logic | Inf | Unsupported | No | 4hrs | +| Carrot | Arbitrary External Call | 17s | 11s | Yes | 1s | +| Olympus | Access Control | 36s | Inf | Yes | 1s | +| MUMUG | Price Manipulation | Inf | Unsupported | No | 18hrs | +| Omni | Reentrancy | Inf | Unsupported | Yes\* | 22hrs | +| Verilog CTF-2 | Reentrancy | Inf | Unsupported | Yes\* | 3s | \* Slither only finds the reentrancy location, but not how to leverage reentrancy to trigger final buggy code. The output also contains significant amount of false positives. Test Coverage: | **Dataset** | **SMARTIAN** | **Echidna** | **ItyFuzz** | -|-------------|--------------|-------------|-------------| +| ----------- | ------------ | ----------- | ----------- | | B1 | 97.1% | 47.1% | 99.2% | | B2 | 86.2% | 82.9% | 95.4% | | Tests | Unsupported | 52.9% | 100% | \* B1 and B2 contain 72 single-contract projects from SMARTIAN artifacts. Tests are the projects in `tests` directory. The coverage is calculated as `(instruction covered) / (total instruction - dead code)`. - # Development ### Building -You need to have `libssl-dev` (OpenSSL) and `libz3-dev` (refer to [Z3 Installation](#z3-installation) section for instruction) installed. +You need to have `libssl-dev` (OpenSSL) and `libz3-dev` (refer to [Z3 Installation](#z3-installation) section for instruction) installed. ```bash # download move dependencies @@ -62,28 +63,32 @@ You can enable certain debug gates in `Cargo.toml` `solc` is needed for compiling smart contracts. You can use `solc-select` tool to manage the version of `solc`. ### Run + Compile Smart Contracts: + ```bash cd ./tests/multi-contract/ # include the library from ./solidity_utils for example solc *.sol -o . --bin --abi --overwrite --base-path ../../ ``` + Run Fuzzer: + ```bash # if cli binary exists cd ./cli/ ./cli -t '../tests/multi-contract/*' ``` - ### Demo **Verilog CTF Challenge 2** `tests/verilog-2/` -Flashloan attack + Reentrancy. The target is to reach line 34 in `Bounty.sol`. +Flashloan attack + Reentrancy. The target is to reach line 34 in `Bounty.sol`. Exact Exploit: + ``` 0. Borrow k MATIC such that k > balance() / 10 1. depositMATIC() with k MATIC @@ -92,6 +97,7 @@ Exact Exploit: ``` Use fuzzer to detect the vulnerability and generate the exploit (takes 0 - 200s): + ```bash # build contracts in tests/verilog-2/ solc *.sol -o . --bin --abi --overwrite --base-path ../../ @@ -99,16 +105,19 @@ solc *.sol -o . --bin --abi --overwrite --base-path ../../ ./cli -f -t "./tests/verilog-2/*" ``` -`-f` flag enables automated flashloan, which hooks all ERC20 external calls and make any users to have infinite balance. +`-f` flag enables automated flashloan, which hooks all ERC20 external calls and make any users to have infinite balance. ### Fuzz a Project (Offline) -You can fuzz a project by providing a path to the project directory. + +You can fuzz a project by providing a path to the project directory. + ```bash ./cli -t '[DIR_PATH]/*' ``` + ItyFuzz would attempt to deploy all artifacts in the directory to a blockchain with no other smart contracts. -Specifically, the project directory should contain +Specifically, the project directory should contain a few `[X].abi` and `[X].bin` files. For example, to fuzz a contract named `main.sol`, you should ensure `main.abi` and `main.bin` exist in the project directory. The fuzzer will automatically detect the contracts in directory, the correlation between them (see `tests/multi-contract`), @@ -119,65 +128,127 @@ can add a `[X].address`, where `[X]` is the contract name, to specify the addres Caveats: -* Keep in mind that ItyFuzz is fuzzing on a clean blockchain, -so you should ensure all related contracts (e.g., ERC20 token, Uniswap, etc.) are deployed to the blockchain before fuzzing. - -* You also need to overwrite all `constructor(...)` in the smart contract to -to make it have no function argument. ItyFuzz assumes constructors have no argument. +- Keep in mind that ItyFuzz is fuzzing on a clean blockchain, + so you should ensure all related contracts (e.g., ERC20 token, Uniswap, etc.) are deployed to the blockchain before fuzzing. ### Fuzz a Project (Online) -Rebuild with `flashloan_v2` (only supported in onchain) enabled to get better result. +Rebuild with `flashloan_v2` (only supported in onchain) enabled to get better result. + ```bash sed -i 's/\"default = [\"/\"default = [flashloan_v2,\"/g' ./Cargo.toml cd ./cli/ cargo build --release ``` -To effectively cache the costly RPC calls to blockchains, third-party APIs, and Etherscan, a proxy is built. -To run the proxy: -```bash -pip3 install -r proxy/requirements.txt -python3 proxy/main.py -``` - You can fuzz a project by providing an address, a block, and a chain type. + ```bash -./cli -o -t [TARGET_ADDR] --onchain-block-number [BLOCK] -c [CHAIN_TYPE] +./cli -o -t [TARGET_ADDR] --onchain-block-number [BLOCK] -c [CHAIN_TYPE] ``` Example: Fuzzing WETH contract (`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`) on Ethereum mainnet at latest block. + ```bash ./cli -o -t 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 --onchain-block-number 0 -c ETH ``` + Fuzzing with flashloan and oracles enabled: + ```bash ./cli -o -t 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 --onchain-block-number 0 -c ETH -f -i -p ``` ItyFuzz would pull the ABI of the contract from Etherscan and fuzz it. If ItyFuzz encounters an unknown slot in the memory, it would pull the slot from chain RPC. -If ItyFuzz encounters calls to external unknown contract, it would pull the bytecode and ABI of that contract. +If ItyFuzz encounters calls to external unknown contract, it would pull the bytecode and ABI of that contract. If its ABI is not available, ItyFuzz would not send any transaction to that contract. -To use proxy, append `--onchain-local-proxy-addr http://localhost:5003` to your CLI command. +To use proxy, append `--onchain-local-proxy-addr http://localhost:5003` to your CLI command. ### Onchain Fetching + ItyFuzz attempts to fetch storage from blockchain nodes when SLOAD is encountered and the target is uninitialized. -There are three ways of fetching: -* OneByOne: fetch one slot at a time. This is the default mode. It is slow but never fails. -* All: fetch all slots at once using custom API `eth_getStorageAll` on our nodes. This is the fastest mode, but it may fail if the contract is too large. -* Dump: dump storage using debug API `debug_storageRangeAt`. This only works for ETH (for now) and fails most of the time. +There are three ways of fetching: + +- OneByOne: fetch one slot at a time. This is the default mode. It is slow but never fails. +- All: fetch all slots at once using custom API `eth_getStorageAll` on our nodes. This is the fastest mode, but it may fail if the contract is too large. +- Dump: dump storage using debug API `debug_storageRangeAt`. This only works for ETH (for now) and fails most of the time. + +### Constructor Arguments + +ItyFuzz provides two methods to pass in constructor arguments. These arguments are necessary for initializing the state of the contract when deployed. + +**Method 1: CLI Arguments** + +The first method is to pass in the constructor arguments directly as CLI arguments. + +When you run ItyFuzz using the CLI, you can include the `--constructor-args` flag followed by a string that specifies the arguments for each constructor. + +The format is as follows: + +``` +cli -t 'tests/multi-contract/*' --constructor-args "ContractName:arg1,arg2,...;AnotherContract:arg1,arg2,..;" +``` + +For example, if you have two contracts, `main` and `main2`, both having a `bytes32` and a `uint256` as constructor arguments, you would pass them in like this: + +```bash +cli -t 'tests/multi-contract/*' --constructor-args "main:1,0x6100000000000000000000000000000000000000000000000000000000000000;main2:2,0x6200000000000000000000000000000000000000000000000000000000000000;" +``` + +**Method 2: Server Forwarding** + +The second method is to use our server to forward requests to a user-specified RPC, and cli will fetch the constructor arguments from the transactions sent to the RPC. + +Firstly, go to the `/server` directory, and install the necessary packages: + +```bash +cd /server +npm install +``` + +Then, start the server using the following command: + +```bash +node app.js +``` + +By default, the server will forward requests to `http://localhost:8545`, which is the default address for [Ganache](https://github.com/trufflesuite/ganache), if you do not have a local blockchain running, you can use Ganache to start one. +If you wish to forward requests to another location, you can specify the address as a command-line argument like so: + +```bash +node app.js http://localhost:8546 +``` + +Once the server is running, you can deploy your contract to `localhost:5001` using a tool of your choice. + +For example, you can use Foundry to deploy your contract through the server: + +```bash +forge create src/flashloan.sol:main2 --rpc-url http://127.0.0.1:5001 --private-key 0x0000000000000000000000000000000000000000000000000000000000000000 --constructor-args "1" "0x6100000000000000000000000000000000000000000000000000000000000000" +``` + +Finally, you can fetch the constructor arguments using the `--fetch-tx-data` flag: + +```bash +cli -t 'tests/multi-contract/*' --fetch-tx-data +``` + +ItyFuzz will fetch the constructor arguments from the transactions forwarded to the RPC through the server. ### Z3 Installation + **macOS** + ```bash git clone https://github.com/Z3Prover/z3 && cd z3 python scripts/mk_make.py --prefix=/usr/local cd build && make -j64 && sudo make install ``` -If the build command still fails for not finding `z3.h`, do `export Z3_SYS_Z3_HEADER=/usr/local/include/z3.h` + +If the build command still fails for not finding `z3.h`, do `export Z3_SYS_Z3_HEADER=/usr/local/include/z3.h` **Ubuntu** @@ -186,7 +257,8 @@ apt install libz3-dev ``` ### Telemetry -ItyFuzz collects telemetry data to help us improve the fuzzer. The data is collected anonymously and is not used for any commercial purpose. + +ItyFuzz collects telemetry data to help us improve the fuzzer. The data is collected anonymously and is not used for any commercial purpose. You can disable telemetry by setting `NO_TELEMETRY=1` in your environment variable. ### Citation @@ -201,7 +273,6 @@ You can disable telemetry by setting `NO_TELEMETRY=1` in your environment variab } ``` - ### Acknowledgement This work was supported in part by NSF grants CCF-1900968, CCF1908870, and CNS1817122 and SKY Lab industrial sponsors and diff --git a/README_CN.md b/README_CN.md index 49352408d..35c57cdd5 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,46 +2,54 @@ ItyFuzz 是一款快速的混合模糊测试工具,用于 EVM、MoveVM(WIP)等。 -### 使用 UI 运行 ItyFuzz -安装 Docker,然后运行我们的 Docker 镜像(仅支持 x86,在非 x86 平台上运行会显著降低性能): +只需提供合约地址,就能**立即**找到漏洞: +![](https://ityfuzz.assets.fuzz.land/demo2.gif) -```bash -docker run -p 8000:8000 fuzzland/ityfuzz +[英文版 README](https://github.com/fuzzland/ityfuzz/blob/master/README.md) / [研究论文](https://scf.so/ityfuzz.pdf) / [开发信息](#development) + +### 通过 UI 运行 ItyFuzz + +安装 [Docker](https://www.docker.com/) 并运行适用于你的系统架构的 docker 镜像: + +``` +docker pull fuzzland/ityfuzz:stable +docker run -p 8000:8000 fuzzland/ityfuzz:stable ``` -然后,您可以在 http://localhost:8000 访问UI。 +然后,您可以在 http://localhost:8000 访问 UI。 + +注意:容器使用公共 ETH RPC,可能超时或运行缓慢 ### 统计 发现漏洞/生成攻击所花费的时间: -| 项目名称 | 漏洞 | **Mythril** | **SMARTIAN** | **Slither** | **ItyFuzz** | -|---------------|-------------------------|---------|-------------|---------|---------| -| AES | 业务逻辑 | Inf | 不支持 | No | 4小时 | -| Carrot | 任意外部调用 | 17s | 11s | Yes | 1s | -| Olympus | 访问控制 | 36s | Inf | Yes | 1s | -| MUMUG | 价格操纵 | Inf | 不支持 | No | 18小时 | -| Omni | 重入 | Inf | 不支持 | Yes* | 22小时 | -| Verilog CTF-2 | 重入 | Inf | 不支持 | Yes* | 3s | +| 项目名称 | 漏洞 | **Mythril** | **SMARTIAN** | **Slither** | **ItyFuzz** | +| ------------- | ------------ | ----------- | ------------ | ----------- | ----------- | +| AES | 业务逻辑 | Inf | 不支持 | No | 4 小时 | +| Carrot | 任意外部调用 | 17s | 11s | Yes | 1s | +| Olympus | 访问控制 | 36s | Inf | Yes | 1s | +| MUMUG | 价格操纵 | Inf | 不支持 | No | 18 小时 | +| Omni | 重入 | Inf | 不支持 | Yes\* | 22 小时 | +| Verilog CTF-2 | 重入 | Inf | 不支持 | Yes\* | 3s | \* Slither 仅发现重入位置,而不是如何利用重入来触发最终的错误代码。输出还包含大量的误报。 测试覆盖率: | **数据集** | **SMARTIAN** | **Echidna** | **ItyFuzz** | -|-------------|--------------|-------------|-------------| -| B1 | 97.1% | 47.1% | 99.2% | -| B2 | 86.2% | 82.9% | 95.4% | -| Tests | 不支持 | 52.9% | 100% | +| ---------- | ------------ | ----------- | ----------- | +| B1 | 97.1% | 47.1% | 99.2% | +| B2 | 86.2% | 82.9% | 95.4% | +| Tests | 不支持 | 52.9% | 100% | \* B1 和 B2 包含 72 个合约。Tests 是 `tests` 目录中的项目。覆盖率计算为 `(覆盖的指令)/(总指令 - 无效代码)`。 - # 开发 ### 构建 -您需要安装`libssl-dev`(OpenSSL)和`libz3-dev`。 +您需要安装 `libssl-dev`(OpenSSL)和 `libz3-dev`(参见[Z3 安装](#z3-installation)章节中的说明)。 ```bash # 下载依赖 @@ -50,13 +58,19 @@ cd cli/ cargo build --release ``` +你需要`solc`来编译智能合约。你可以使用`solc-select`工具来管理`solc`的版本。 + ### 运行 + 编译智能合约: + ```bash cd ./tests/multi-contract/ solc *.sol -o . --bin --abi --overwrite --base-path ../../ ``` -运行Fuzzer: + +运行 Fuzzer: + ```bash cd ./cli/ ./cli -t '../tests/multi-contract/*' @@ -67,9 +81,10 @@ cd ./cli/ **Verilog CTF Challenge 2** `tests/verilog-2/` -合约有闪电贷款攻击+重入漏洞。攻击目标是到达`Bounty.sol`中的第34行。 +合约有闪电贷款攻击+重入漏洞。攻击目标是到达`Bounty.sol`中的第 34 行。 具体漏洞利用过程: + ``` 0. 借k MATIC,使得 k > balance() / 10 1. 用k MATIC 调用 depositMATIC() @@ -77,7 +92,8 @@ cd ./cli/ 3. 返还k MATIC ``` -使用ItyFuzz检测漏洞并生成具体漏洞利用过程(需要0-200秒): +使用 ItyFuzz 检测漏洞并生成具体漏洞利用过程(需要 0-200 秒): + ```bash # 在tests/verilog-2/中构建合约 solc *.sol -o . --bin --abi --overwrite --base-path ../../ @@ -85,30 +101,32 @@ solc *.sol -o . --bin --abi --overwrite --base-path ../../ ./cli -f -t "./tests/verilog-2/*" ``` -`-f`标志启用自动闪电贷款,它会hook所有ERC20外部调用,使任何用户都具有无限余额。 +`-f` 标志启用自动闪电贷款,它会 hook 所有 ERC20 外部调用,使任何用户都具有无限余额。 + +### 离线 Fuzz 一个项目 + +您可以通过提供项目目录的路径(glob)来 Fuzz 一个项目。 -### 离线Fuzz一个项目 -您可以通过提供项目目录的路径(glob)来Fuzz一个项目。 ```bash ./cli -t '[DIR_PATH]/*' ``` -ItyFuzz将尝试将目录中的所有工件部署到没有其他智能合约的区块链中。 -项目目录中应当包含`[X].abi`和`[X].bin`文件。例如,要fuzz一个名为`main.sol`的合约,您应该 + +ItyFuzz 将尝试将目录中的所有工件部署到没有其他智能合约的区块链中。 +项目目录中应当包含`[X].abi`和`[X].bin`文件。例如,要 fuzz 一个名为`main.sol`的合约,您应该 确保项目目录中存在`main.abi`和`main.bin`。 -ItyFuzz将自动检测目录中的合约之间的关联(参见`tests/multi-contract`), -并fuzz它们。 +ItyFuzz 将自动检测目录中的合约之间的关联(参见`tests/multi-contract`), +并 fuzz 它们。 -如果ItyFuzz无法推断合约之间的关联,您 +如果 ItyFuzz 无法推断合约之间的关联,您 也可以添加一个`[X].address`,其中`[X]`是合约名称,以指定合约的地址。 注意事项: -* ItyFuzz在无任何合约的区块链上进行fuzz, -因此您应该确保在fuzz之前将所有相关合约(例如,ERC20令牌,Uniswap等)都将部署到 ItyFuzz 的区块链中。 +- ItyFuzz 在无任何合约的区块链上进行 fuzz, + 因此您应该确保在 fuzz 之前将所有相关合约(例如,ERC20 令牌,Uniswap 等)都将部署到 ItyFuzz 的区块链中。 -* 您还需要覆盖智能合约中的所有`constructor(...)`使它没有参数。 ItyFuzz假定构造函数没有参数。 +### 在线 Fuzz 一个项目 -### 在线Fuzz一个项目 (可选)启用 flashloan_v2 重新构建以获得更好的结果。 ```bash @@ -117,27 +135,145 @@ cd ./cli/ cargo build --release ``` -您可以通过提供地址,块和链来fuzz一个项目。 +您可以通过提供地址,块和链来 fuzz 一个项目。 + ```bash -./cli -o -t [TARGET_ADDR] --onchain-block-number [BLOCK] -c [CHAIN_TYPE] +./cli -o -t [TARGET_ADDR] --onchain-block-number [BLOCK] -c [CHAIN_TYPE] ``` 示例: -在以太坊主网最新区块上fuzz WETH合约(`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`)。 +在以太坊主网最新区块上 fuzz WETH 合约(`0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`)。 + ```bash ./cli -o -t 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 --onchain-block-number 0 -c ETH ``` -ItyFuzz将从Etherscan拉取合约的ABI并fuzz它。如果ItyFuzz遇到Storage中未知的槽,它将从RPC同步槽。 -如果ItyFuzz遇到对外部未知合约的调用,它将拉取该合约的字节码和ABI。 如果它的ABI不可用,ItyFuzz将使用heimdall对字节码进行反编译分析ABI。 +ItyFuzz 将从 Etherscan 拉取合约的 ABI 并 fuzz 它。如果 ItyFuzz 遇到 Storage 中未知的槽,它将从 RPC 同步槽。 +如果 ItyFuzz 遇到对外部未知合约的调用,它将拉取该合约的字节码和 ABI。 如果它的 ABI 不可用,ItyFuzz 将使用 heimdall 对字节码进行反编译分析 ABI。 + +### Onchain 获取 + +当遇到 SLOAD 与目标未初始化的时,ItyFuzz 尝试从区块链节点获取存储。有三种获取方式: + +- OneByOne:一次获取一个 slot 。这是默认模式。它很慢,但不会失败。 +- All:使用我们节点上的自定义 API `eth_getStorageAll` 一次性获取所有 slot 。这是最快的模式,但如果合约太大,可能会失败。 +- Dump:使用 debug API `debug_storageRangeAt` 来转储存储。这只适用于 ETH(目前),并且很容易失败。 + +### 构造函数参数 + +ItyFuzz 提供两种方法来传入构造函数参数。这些参数对于在部署时初始化合约的状态是必要的。 + +**方法 1:CLI 参数** + +第一种方法是直接作为 CLI 参数传入构造函数参数。 + +当你使用 CLI + +运行 ItyFuzz 时,你可以包含`--constructor-args`标志,后跟一个指定每个构造函数的参数的字符串。 + +格式如下: + +``` +cli -t 'tests/multi-contract/*' --constructor-args "ContractName:arg1,arg2,...;AnotherContract:arg1,arg2,..;" +``` + +例如,如果你有两个合约,`main` 和 `main2`,它们都有一个 `bytes32` 和一个 `uint256` 作为构造函数参数,你可以这样传入它们: + +```bash +cli -t 'tests/multi-contract/*' --constructor-args "main:1,0x6100000000000000000000000000000000000000000000000000000000000000;main2:2,0x6200000000000000000000000000000000000000000000000000000000000000;" +``` + +**方法 2:服务器转发** + +第二种方法是使用我们的服务器将请求转发到用户指定的 RPC,cli 将从发送到 RPC 的交易中获取构造函数参数。 + +首先,进入`/server`目录,并安装必要的包: + +```bash +cd /server +npm install +``` + +然后,使用以下命令启动服务器: + +```bash +node app.js +``` + +默认情况下,服务器将请求转发到`http://localhost:8545`,这是[Ganache](https://github.com/trufflesuite/ganache)的默认地址,如果你没有运行本地区块链,你可以使用 Ganache 启动一个。 +如果你希望将请求转发到其他位置,你可以像这样指定地址作为命令行参数: + +```bash +node app.js http://localhost:8546 +``` + +一旦服务器运行起来,你就可以使用你选择的工具将你的合约部署到 `localhost:5001`。 + +例如,你可以使用 Foundry 通过服务器部署你的合约: -### 代理 +```bash +forge create src/flashloan.sol:main2 --rpc-url http://127.0.0.1:5001 --private-key 0x0000000000000000000000000000000000000000000000000000000000000000 --constructor-args "1" "0x6100000000000000000000000000000000000000000000000000000000000000" +``` -为了有效地缓存昂贵的RPC调用,第三方API和Etherscan,我们创建了一个代理。 +最后,你可以使用`--fetch-tx-data`标志获取构造函数参数: -运行代理: ```bash -python3 proxy/main.py +cli -t 'tests/multi-contract/*' --fetch-tx-data ``` -然后请将`--onchain-local-proxy-addr http://localhost:5003`附加到您的CLI命令中。 +ItyFuzz 将从通过服务器转发到 RPC 的交易中获取构造函数参数。 + +### Z3 安装 + +**macOS** + +```bash +git clone https://github.com/Z3Prover/z3 && cd z3 +python scripts/mk_make.py --prefix=/usr/local +cd build && make -j64 && sudo make install +``` + +如果构建命令仍然因找不到`z3.h`而失败,执行`export Z3_SYS_Z3_HEADER=/usr/local/include/z3.h` + +**Ubuntu** + +```bash +apt install libz3-dev +``` + +### 数据收集 + +ItyFuzz 收集遥测数据以帮助我们改进模糊器。这些数据对我们非常有价值,我们非常感谢你让我们收集它们! + +ItyFuzz 收集以下类型的数据: + +- ItyFuzz 的版本 +- 运行模糊器的操作系统和版本 +- 模糊器运行的时间 +- 使用的命令行参数(不包括你的输入目录) +- 生成的目标列表和数量 +- 发现的漏洞类型和数量 +- 测试生成器和模糊器的统计数据 + +收集的数据不包括任何可以用来识别你的信息,例如 IP 地址,目录名或文件名。 + +默认情况下,ItyFuzz 将在每次运行结束时将遥测数据发送到我们的服务器。你可以在每次运行结束时看到一个摘要,显示发送了哪些数据。 + +如果你不希望 ItyFuzz 收集遥测数据,你可以在运行模糊器时使用`--no-telemetry`标志。 + +```bash +./cli -t '[DIR_PATH]/*' --no-telemetry +``` + +### Citation + +``` +@misc{ityfuzz, + title={ItyFuzz: Snapshot-Based Fuzzer for Smart Contract}, + author={Chaofan Shou and Shangyin Tan and Koushik Sen}, + year={2023}, + eprint={2306.17135}, + archivePrefix={arXiv}, + primaryClass={cs.CR} +} +``` diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 62b7fa596..974202541 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -10,4 +10,9 @@ ityfuzz = {path = "../", version = "0.1.0"} clap = {version = "4.0.18", features = ["derive"]} primitive-types = { version = "0.12.1", features = ["rlp", "serde"] } sentry = "0.29.1" -reqwest = "0.11.6" \ No newline at end of file +reqwest = "0.11.6" +serde_json = "1.0.73" +serde = "1.0.147" +rlp = "0.5.2" +hex = "0.4" +ethers = "2.0.7" \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs index e4335d0c2..e304e60f8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,14 +1,20 @@ use crate::TargetType::{Address, Glob}; use clap::Parser; +use ethers::types::Transaction; +use hex::{decode, encode}; use ityfuzz::evm::config::{Config, FuzzerTypes, StorageFetchingMode}; use ityfuzz::evm::contract_utils::{set_hash, ContractLoader}; +use ityfuzz::evm::host::PANIC_ON_BUG; +use ityfuzz::evm::host::PANIC_ON_TYPEDBUG; use ityfuzz::evm::input::EVMInput; use ityfuzz::evm::middlewares::middleware::Middleware; use ityfuzz::evm::onchain::endpoints::{Chain, OnChainConfig}; use ityfuzz::evm::onchain::flashloan::{DummyPriceOracle, Flashloan}; use ityfuzz::evm::oracles::bug::BugOracle; -use ityfuzz::evm::oracles::selfdestruct::SelfdestructOracle; use ityfuzz::evm::oracles::erc20::IERC20OracleFlashloan; +use ityfuzz::evm::oracles::function::FunctionHarnessOracle; +use ityfuzz::evm::oracles::selfdestruct::SelfdestructOracle; +use ityfuzz::evm::oracles::typed_bug::TypedBugOracle; use ityfuzz::evm::oracles::v2_pair::PairBalanceOracle; use ityfuzz::evm::producers::erc20::ERC20Producer; use ityfuzz::evm::producers::pair::PairProducer; @@ -16,15 +22,15 @@ use ityfuzz::evm::types::{EVMAddress, EVMFuzzState, EVMU256}; use ityfuzz::evm::vm::EVMState; use ityfuzz::fuzzers::evm_fuzzer::evm_fuzzer; use ityfuzz::oracle::{Oracle, Producer}; +use ityfuzz::r#const; use ityfuzz::state::FuzzState; +use serde::Deserialize; use std::cell::RefCell; +use std::collections::HashMap; use std::collections::HashSet; +use std::env; use std::rc::Rc; use std::str::FromStr; -use std::env; -use ityfuzz::evm::host::PANIC_ON_BUG; -use ityfuzz::evm::host::PANIC_ON_TYPEDBUG; -use ityfuzz::evm::oracles::typed_bug::TypedBugOracle; pub fn init_sentry() { let _guard = sentry::init(("https://96f3517bd77346ea835d28f956a84b9d@o4504503751344128.ingest.sentry.io/4504503752523776", sentry::ClientOptions { @@ -41,6 +47,53 @@ pub fn init_sentry() { } } +pub fn parse_constructor_args_string(input: String) -> HashMap> { + let mut map = HashMap::new(); + + if input.len() == 0 { + return map; + } + + let pairs: Vec<&str> = input.split(';').collect(); + for pair in pairs { + let key_value: Vec<&str> = pair.split(':').collect(); + if key_value.len() == 2 { + let values: Vec = key_value[1].split(',').map(|s| s.to_string()).collect(); + map.insert(key_value[0].to_string(), values); + } + } + + map +} + +#[derive(Deserialize)] +struct Data { + body: RPCCall, + response: serde_json::Value, +} + +#[derive(Deserialize)] +struct RPCCall { + method: String, + params: Option, +} + +#[derive(Deserialize)] +struct Response { + data: ResponseData, +} + +#[derive(Deserialize)] +struct ResponseData { + id: u16, + result: TXResult, +} + +#[derive(Deserialize)] +struct TXResult { + input: String, +} + /// CLI for ItyFuzz #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -49,6 +102,15 @@ struct Args { #[arg(short, long)] target: String, + #[arg(long, default_value = "false")] + fetch_tx_data: bool, + + #[arg(long, default_value = "http://localhost:5001/data")] + proxy_address: String, + + #[arg(long, default_value = "")] + constructor_args: String, + /// Target type (glob, address) (Default: Automatically infer from target) #[arg(long)] target_type: Option, @@ -126,7 +188,7 @@ struct Args { #[arg(long, default_value = "true")] selfdestruct_oracle: bool, - + ///Enable oracle for detecting whether typed_bug() is called #[arg(long, default_value = "true")] typed_bug_oracle: bool, @@ -231,13 +293,37 @@ fn main() { // FunctionHarnessOracle::new_no_condition(EVMAddress::zero(), Vec::from(harness_hash)); let mut oracles: Vec< - Rc, EVMInput, EVMFuzzState>>>, + Rc< + RefCell< + dyn Oracle< + EVMState, + EVMAddress, + _, + _, + EVMAddress, + EVMU256, + Vec, + EVMInput, + EVMFuzzState, + >, + >, + >, > = vec![]; let mut producers: Vec< Rc< RefCell< - dyn Producer, EVMInput, EVMFuzzState>, + dyn Producer< + EVMState, + EVMAddress, + _, + _, + EVMAddress, + EVMU256, + Vec, + EVMInput, + EVMFuzzState, + >, >, >, > = vec![]; @@ -286,10 +372,56 @@ fn main() { let is_onchain = onchain.is_some(); let mut state: EVMFuzzState = FuzzState::new(args.seed); + let mut deploy_codes: Vec = vec![]; + + if args.fetch_tx_data { + let response = reqwest::blocking::get(args.proxy_address) + .unwrap() + .text() + .unwrap(); + let data: Vec = serde_json::from_str(&response).unwrap(); + + for d in data { + if d.body.method != "eth_sendRawTransaction" { + continue; + } + + let tx = d.body.params.unwrap(); + + let params: Vec = serde_json::from_value(tx).unwrap(); + + let data = params[0].clone(); + + let data = if data.starts_with("0x") { + &data[2..] + } else { + &data + }; + + let bytes_data = hex::decode(data).unwrap(); + + let transaction: Transaction = rlp::decode(&bytes_data).unwrap(); + + let code = hex::encode(transaction.input); + + deploy_codes.push(code); + } + } + + let constructor_args_map = parse_constructor_args_string(args.constructor_args); + let config = Config { fuzzer_type: FuzzerTypes::from_str(args.fuzzer_type.as_str()).expect("unknown fuzzer"), contract_info: match target_type { - Glob => ContractLoader::from_glob(args.target.as_str(), &mut state).contracts, + Glob => { + ContractLoader::from_glob( + args.target.as_str(), + &mut state, + &deploy_codes, + &constructor_args_map, + ) + .contracts + } Address => { if onchain.is_none() { panic!("Onchain is required for address target type"); @@ -343,7 +475,7 @@ fn main() { }, replay_file: args.replay_file, flashloan_oracle, - selfdestruct_oracle:args.selfdestruct_oracle, + selfdestruct_oracle: args.selfdestruct_oracle, work_dir: args.work_dir, write_relationship: args.write_relationship, run_forever: args.run_forever, diff --git a/server/app.js b/server/app.js new file mode 100644 index 000000000..fb17138a0 --- /dev/null +++ b/server/app.js @@ -0,0 +1,62 @@ +const express = require('express'); +const axios = require('axios'); +const process = require('process'); + +const app = express(); +app.use(express.json({ + verify: (req, res, buf) => { + req.rawBody = buf + } +})) + +let data = []; + +let targetUrl = 'http://localhost:8545'; + +// Parse command line argument for target url +if (process.argv.length > 2) { + targetUrl = process.argv[2]; +} + +// Add an endpoint to retrieve the collected data +app.get('/data', (req, res) => { + res.send(data); +}); + +// Collect and analyze request and response +app.all('*', async (req, res) => { + try { + const { originalUrl, method, headers, query, cookies } = req; + body = req.rawBody.toString(); + const response = await axios({ + method: method, + url: `${targetUrl}${originalUrl}`, + headers: headers, + data: body, + params: query, + }); + console.log(`Request: ${method} ${originalUrl} ${body}`) + console.log(`Response: ${response.status} ${JSON.stringify(response.data)}`); + data.push({ + method: req.method, + url: req.url, + body: req.body, + headers: req.headers, + response: { + status: response.status, + data: response.data, + headers: response.headers, + } + }); + res.set(response.headers).status(response.status).send(response.data) + } catch (err) { + console.error(err); + res.status(500).send(err.toString()); + } +}); + + + +const port = process.env.PORT || 5001; + +app.listen(port, () => console.log(`Server listening on port ${port}`)); diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 000000000..d505013a3 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,672 @@ +{ + "name": "server", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^1.4.0", + "express": "^4.18.2" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 000000000..f129ba565 --- /dev/null +++ b/server/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "axios": "^1.4.0", + "express": "^4.18.2" + } +} \ No newline at end of file diff --git a/src/evm/contract_utils.rs b/src/evm/contract_utils.rs index 356f8f152..9f1ad95f6 100644 --- a/src/evm/contract_utils.rs +++ b/src/evm/contract_utils.rs @@ -1,14 +1,16 @@ +use crate::evm::types::{ + fixed_address, generate_random_address, EVMAddress, EVMFuzzMutator, EVMFuzzState, +}; /// Load contract from file system or remote use glob::glob; use serde_json::Value; use std::collections::{HashMap, HashSet}; -use crate::evm::types::{EVMAddress, EVMFuzzMutator, EVMFuzzState, fixed_address, generate_random_address}; use std::fs::File; +use crate::state::FuzzState; +use itertools::Itertools; use std::io::Read; use std::path::Path; -use itertools::Itertools; -use crate::state::FuzzState; extern crate crypto; use crate::evm::abi::get_abi_type_boxed_with_address; @@ -17,6 +19,8 @@ use crate::evm::srcmap::parser::{decode_instructions, SourceMapLocation}; use self::crypto::digest::Digest; use self::crypto::sha3::Sha3; +use hex::encode; +use regex::Regex; // to use this address, call rand_utils::fixed_address(FIX_DEPLOYER) pub static FIX_DEPLOYER: &str = "8b21e662154b4bbc1ec0754d0238875fe3d22fa6"; @@ -134,17 +138,49 @@ impl ContractLoader { hex::decode(data).expect("Failed to parse hex file") } - pub fn from_prefix(prefix: &str, state: &mut EVMFuzzState, source_map_info: Option) -> Self { + pub fn from_prefix( + prefix: &str, + state: &mut EVMFuzzState, + source_map_info: Option, + deploy_codes: &Vec, + constructor_args: &Vec, + ) -> Self { + let constructor_args_in_bytes: Vec = constructor_args + .iter() + .flat_map(|arg| { + let arg = if arg.starts_with("0x") { + &arg[2..] + } else { + arg + }; + let arg = if arg.len() % 2 == 1 { + format!("0{}", arg) + } else { + arg.to_string() + }; + let mut decoded = hex::decode(arg).unwrap(); + let len = decoded.len(); + if len < 32 { + let mut padding = vec![0; 32 - len]; // Create a vector of zeros + padding.append(&mut decoded); // Append the original vector to it + padding + } else { + decoded + } + }) + .collect(); let mut result = ContractInfo { name: prefix.to_string(), abi: vec![], code: vec![], is_code_deployed: false, - constructor_args: vec![], // todo: fill this + constructor_args: constructor_args_in_bytes, deployed_address: generate_random_address(state), - source_map: source_map_info.map(|info| - info.get(prefix).expect("combined.json provided but contract not found").clone() - ), + source_map: source_map_info.map(|info| { + info.get(prefix) + .expect("combined.json provided but contract not found") + .clone() + }), }; println!("Loading contract {}", prefix); for i in glob(prefix).expect("not such path for prefix") { @@ -185,10 +221,45 @@ impl ContractLoader { } print!("Random bytes {:?}", random_bytes); // result.constructor_args = random_bytes; - result.constructor_args = abi_instance.get().get_bytes(); + if result.constructor_args.len() == 0 { + result.constructor_args = abi_instance.get().get_bytes(); + } // println!("Constructor args: {:?}", result.constructor_args); result.code.extend(result.constructor_args.clone()); } + + let s = hex::encode(&result.code); + + for deploy_code in deploy_codes { + // if deploy_code startwiths '0x' then remove it + let deploy_code = if deploy_code.starts_with("0x") { + &deploy_code[2..] + } else { + deploy_code + }; + + let re = Regex::new(r"63.{8}14").unwrap(); + let matches: Vec = re + .find_iter(deploy_code) + .map(|m| m.as_str().to_string()) + .collect(); + let matches_for_s: Vec = + re.find_iter(&s).map(|m| m.as_str().to_string()).collect(); + + // compare matches and match_for_s + if matches.len() == matches_for_s.len() { + let mut is_match = true; + for i in 0..matches.len() { + if matches[i] != matches_for_s[i] { + is_match = false; + break; + } + } + if is_match { + result.code = hex::decode(deploy_code).expect("Failed to parse deploy code"); + } + } + } return Self { contracts: if result.code.len() > 0 { vec![result] @@ -205,7 +276,12 @@ impl ContractLoader { // |- contract1.bin // |- contract2.abi // |- contract2.bin - pub fn from_glob(p: &str, state: &mut EVMFuzzState) -> Self { + pub fn from_glob( + p: &str, + state: &mut EVMFuzzState, + deploy_codes: &Vec, + constructor_args_map: &HashMap>, + ) -> Self { let mut prefix_file_count: HashMap = HashMap::new(); let mut contract_combined_json_info = None; for i in glob(p).expect("not such folder") { @@ -242,11 +318,25 @@ impl ContractLoader { let mut contracts: Vec = vec![]; for (prefix, count) in prefix_file_count { + let p = prefix.to_string(); if count == 2 { - for contract in - Self::from_prefix((prefix.to_owned() + &String::from('*')).as_str(), - state, - parsed_contract_info.clone()).contracts + let mut constructor_args: Vec = vec![]; + for (k, v) in constructor_args_map.iter() { + let components: Vec<&str> = p.split('/').collect(); + if let Some(last_component) = components.last() { + if last_component == &k { + constructor_args = v.clone(); + } + } + } + for contract in Self::from_prefix( + (prefix.to_owned() + &String::from('*')).as_str(), + state, + parsed_contract_info.clone(), + deploy_codes, + &constructor_args, + ) + .contracts { contracts.push(contract); } @@ -256,7 +346,6 @@ impl ContractLoader { ContractLoader { contracts } } - pub fn from_address(onchain: &mut OnChainConfig, address: HashSet) -> Self { let mut contracts: Vec = vec![]; for addr in address { @@ -286,8 +375,11 @@ type ContractsSourceMapInfo = HashMap> pub fn parse_combined_json(json: String) -> ContractsSourceMapInfo { let map_json = serde_json::from_str::(&json).unwrap(); - let contracts = map_json["contracts"].as_object().expect("contracts not found"); - let file_list = map_json["sourceList"].as_array() + let contracts = map_json["contracts"] + .as_object() + .expect("contracts not found"); + let file_list = map_json["sourceList"] + .as_array() .expect("sourceList not found") .iter() .map(|x| x.as_str().expect("sourceList is not string").to_string()) @@ -297,21 +389,21 @@ pub fn parse_combined_json(json: String) -> ContractsSourceMapInfo { for (contract_name, contract_info) in contracts { let splitter = contract_name.split(':').collect::>(); - let file_name = splitter.iter().take(splitter.len()-1).join(":"); + let file_name = splitter.iter().take(splitter.len() - 1).join(":"); let contract_name = splitter.last().unwrap().to_string(); - let bin_runtime = contract_info["bin-runtime"].as_str().expect("bin-runtime not found"); + let bin_runtime = contract_info["bin-runtime"] + .as_str() + .expect("bin-runtime not found"); let bin_runtime_bytes = hex::decode(bin_runtime).expect("bin-runtime is not hex"); - let srcmap_runtime = contract_info["srcmap-runtime"].as_str().expect("srcmap-runtime not found"); + let srcmap_runtime = contract_info["srcmap-runtime"] + .as_str() + .expect("srcmap-runtime not found"); result.insert( contract_name.clone(), - decode_instructions( - bin_runtime_bytes, - srcmap_runtime.to_string(), - &file_list - ) + decode_instructions(bin_runtime_bytes, srcmap_runtime.to_string(), &file_list), ); } result @@ -323,7 +415,9 @@ mod tests { #[test] fn test_load() { - let loader = ContractLoader::from_glob("demo/*", &mut FuzzState::new(0)); + let codes: Vec = vec![]; + let args: HashMap> = HashMap::new(); + let loader = ContractLoader::from_glob("demo/*", &mut FuzzState::new(0), &codes, &args); println!( "{:?}", loader diff --git a/src/evm/corpus_initializer.rs b/src/evm/corpus_initializer.rs index e283e444a..91ba325a2 100644 --- a/src/evm/corpus_initializer.rs +++ b/src/evm/corpus_initializer.rs @@ -7,7 +7,9 @@ use crate::evm::input::{EVMInput, EVMInputTy}; use crate::evm::mutator::AccessPattern; use crate::evm::onchain::onchain::BLACKLIST_ADDR; -use crate::evm::types::{EVMAddress, EVMFuzzState, EVMInfantStateState, EVMStagedVMState, EVMU256, fixed_address}; +use crate::evm::types::{ + fixed_address, EVMAddress, EVMFuzzState, EVMInfantStateState, EVMStagedVMState, EVMU256, +}; use crate::evm::vm::{EVMExecutor, EVMState}; use crate::generic_vm::vm_executor::GenericVM; @@ -26,10 +28,11 @@ use std::ops::Deref; use crate::evm::onchain::flashloan::register_borrow_txn; use crate::evm::presets::presets::Preset; +use crate::evm::srcmap::parser::{decode_instructions, SourceMapLocation}; +use hex; +use itertools::Itertools; use std::rc::Rc; use std::time::Duration; -use itertools::Itertools; -use crate::evm::srcmap::parser::{decode_instructions, SourceMapLocation}; pub struct EVMCorpusInitializer<'a> { executor: &'a mut EVMExecutor, @@ -219,9 +222,11 @@ impl<'a> EVMCorpusInitializer<'a> { ]); for caller in contract_callers { self.state.add_caller(&caller); - self.executor - .host - .set_code(caller, Bytecode::new_raw(Bytes::from(vec![0xfd, 0x00])), self.state); + self.executor.host.set_code( + caller, + Bytecode::new_raw(Bytes::from(vec![0xfd, 0x00])), + self.state, + ); } } diff --git a/src/evm/vm.rs b/src/evm/vm.rs index 45196cf53..2ef5c1f52 100644 --- a/src/evm/vm.rs +++ b/src/evm/vm.rs @@ -19,8 +19,8 @@ use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; +use crate::evm::types::{float_scale_to_u512, EVMU512}; use crate::input::VMInputT; -use crate::evm::types::{EVMU512, float_scale_to_u512}; use crate::state_input::StagedVMState; use crate::tracer::build_basic_txn_from_input; use bytes::Bytes; @@ -36,7 +36,6 @@ use revm::db::BenchmarkDB; use revm_interpreter::{CallContext, CallScheme, Contract, InstructionResult, Interpreter}; use revm_primitives::{Bytecode, LatestSpec}; - use crate::evm::bytecode_analyzer; use crate::evm::concolic::concolic_exe_host::ConcolicEVMExecutor; use crate::evm::host::{ @@ -46,6 +45,7 @@ use crate::evm::host::{ use crate::evm::input::{EVMInputT, EVMInputTy}; use crate::evm::middlewares::middleware::{Middleware, MiddlewareType}; use crate::evm::onchain::flashloan::FlashloanData; +use crate::evm::types::{EVMAddress, EVMU256}; use crate::evm::uniswap::generate_uniswap_router_call; use crate::generic_vm::vm_executor::{ExecutionResult, GenericVM, MAP_SIZE}; use crate::generic_vm::vm_state::VMStateT; @@ -53,7 +53,6 @@ use crate::r#const::DEBUG_PRINT_PERCENT; use crate::state::{HasCaller, HasCurrentInputIdx, HasItyState}; use serde::{Deserialize, Serialize}; use serde_traitobject::Any; -use crate::evm::types::{EVMAddress, EVMU256}; /// Get the token context from the flashloan middleware, /// which contains uniswap pairs of that token @@ -242,12 +241,10 @@ impl EVMState { } } - /// Is current EVM execution fast call /// - Fast call is a call that does not change the state of the contract pub static mut IS_FAST_CALL: bool = false; - /// EVM executor, wrapper of revm #[derive(Debug, Clone)] pub struct EVMExecutor @@ -263,7 +260,6 @@ where phandom: PhantomData<(I, S, VS)>, } - /// Execution result that may have control leaked /// Contains raw information of revm output and execution #[derive(Clone, Debug)] @@ -392,8 +388,7 @@ where } else { // if there is no post execution context, then we create the interpreter from the // beginning - let call = - Contract::new_with_context_analyzed(data, bytecode, call_ctx); + let call = Contract::new_with_context_analyzed(data, bytecode, call_ctx); Interpreter::new(call, 1e10 as u64, false) }; @@ -596,7 +591,10 @@ where unsafe { ExecutionResult { output: r.output.to_vec(), - reverted: r.ret != InstructionResult::Return && r.ret != InstructionResult::Stop && r.ret != ControlLeak && r.ret != InstructionResult::SelfDestruct, + reverted: r.ret != InstructionResult::Return + && r.ret != InstructionResult::Stop + && r.ret != ControlLeak + && r.ret != InstructionResult::SelfDestruct, new_state: StagedVMState::new_with_state( VMStateT::as_any(&mut r.new_state) .downcast_ref_unchecked::() @@ -606,16 +604,20 @@ where vec![self.host.call_count as u8] } else { vec![u8::MAX] - }) + }), } } } - pub fn reexecute_with_middleware(&mut self, input: &I, state: &mut S, middleware: Rc>>) { + pub fn reexecute_with_middleware( + &mut self, + input: &I, + state: &mut S, + middleware: Rc>>, + ) { self.host.add_middlewares(middleware.clone()); self.execute(input, state); self.host.remove_middlewares(middleware); - } } @@ -657,7 +659,8 @@ where let mut interp = Interpreter::new(deployer, 1e10 as u64, false); self.host.middlewares_enabled = middleware_status; let mut dummy_state = S::default(); - let r = interp.run_inspect::, LatestSpec>(&mut self.host, &mut dummy_state); + let r = interp + .run_inspect::, LatestSpec>(&mut self.host, &mut dummy_state); #[cfg(feature = "evaluation")] { self.host.pc_coverage = Default::default(); @@ -667,7 +670,11 @@ where return None; } assert_eq!(r, InstructionResult::Return); - println!("deployer = 0x{} contract = {:?}", hex::encode(self.deployer),hex::encode(interp.return_value())); + println!( + "deployer = 0x{} contract = {:?}", + hex::encode(self.deployer), + hex::encode(interp.return_value()) + ); let contract_code = Bytecode::new_raw(interp.return_value()); bytecode_analyzer::add_analysis_result_to_state(&contract_code, state); self.host.set_code(deployed_address, contract_code, state); @@ -676,13 +683,21 @@ where /// Execute an input (transaction) #[cfg(not(feature = "flashloan_v2"))] - fn execute(&mut self, input: &I, state: &mut S) -> ExecutionResult> { + fn execute( + &mut self, + input: &I, + state: &mut S, + ) -> ExecutionResult> { self.execute_abi(input, state) } /// Execute an input (can be transaction or borrow) #[cfg(feature = "flashloan_v2")] - fn execute(&mut self, input: &I, state: &mut S) -> ExecutionResult> { + fn execute( + &mut self, + input: &I, + state: &mut S, + ) -> ExecutionResult> { match input.get_input_type() { // buy (borrow because we have infinite ETH) tokens with ETH using uniswap EVMInputTy::Borrow => { @@ -735,7 +750,7 @@ where .downcast_ref_unchecked::() .clone(), ), - additional_info: Some(vec![input.get_randomness()[0]]) + additional_info: Some(vec![input.get_randomness()[0]]), } } } @@ -744,7 +759,7 @@ where output: vec![], reverted: false, new_state: StagedVMState::new_with_state(input.get_state().clone()), - additional_info: None + additional_info: None, }, } } @@ -774,8 +789,8 @@ where self.host.randomness = vec![9]; } - data.iter().map( - |(address, by)| { + data.iter() + .map(|(address, by)| { let ctx = CallContext { address: *address, caller: Default::default(), @@ -784,20 +799,17 @@ where scheme: CallScheme::StaticCall, }; let code = self.host.code.get(&address).expect("no code").clone(); - let call = Contract::new_with_context_analyzed( - by.clone(), - code.clone(), - &ctx, - ); + let call = Contract::new_with_context_analyzed(by.clone(), code.clone(), &ctx); let mut interp = Interpreter::new(call, 1e10 as u64, false); - let ret = interp.run_inspect::, LatestSpec>(&mut self.host, state); + let ret = + interp.run_inspect::, LatestSpec>(&mut self.host, state); if ret == InstructionResult::Revert { vec![] } else { interp.return_value().to_vec() } - } - ).collect::>>() + }) + .collect::>>() } fn get_jmp(&self) -> &'static mut [u8; MAP_SIZE] { @@ -825,7 +837,7 @@ mod tests { use crate::evm::host::{FuzzHost, JMP_MAP}; use crate::evm::input::{EVMInput, EVMInputTy}; use crate::evm::mutator::AccessPattern; - use crate::evm::types::{EVMFuzzState, EVMU256, generate_random_address}; + use crate::evm::types::{generate_random_address, EVMFuzzState, EVMU256}; use crate::evm::vm::{EVMExecutor, EVMState}; use crate::generic_vm::vm_executor::{GenericVM, MAP_SIZE}; use crate::state::FuzzState;