#fuzz #substrate #ink

nightly no-std bin+lib phink

πŸ™ Phink, a ink! smart-contract property-based and coverage-guided fuzzer

1 unstable release

new 0.1.3 Oct 17, 2024

#370 in Magic Beans

Download history 109/week @ 2024-10-13

109 downloads per month

WTFPL and GPL-3.0-only

4MB
6.5K SLoC

Rust 5.5K SLoC // 0.0% comments JavaScript 1K SLoC // 0.1% comments Shell 55 SLoC // 0.2% comments

Contains (WOFF font, 99KB) fontawesome-webfont.woff, (WOFF font, 78KB) fontawesome-webfont.woff2, (WOFF font, 45KB) open-sans-v17-all-charsets-300.woff2, (WOFF font, 41KB) open-sans-v17-all-charsets-300italic.woff2, (WOFF font, 45KB) open-sans-v17-all-charsets-600.woff2, (WOFF font, 43KB) open-sans-v17-all-charsets-600italic.woff2 and 7 more.

phink

Build Status License dependency status Discord Documentation


Phink is a blazing-fast⚑, property-based, coverage-guided fuzzer for ink! smart contracts. It enables developers to embed inviolable properties into their smart contract testing workflows, equipping them with automatic tools to detect vulnerabilities and ensure contract reliability before deployment.

For comprehensive documentation, visit our official documentation site. If you have any question, feedback, features suggestion, join our Discord.

⚠️ This project is actively under development with new features and improvements being made regularly. Contributions and feedback are welcome!

Install

Building from source

From Cargo via Git

cargo install --git https://github.com/srlabs/phink
phink --help

If you prefer to install Phink manually, follow these steps:

git clone https://github.com/kevin-valerio/phink
cd phink/
cargo install --force ziggy cargo-afl honggfuzz grcov cargo-contract --locked 
sudo -E cargo afl config --build --plugins --verbose --force # don't use `--plugins` if you're on macOS
sudo cargo-afl afl system-config
cargo build --release
./target/release/phink --help

Using Docker

Alternatively, you can use Docker to set up and run Phink without needing to manually install dependencies. Detailed instructions are available in README.Docker.md.

To build the Docker image:

docker build -t phink .

Usage

Docker Usage

To use Phink via Docker, you can run:

docker run --rm phink

For instrumenting a specific contract:

docker run --rm phink instrument path/to/ink_contract

Refer to README.Docker.md for more detailed instructions on using Phink with Docker.

Manual Usage

phink instrument path/to/ink_contract
phink fuzz  

Example

Adding some invariants

Below are some invariants created for the dns contract.

  1. Add the phink feature to your Cargo.toml
[features]
...
phink = []
  1. Create your invariants as below:
#[cfg(feature = "phink")]
#[ink(impl)]
impl DomainNameService {
    // This invariant ensures that `domains` doesn't contain the forbidden domain that nobody should regsiter 
    #[ink(message)]
    #[cfg(feature = "phink")]
    pub fn phink_assert_hash42_cant_be_registered(&self) {
        for i in 0..self.domains.len() {
            if let Some(domain) = self.domains.get(i) {
                // Invariant triggered! We caught an invalid domain in the storage...
                assert_ne!(domain.clone().as_mut(), FORBIDDEN_DOMAIN);
            }
        }
    }

    // This invariant ensures that nobody registed the forbidden number
    #[ink(message)]
    #[cfg(feature = "phink")]
    pub fn phink_assert_dangerous_number(&self) {
        let forbidden_number = 42;
        assert_ne!(self.dangerous_number, forbidden_number);
    }
}

Catching an invariant

phink execute output/phink/crashes/<timestamp>/<id:000x:seed>  

Below, the trace after executing the crash:

πŸš€ Now fuzzing `/tmp/ink_fuzzed_XqUCn/target/ink/transfer.json` (5H31F11yQUkqugbgC7ur4rT2WLKSkZKAZUfcmHkKoLkaRaZ4)!

🀯 An invariant got caught! Let's dive into it

🫡  This was caused by `phink_assert_cannot_transfer_1337`

πŸŽ‰ Find below the trace that caused that invariant

🌱 Executing new seed

+---------+-------------------------------------------------------------------+
| Message | Details                                                           |
+---------+-------------------------------------------------------------------+
| pay_me  |  ⛽️ Gas required : Weight(ref_time: 591391866, proof_size: 28781) |
|         | πŸ”₯ Gas consumed : Weight(ref_time: 582570121, proof_size: 12443)  |
|         | πŸ’Ύ Storage deposit : StorageDeposit::Charge(0)                    |
|         | πŸ’Έ Message was payable, and 1809739 units were transferred        |
+---------+-------------------------------------------------------------------+
thread 'main' panicked at src/fuzzer/bug.rs:83:9:

Job is done! Please, don't matter the backtrace below/above 🫑

List of samples

You can find various sample ink! smart-contracts in the sample/ directory. For detailed descriptions of these samples and instructions on how to instrument them for testing with Phink, please refer to the sample's README file.

Features and upcoming ideas

  • Integration of a custom runtime, using a generic one by default
  • Invariants-based fuzzing
  • Detection of incorrect arithmetic, reentrancy, and panic handlers
  • Handling of ink! specific encoding and constructors
  • Automatic contract instantiation
  • Crafting multiple messages in a single transaction
  • Visualization of ink! contract coverage
  • Proper binary usage
  • Enabling multi-contract fuzzing and cross-contract interactions
  • Development of a custom fuzzing dashboard (default options: Ziggy/AFL++/Honggfuzz dashboard)
  • Creation of default invariants common to every contract (research needed)
  • Provision of a specified on-chain state (research needed)
  • Implementation of a snapshot-based fuzzing approach (research needed)
  • Extraction of seeds and constants from the codebase (research needed)
  • Creation of LLM-based invariants using rust-llama (research needed)

Dependencies

~40–54MB
~1M SLoC