3 releases
0.1.5 | Dec 5, 2024 |
---|---|
0.1.4 | Oct 28, 2024 |
0.1.3 | Oct 17, 2024 |
#19 in #ink
342 downloads per month
10MB
7K
SLoC
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 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. Please, read the documentation in order to properly use Phink.
For documentation, visit our documentation site here. If you have any question, feedback, features suggestion, join our Discord.
Install
Important note: Phink requires Cargo and can only be used by running it from source code. You can't install it via
cargo install
. Since the fuzzing harness needs to be compiled with instrumentation and is included within the tool,
you must use cargo run
with the source code to execute Phink.
Building from source
Requirements
Install the requirements, configure AFL++ plugins and adapt the system configs
cargo install --force ziggy cargo-afl honggfuzz grcov cargo-contract --locked
cargo afl config --build --plugins --verbose --force # don't use `--plugins` if you're on macOS
sudo cargo-afl afl system-config
Install Phink
git clone https://github.com/srlabs/phink && cd phink;
cargo run -- help
Install Phink via 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.
Usage
Via normal installation
cargo run -- instrument path/to/ink_contract
cargo run -- generate-seed path/to/ink_contract #optional but recommended
cargo run -- fuzz
Example
Adding some invariants (required!)
Below are some invariants created for the dns contract.
- Add the
phink
feature to yourCargo.toml
[features]
phink = []
- 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
cargo run -- 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 |
+---------+-------------------------------------------------------------------+
Generating a coverage report
The user can generate a coverage report after running all the seeds with run
. The only parameter required is the one
for coverage
, which corresponds to the instrumented contract path. Here is a possible output of the
coverage statistics:
cargo run -- run && cargo run -- coverage toooooooooooz/
π Coverage report generated at: output/phink/contract_coverage π Phink Coverage Benchmark:
- Total contract's files: 1
- Total of unique hit lines: 47
- Maximum theoretically reachable coverage: 74
- Coverage percentage: 63%
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
- Benchmarking the fuzzing campaign and generating statistics
- Enabling multi-contract fuzzing and cross-contract interactions
- Seed extraction out of unit and end-to-end tests
- Development of a custom fuzzing dashboard (default options: Ziggy/AFL++/
Honggfuzzdashboard) - Implementation of a snapshot-based fuzzing approach (research needed)
- Migrate from pallet-contract to pallet-revive and targetting PolkaVM (research needed)
Dependencies
~40β54MB
~1M SLoC