<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Phink Book</title>
<meta name="robots" content="noindex">
<!-- Custom HTML head -->
<meta name="description" content="Documentation for Phink fuzzer">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="INTRO.html">Introduction</a></li><li class="chapter-item expanded affix "><li class="part-title">User guide</li><li class="chapter-item expanded "><a href="START.html"><strong aria-hidden="true">1.</strong> Installation</a></li><li class="chapter-item expanded "><a href="CONFIG.html"><strong aria-hidden="true">2.</strong> Configuration</a></li><li class="chapter-item expanded "><a href="CAMPAIGN.html"><strong aria-hidden="true">3.</strong> Starting a campaign</a></li><li class="chapter-item expanded "><a href="INVARIANTS.html"><strong aria-hidden="true">4.</strong> Invariants</a></li><li class="chapter-item expanded "><a href="RUNTIME.html"><strong aria-hidden="true">5.</strong> Plug-in your runtime</a></li><li class="chapter-item expanded "><a href="SEEDS.html"><strong aria-hidden="true">6.</strong> Seeds</a></li><li class="chapter-item expanded affix "><li class="part-title">Concepts and understanding</li><li class="chapter-item expanded "><a href="CONCEPT.html"><strong aria-hidden="true">7.</strong> Concept and terminology</a></li><li class="chapter-item expanded "><a href="TECH.html"><strong aria-hidden="true">8.</strong> How does Phink work</a></li><li class="chapter-item expanded "><a href="TROUBLESHOTING.html"><strong aria-hidden="true">9.</strong> Troubleshoting</a></li><li class="chapter-item expanded "><a href="BENCHMARKS.html"><strong aria-hidden="true">10.</strong> Benchmarks</a></li><li class="chapter-item expanded "><a href="FAQ.html"><strong aria-hidden="true">11.</strong> FAQ</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Phink Book</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/srlabs/phink/" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<center>
<img src="https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/assets/phink.png" alt="phink" width="250"/>
<h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
</center>
<h2 id="overview-of-phink"><a class="header" href="#overview-of-phink">Overview of Phink</a></h2>
<p><strong>Phink</strong> is a blazing-fastâĄ, property-based, coverage-guided fuzzer for ink! smart contracts. It lets developers
embed inviolable properties into smart contract testing workflows, equipping teams with automatic tools to detect
vulnerabilities and ensure contract reliability before deployment.</p>
<h3 id="dashboard-overview"><a class="header" href="#dashboard-overview">Dashboard overview</a></h3>
<img src="https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/assets/dashboard.png" alt="phink"/>
<h3 id="key-features"><a class="header" href="#key-features">Key features</a></h3>
<h4 id="property-based-testing"><a class="header" href="#property-based-testing">Property-based testing</a></h4>
<p>Phink requires developers to define properties directly within ink! smart contracts. By prefixing functions with
<code>phink</code>, such as <code>fn phink_assert_abc_always_true()</code>, you create properties that
act as assertions. During testing, the fuzzer checks these properties against every input, which is a set of ink!
messages. If a
propertyâs assertion fails, this
triggers a panic. An invariant has been broken! This method ensures thorough validation of contract logic
and behavior.</p>
<h4 id="coverage-guided-fuzzing"><a class="header" href="#coverage-guided-fuzzing">Coverage-guided fuzzing</a></h4>
<p>In order to become coverage-guided, Phink needs to instrument the ink! smart contract.
Feedback is transmitted to the <code>pallet_contract</code> via the <code>debug_message</code>.
Although the fuzzer
currently adds feedback on each line executed, Phink is designed to evolve. It will eventually monitor coverage across
new
edges and code branches.</p>
<h3 id="why-use-phink"><a class="header" href="#why-use-phink">Why use Phink</a></h3>
<p>Phink addresses security concerns in these three main ways:</p>
<ol>
<li><strong>Automatically generate and test</strong> a diverse range of inputs</li>
<li><strong>Detect</strong> edge cases, logical flaws, and bugs leading to contract state reversion</li>
<li><strong>Explore</strong> different execution paths by generating input mutation</li>
</ol>
<p>This extensive testing identifies bugs and potential flaws early in the development cycle, empowering teams to
fix vulnerabilities before deployment and deliver safer applications.</p>
<div style="break-before: page; page-break-before: always;"></div><h2 id="getting-started-with-phink"><a class="header" href="#getting-started-with-phink">Getting started with Phink</a></h2>
<h3 id="installation"><a class="header" href="#installation">Installation</a></h3>
<h4 id="system-requirements"><a class="header" href="#system-requirements">System requirements</a></h4>
<p>To successfully install and run Phink, ensure your system meets the following requirements:</p>
<ul>
<li>
<p><strong>Operating System:</strong></p>
<ul>
<li><strong>Linux:</strong> <strong>Recommended</strong> for compatibility</li>
<li><strong>macOS:</strong> <em>Not recommended</em> as it doesnât support some AFL++ plugins</li>
<li><strong>Windows:</strong> <em>Untested</em></li>
</ul>
</li>
<li>
<p><strong>Rust:</strong></p>
<ul>
<li><strong>Version:</strong> Rust <em>nightly</em></li>
<li><strong>Current Compatibility:</strong> <code>cargo 1.83.0-nightly (ad074abe3 2024-10-04)</code></li>
</ul>
</li>
</ul>
<h4 id="installation-guide"><a class="header" href="#installation-guide">Installation guide</a></h4>
<p>You can install Phink by building it from the source or by using Docker. Choose the method that best suits your setup
and IT environment. Letâs jump right into it!</p>
<h5 id="building-from-source"><a class="header" href="#building-from-source">Building from source</a></h5>
<p>Follow these 5 easy steps:</p>
<ol>
<li>
<p><strong>Clone the Repository</strong></p>
<pre><code class="language-bash">git clone https://github.com/srlabs/phink && cd phink/
</code></pre>
<p>You can also run the following command:</p>
<pre><code class="language-bash">cargo +nightly install --git https://github.com/srlabs/phink
</code></pre>
</li>
<li>
<p><strong>Install Dependencies</strong></p>
<pre><code class="language-bash">cargo install --force ziggy cargo-afl honggfuzz grcov cargo-contract --locked
</code></pre>
</li>
<li>
<p><strong>Configure AFL++</strong></p>
<pre><code class="language-bash">cargo afl config --build --plugins --verbose --force
sudo cargo-afl afl system-config
</code></pre>
</li>
<li>
<p><strong>Build Phink</strong></p>
<pre><code class="language-bash">cargo build --release
</code></pre>
</li>
<li>
<p><strong>Run Phink</strong></p>
<pre><code class="language-bash">cargo run -- help
</code></pre>
</li>
</ol>
<h5 id="using-docker"><a class="header" href="#using-docker">Using Docker</a></h5>
<ol>
<li>
<p><strong>Build the Docker Image</strong>
To build the <strong>Phink Docker image</strong>, run the following command in your terminal:</p>
<pre><code class="language-bash">docker build -t phink .
</code></pre>
</li>
</ol>
<p>For detailed Phink Docker installation instructions, refer
to <a href="https://github.com/srlabs/phink/blob/main/README.Docker.md">README.Docker.md</a>.</p>
<h3 id="basic-workflow"><a class="header" href="#basic-workflow">Basic workflow</a></h3>
<p>Follow these three high-level steps:</p>
<ol>
<li>
<p><strong>Instrument the contract</strong></p>
<ul>
<li>Use Phink to instrument your ink! smart contract for fuzzing</li>
</ul>
</li>
<li>
<p><strong>Configure fuzzing parameters</strong></p>
<ul>
<li>Edit the <code>phink.toml</code> file to set paths, deployment settings, and fuzzing options according to your project needs</li>
</ul>
</li>
<li>
<p><strong>Run your fuzzing campaign</strong></p>
<ul>
<li>Execute fuzzing with your configured settings to identify vulnerabilities early in the development cycle</li>
</ul>
</li>
</ol>
<div style="break-before: page; page-break-before: always;"></div><h1 id="phink-configuration-guide"><a class="header" href="#phink-configuration-guide">Phink configuration guide</a></h1>
<p>This guide provides an overview of the Phink configuration settings. You will learn how to configure the general
settings, specify some key paths and fuzzing options, and do some other essential tasks. Without further ado, letâs jump
right into it!</p>
<h2 id="configuration-file-overview"><a class="header" href="#configuration-file-overview">Configuration file overview</a></h2>
<p>Hereâs how a configuration file looks like:</p>
<pre><code class="language-toml">### Phink Configuration
# General Settings
cores = 10 # Set to 1 for single-core execution
max_messages_per_exec = 1 # Maximum number of message calls per input
# Paths
instrumented_contract_path.path = "toooooooooooz" # Path to the instrumented contract, after `phink instrument my_contract` is invoked
report_path = "output/phink/contract_coverage" # Directory for coverage HTML files
fuzz_output = "output" # Directory for fuzzing output
# Deployment
deployer_address = "5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT" # Contract deployer address (Alice by default)
constructor_payload = "9BAE9D5E" # Hexadecimal scale-encoded data for contract instantiation
storage_deposit_limit = "100000000000" # Storage deposit limit
instantiate_initial_value = "0" # Value transferred during instantiation, if needed
# Fuzzing Options
fuzz_origin = false # Attempt to call each message as a different user (affects performance)
verbose = true # Print detailed debug messages
show_ui = true # Display advanced UI
use_honggfuzz = false # Use Honggfuzz (set as false)
catch_trapped_contract = false # Not setting trapped contract as a bug, only detecting invariant-based bugs
# Gas Limits
[default_gas_limit]
ref_time = 100_000_000_000 # Reference time for gas
proof_size = 3_145_728 # Proof size (3 * 1024 * 1024 bytes)
</code></pre>
<h2 id="general-settings"><a class="header" href="#general-settings">General settings</a></h2>
<p>The General settings cover these 2 parameters:</p>
<ul>
<li><strong>cores</strong>: Allocate the number of CPU cores for fuzzing. Setting this to <code>1</code> enables single-core execution. We *
<em>highly</em>* not recommend using single-core, since this will dissalow <code>CMPLOG</code> feature from AFL++.</li>
<li><strong>max_messages_per_exec</strong>: Define the maximum number of message calls allowed per fuzzing input. If you want to fuzz
one function per one function, set this number to 1. Setting it to zero will fuzz zero message. Setting it, for
example,
to 4 will generate 4 different messages in one input, run all the invariants, and go to the next input.</li>
</ul>
<h2 id="paths"><a class="header" href="#paths">Paths</a></h2>
<p>The Paths settings cover these 3 parameters:</p>
<ul>
<li><strong>instrumented_contract_path.path</strong>: Specify the path to the instrumented contract, which should be set
post-invocation of <code>phink instrument my_contract</code>. This path will contain the source code of the initial contract,
with the additional instrumentation instructions. It will also contain the instrumented compiled contract.</li>
<li><strong>report_path</strong>: Designate the directory where HTML coverage reports will be generated if the user wishes to generate
a coverage report.</li>
<li><strong>fuzz_output</strong>: Indicate the directory for storing all fuzzing output. This output is important as it will contain
the log file, the corpus entries, the crashes, and way more.</li>
</ul>
<h2 id="deployment"><a class="header" href="#deployment">Deployment</a></h2>
<p>The Deployment settings include these 4 parameters:</p>
<ul>
<li><strong>deployer_address</strong>: Set the address of the smart contract deployer. The default is Aliceâs address.</li>
<li><strong>constructor_payload</strong>: Hexadecimal scale-encoded data necessary for contract instantiation. This is used when
calling <code>bare_instantiate</code> extrinsic to instantiate the contract. You can use https://ui.use.ink/ to generate this
payload. By default, Phink will deploy the contract using the constructor that has no arguments <code>new()</code>.</li>
<li><strong>storage_deposit_limit</strong>: Limit for storage deposits during contract deployment. It represents
an optional cap on the amount of blockchain storage (measured in balance units) that can be used or reserved by the
contract call.</li>
<li><strong>instantiate_initial_value</strong>: Initial value to be transferred upon contract instantiation if required. So if the
contract requires a minimum amount of 3000 units during instantiation, set 3000 here.</li>
</ul>
<h2 id="fuzzing-options"><a class="header" href="#fuzzing-options">Fuzzing options</a></h2>
<p>These 4 parameters are important when you configure fuzzing options:</p>
<ul>
<li><strong>fuzz_origin</strong>: A Boolean option to try calling each message as a different user, which may impact performance. If
set to <code>false</code>, the fuzzer will fuzz any message with the one input (Alice).</li>
<li><strong>verbose</strong>: Enables detailed debugging of messages when set to <code>true</code>. This will just output more logs.</li>
<li><strong>show_ui</strong>: Toggle for displaying the advanced user interface.</li>
<li><strong>use_honggfuzz</strong>: Determines whether to use Honggfuzz; remains <code>false</code> by
default. (<strong>let it false! is not handled currently</strong>)</li>
<li><strong>catch_trapped_contract</strong>: Indicate whether the fuzzer should treat trapped contracts as bugs.
<ul>
<li>When set to <code>true</code>: The fuzzer will identify any contracts that become trapped (<code>ContractTrapped</code>) as bugs. This
is
useful for an examination of potential issues, as it covers all types of bugs, not just ones related to
logic or state invariants.</li>
<li>When set to <code>false</code>: Focuses only on catching bugs related to invariant violations, ignoring trapped contract
scenarios. This is preferable when you are only interested in logical correctness and not in trapping errors.</li>
</ul>
</li>
</ul>
<h2 id="gas-limit"><a class="header" href="#gas-limit">Gas limit</a></h2>
<p>The gas limit refers to the maximum amount of computational effort (or weight) that an execution is allowed to use when
performing a call to a contract. It controls how much balance a contract is allowed to use for expanding its state
storage during execution. The setting ensures that users wonât unintentionally spend more than they wanted on storage
allocation. Besides, it offers protection against excessive storage costs by defining an upper limit on how much can be
spent on storage
within that call.</p>
<h3 id="default-gas-limit-configuration"><a class="header" href="#default-gas-limit-configuration">Default gas limit configuration</a></h3>
<p>The key Gas limit settings include:</p>
<ul>
<li><strong>ref_time</strong>: Specify the reference time for gas allocation.</li>
<li><strong>proof_size</strong>: Define the proof size (e.g., <code>3145728</code> corresponds to 3 MB).</li>
</ul>
<div style="break-before: page; page-break-before: always;"></div><h2 id="writing-properties-for-ink-contracts"><a class="header" href="#writing-properties-for-ink-contracts">Writing properties for ink! contracts</a></h2>
<h3 id="adding-properties"><a class="header" href="#adding-properties">Adding properties</a></h3>
<h4 id="inside-your-cargotoml"><a class="header" href="#inside-your-cargotoml">Inside your <code>Cargo.toml</code></a></h4>
<p>First, you need to add the <code>phink</code> feature to your <code>Cargo.toml</code>, such as:</p>
<pre><code class="language-toml">[features]
phink = []
</code></pre>
<h4 id="inside-your-filers"><a class="header" href="#inside-your-filers">Inside your <code>file.rs</code></a></h4>
<p>Then, you can use the following example to create invariants. Create another <code>impl</code> in your contract, and
put
it under the feature of <code>phink</code>. Use <code>assert!</code> or <code>panic!</code> for your properties.</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[cfg(feature = "phink")]
#[ink(impl)]
impl DomainNameService {
// 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);
}
}
<span class="boring">}</span></code></pre></pre>
<p>You can find more informations in the page dedicated to <a href="INVARIANTS.html">invariants</a>.</p>
<h2 id="running-phink"><a class="header" href="#running-phink">Running Phink</a></h2>
<h3 id="1-instrument-the-contract"><a class="header" href="#1-instrument-the-contract">1. Instrument the contract</a></h3>
<p>First things first: Letâs enable your contract for fuzzing. Run the following command to instrument your ink! smart
contract:</p>
<pre><code class="language-sh">cargo run -- instrument my_contract/
</code></pre>
<p>This step modifies the contract to include necessary hooks for Phinkâs fuzzing process. It creates a fork of the
contract, so you donât have to make a copy before.</p>
<h3 id="2-generate-seeds-optionnal-but-highly-recommended"><a class="header" href="#2-generate-seeds-optionnal-but-highly-recommended">2. Generate seeds (optionnal but highly recommended)</a></h3>
<p>The <code>cargo run -- generate-seed</code> command is an optional but powerful feature that enhances your fuzzing experience by
generating initial seeds from your existing unit and end-to-end (E2E) tests.</p>
<h4 id="what-it-does"><a class="header" href="#what-it-does">What it Does</a></h4>
<p><code>cargo run -- generate-seed</code> executes the unit tests and E2E tests of your ink! smart contract, extracting seeds based
on
executed messages. These seeds are saved in the <code>corpus/</code> directory, which highly helps to reach good coverage, as long
as you have good tests.
<strong>Therefore, we encourage to have good and various unit-tests and E2E tests in your contract.</strong></p>
<h4 id="how-it-works"><a class="header" href="#how-it-works">How It Works</a></h4>
<ul>
<li>
<p><strong>Unit Tests</strong>: The command runs through all defined unit tests and captures the invoked messages, with Alice as the
origin and a value of 0.</p>
</li>
<li>
<p><strong>End-to-End Tests</strong>: For E2E tests, Phink modifies the <code>Cargo.toml</code> to point to
a <a href="https://github.com/kevin-valerio/ink/commit/5869d341ff13a454c22a6980fd232f4520721b97">custom ink! repository</a>. This
step
ensures necessary modifications are included to print debug messages containing the messageâs 4-byte hash and
scale-encoded parameters to stdout.</p>
</li>
<li>
<p>If a test invokes at least one message, Phink extracts them all as seeds for use during fuzzing.</p>
</li>
</ul>
<h4 id="usage"><a class="header" href="#usage">Usage</a></h4>
<pre><code class="language-sh">cargo run -- generate-seed <CONTRACT> [COMPILED_DIRECTORY]
</code></pre>
<ul>
<li><code><CONTRACT></code>: The root directory path of your ink! smart contract.</li>
<li><code>[COMPILED_DIRECTORY]</code>: Optional path for where the temporary contract will be compiled. Defaults to <code>tmp</code> if
unspecified.</li>
</ul>
<p>This will generate a set of initial inputs, derived from your current tests, to kickstart fuzzing.</p>
<h4 id="why-using-generate-seed"><a class="header" href="#why-using-generate-seed">Why using <code>generate-seed</code>?</a></h4>
<p>Generating seeds from your existing test suite can increase the efficiency of fuzz testing by:</p>
<ul>
<li>Providing a good starting point for fuzzing inputs.</li>
<li>Ensuring that the fuzzing process begins with valid and meaningful test cases.</li>
</ul>
<p>For more information on how seeds work with Phink, refer to
the <a href="SEEDS.html">seeds documentation</a>.</p>
<h3 id="3-run-the-fuzzer"><a class="header" href="#3-run-the-fuzzer">3. Run the fuzzer</a></h3>
<p>After <strong>instrumenting</strong> your contract and <strong>writing</strong> properties and <strong>configuring</strong> your <code>phink.toml</code>, letâs get our
hands on the fuzzing process:</p>
<pre><code class="language-sh">cargo run -- fuzz
</code></pre>
<p>After executing this command, your fuzzing tests will begin based on the configurations specified in your <code>phink.toml</code>
file. You should see a user interface appear.</p>
<p>If youâre utilizing the advanced UI, youâll receive <em>real-time</em> updates on the fuzzed messages at the bottom of the
screen. For more detailed log information, you can use the following command:</p>
<pre><code class="language-sh">watch -c -t -n 0.5 "clear && cat output/phink/logs/last_seed.phink" # `output` is the default, but it depends of your `phink.toml`
</code></pre>
<p>This will provide you with clearer logs by continuously updating them every <strong>0.1</strong> seconds.</p>
<h2 id="analyzing-results"><a class="header" href="#analyzing-results">Analyzing results</a></h2>
<h3 id="crashes"><a class="header" href="#crashes">Crashes</a></h3>
<p>In case of crashes, you should see something like the following.</p>
<img src="https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/assets/crashed.png" alt="crash"/>
<p>To analyze the crash, you can run <code>cargo run -- execute <your_crash></code>, for instance
<code>cargo run -- execute output/phink/crashes/1729082451630/id:000000,sig:06,src:000008,time:627512,execs:3066742,op:havoc,rep:2</code></p>
<div class="table-wrapper"><table><thead><tr><th>Component</th><th>Description</th></tr></thead><tbody>
<tr><td>1729082451630</td><td>Timestamp representing when the crash was recorded</td></tr>
<tr><td>id:000000</td><td>Unique identifier for the crash</td></tr>
<tr><td>sig:06</td><td>Signal number that triggered the crash</td></tr>
<tr><td>src:000008</td><td>Source test case number</td></tr>
<tr><td>time:627512</td><td>Execution time since the start of the testing process</td></tr>
<tr><td>execs:3066742</td><td>Cumulative number of executions performed until the crash</td></tr>
<tr><td>op:havoc,rep:2</td><td>Type of fuzzing operation (havoc) and its repetition number</td></tr>
</tbody></table>
</div>
<p>By running the above command, you should get an output similar to the screenshot below:</p>
<img src="https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/assets/backtrace.png" alt="crash"/>
<h3 id="coverage"><a class="header" href="#coverage">Coverage</a></h3>
<p><strong>This feature is in alpha and unstable.</strong></p>
<h4 id="generating-a-coverage-report"><a class="header" href="#generating-a-coverage-report">Generating a coverage report</a></h4>
<p>First, you need to create a <code>traces.cov</code> file. For this, execute the command below.</p>
<pre><code class="language-sh">cargo run -- run
</code></pre>
<p>Once done, generate coverage reports to analyze which parts of the contract were tested:</p>
<pre><code class="language-sh">cargo run -- coverage my_contract/
</code></pre>
<p>Some HTML files should then be generated in the path youâve configured inside your <code>phink.toml</code>. The coverage report
provides a visual representation of the tested code areas. As a rule of thumb, the more green lines you can see there,
the better it is for the code.</p>
<h3 id="coverage-report-example"><a class="header" href="#coverage-report-example">Coverage report example</a></h3>
<p><strong>Green Lines</strong>: Code that has been tested.</p>
<p><img src="https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/assets/coverage_1.png" alt="Coverage Report Part 1" /></p>
<p><em>Figure 1: Coverage Report of one specific file.</em></p>
<img src="https://raw.githubusercontent.com/srlabs/phink/refs/heads/main/assets/coverage_2.png" alt="coverage_2" width="400"/>
<p><em>Figure 2: List of fuzzed Rust files from the ink! smart-contract.</em></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="invariants"><a class="header" href="#invariants">Invariants</a></h1>
<p>Invariants are <strong>fundamental properties that must always hold</strong> true in a smart-contract, regardless of any operations
performed. They help ensure that certain logical conditions remain constant throughout the
execution of the contract, preventing potential vulnerabilities and ensuring its reliability.</p>
<p>We suggest to use <strong>integrity</strong> and <strong>unit tests</strong> from your codebase to get inspiration to generate good invariants.</p>
<h2 id="creating-good-invariants-for-ink-smart-contracts"><a class="header" href="#creating-good-invariants-for-ink-smart-contracts">Creating good invariants for ink! smart-contracts</a></h2>
<p>Below are some guidelines to help you design robust invariants:</p>
<ol>
<li>
<p><strong>Understand the Contractâs Logic</strong>: Before crafting invariants, deeply understand the core logic and expected
behaviors of your smart contract.</p>
</li>
<li>
<p><strong>Identify Critical Properties</strong>: Determine critical properties or conditions that must hold <strong>true</strong>. This could
involve
state variables, transaction outcomes, or other interdependent conditions.</p>
</li>
<li>
<p><strong>Consider Corner Cases</strong>: Think about edge cases and potential attack vectors. Invariants should be designed to
capture unexpected or extreme scenarios.</p>
</li>
<li>
<p><strong>Focus on Consistency</strong>: Consider properties that ensure data consistency across state changes. This might involve
ensuring balances are correctly updated or ownership is properly maintained.</p>
</li>
<li>
<p><strong>Keep it Simple</strong>: While considering complex scenarios, ensure your invariants are straightforward to encourage
maintainability and clarity.</p>
</li>
</ol>
<h2 id="example-invariant-in-ink-smart-contracts"><a class="header" href="#example-invariant-in-ink-smart-contracts">Example invariant in ink! smart-contracts</a></h2>
<p>Here is a template to get you started on writing invariants for ink! smart contracts:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>#[cfg(feature = "phink")]
#[ink(impl)]
impl DomainNameService {
/// Example invariant:
#[ink(message)]
#[cfg(feature = "phink")]
pub fn phink_balance_invariant(&self) {
// Ensure total supply equals sum of individual balances
assert_eq!(self.total_supply, self.calculate_total_balances(), "Balance invariant violated!");
}
}
<span class="boring">}</span></code></pre></pre>
<h3 id="annotations-explaination"><a class="header" href="#annotations-explaination">Annotations explaination</a></h3>
<ul>
<li><strong><code>#[cfg(feature = "phink")]</code></strong>: Ensures the function is only compiled when the âphinkâ feature is enabled.</li>
<li><strong><code>#[ink(message)]</code></strong>: Marks the function as an executable entry defined by the ink! framework.</li>
<li><strong>Function Naming</strong>: Begin with âphink_â to indicate the purpose and correlation to fuzz testing.</li>
</ul>
<h2 id="creating-invariants-with-llm"><a class="header" href="#creating-invariants-with-llm">Creating invariants with LLM</a></h2>
<p>Large Language Models (LLMs) offer a good (<em>lazy, yesâŚ</em>) approach to generate invariants by interpreting the logic and
identifying properties from the contract code. Here is an example prompt system you could use to generate a base of
invariants</p>
<h5 id="system-prompt"><a class="header" href="#system-prompt">System prompt</a></h5>
<pre><code class="language-markdown">You are provided with Rust files containing an ink! smart contract. Your task is to generate invariants, which are
inviolable properties that a fuzzer will check to ensure the contract's quality and correctness. Please adhere to the
following requirements while writing the invariants:
1. Ensure that the `impl` block is annotated with `#[cfg(feature = "phink")] #[ink(impl)]`.
2. Confirm that the `impl DomainNameService` is the main implementation block of the contract.
3. Each invariant must be annotated with:
- `#[ink(message)]`
- `#[cfg(feature = "phink")]`
- Function names must start with "phink_".
4. Each invariant function must contain at least one assertion statement, such as `assert`, `assert_ne`, `panic`, etc.
5. Be creative and consider corner cases to ensure the thoroughness of the invariants.
Output example:
```rust
#[cfg(feature = "phink")]
#[ink(impl)]
impl DomainNameService {
// This invariant ensures that `domains` doesn't contain the forbidden domain that nobody should register
#[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);
}
}
}
}
`` `
</code></pre>
<h5 id="sources-in-the-prompt"><a class="header" href="#sources-in-the-prompt">Sources in the prompt</a></h5>
<p>If your contract is small enough and contains multiple Rust files, you could use the following snippet, to put
everything inside <code>everything.rs</code>.</p>
<pre><code class="language-sh">find . -name "*.rs" -not -path "./target/*" -exec cat {} + > everything.rs
</code></pre>
<p>Copy paste the content after your <em>system prompt</em>, and examine the LLM invariants. Otherwise, simply copy paste the code
from your <code>lib.rs</code></p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="runtime-integration"><a class="header" href="#runtime-integration">Runtime integration</a></h1>
<p>Phink provides developers with the flexibility to customize their fuzzing environment through a simple interface. By
editing <code>contract/custom/custom.rs</code> and <code>contract/custom/preferences.rs</code>, developers can tailor the runtime storage and
contract initialization
processes to suit their testing needs. Have a clone of Phink if you want to modify the source code.</p>
<h2 id="custom-runtime-storage"><a class="header" href="#custom-runtime-storage">Custom runtime storage</a></h2>
<p>Phink allows developers to tailor the runtime environment by customizing the storage configuration. Letâs
create some realistic testing scenarios!</p>
<h3 id="example"><a class="header" href="#example">Example</a></h3>
<pre><code class="language-rust ignore">impl DevelopperPreferences for Preferences {
fn runtime_storage() -> Storage {
let storage = RuntimeGenesisConfig {
balances: BalancesConfig {
balances: (0..u8::MAX) // Allocates substantial balance to accounts
.map(|i| [i; 32].into())
.collect::<Vec<_>>()
.iter()
.cloned()
.map(|k| (k, 10000000000000000000 * 2))
.collect(),
},
..Default::default()
}
.build_storage()
.unwrap();
storage
}
}</code></pre>
<h3 id="customization-points"><a class="header" href="#customization-points">Customization points</a></h3>
<ul>
<li><strong><code>runtime_storage</code>:</strong> This function is your gateway to defining any mocks or <code>RuntimeGenesisConfig</code> settings needed
for your testing environment. Whether itâs allocating funds, initializing storage items, or setting up custom
storage, you can adjust these configurations to mirror your deployment scenarios closely. This flexibility allows
you to test how your ink! smart contract behave in various simulated network states.</li>
</ul>
<h2 id="contract-initialization"><a class="header" href="#contract-initialization">Contract initialization</a></h2>
<p>The <code>on_contract_initialize</code> function can be adapted to execute additional initialization logic, such as uploading
supplementary contracts or handling dependencies.</p>
<h3 id="usage-example"><a class="header" href="#usage-example">Usage example</a></h3>
<pre><code class="language-rust ignore">fn on_contract_initialize() -> anyhow::Result<()> {
Contracts::bare_upload_code(
AccountId32::new([1; 32]),
fs::read("adder.wasm")?,
None,
Determinism::Enforced,
);
Ok(())
}</code></pre>
<h3 id="customization-points-1"><a class="header" href="#customization-points-1">Customization points</a></h3>
<ul>
<li><strong><code>runtime_storage</code>:</strong> Use this function as your gateway to defining any mocks or <code>RuntimeGenesisConfig</code> settings
needed
for your testing environment. Whether itâs allocating funds, initializing storage items, or setting up custom
storage, adjust these configurations to mirror your deployment scenarios closely. This flexibility lets
you test how your ink! smart contract behaves in various simulated network states.</li>
</ul>
<h2 id="contract-initialization-1"><a class="header" href="#contract-initialization-1">Contract initialization</a></h2>
<p>The <code>on_contract_initialize</code> function can be adapted to execute additional initialization logic, such as uploading
supplementary contracts or handling dependencies.</p>
<h3 id="example-1"><a class="header" href="#example-1">Example</a></h3>
<pre><code class="language-rust ignore">fn on_contract_initialize() -> anyhow::Result<()> {
Contracts::bare_upload_code(
AccountId32::new([1; 32]),
fs::read("adder.wasm")?,
None,
Determinism::Enforced,
);
Ok(())
}</code></pre>
<h3 id="customization-points-2"><a class="header" href="#customization-points-2">Customization points</a></h3>
<ul>
<li><strong><code>on_contract_initialize</code>:</strong> Use this function to automate contract uploads, configure dependencies, or perform any
setup necessary before testing.</li>
</ul>
<h2 id="custom-runtime-parameters"><a class="header" href="#custom-runtime-parameters">Custom runtime parameters</a></h2>
<p>Phink provides default runtime configurations, but developers can provide their own runtime parameters in
<code>contract/runtime.rs</code>. This can be particularly useful if you wish to connect your fuzzing environment to your own
Substrate runtime, so Phink can be adapted to work with your specific runtime.
<strong>You can edit the runtime configure <a href="https://github.com/srlabs/phink/blob/main/src/contract/runtime.rs">here</a>.</strong></p>
<h3 id="example-custom-runtime-configuration"><a class="header" href="#example-custom-runtime-configuration">Example: custom runtime configuration</a></h3>
<p>For instance, customize the <code>pallet_timestamp</code> runtime parameters like this:</p>
<pre><code class="language-rust ignore">impl pallet_timestamp::Config for Runtime {
type MinimumPeriod = CustomMinimumPeriod;
...
}</code></pre>
<div style="break-before: page; page-break-before: always;"></div><h1 id="seed-format"><a class="header" href="#seed-format">Seed format</a></h1>
<p>In Phink, a seed is structured to guide the fuzzing process effectively. The seed is composed of these 4 parts:</p>
<ul>
<li><strong>4 bytes</strong>: Represents the balance value to be transferred to the message if itâs payable</li>
<li><strong>1 byte</strong>: Specifies the origin; applicable if fuzzing origin is enabled in the configuration</li>
<li><strong>4 bytes</strong>: Identifies the message selector</li>
<li><strong>Remaining bytes</strong>: Contains the SCALE-encoded parameters for the message</li>
</ul>
<p>If your configuration allows more than one message per input, Phink uses the delimiter <code>"********"</code> to separate multiple
messages within a single input. This enables comprehensive testing across multiple scenarios from a single seed.</p>
<h2 id="example-2"><a class="header" href="#example-2">Example</a></h2>
<p>Hereâs a breakdown for the seed
<code>0000000001fa80c2f6002a2a2a2a2a2a2a2a0000000103ba70c3aa18040008000f00100017002a00</code>:</p>
<div class="table-wrapper"><table><thead><tr><th>Segment</th><th>Bytes</th><th>Description</th></tr></thead><tbody>
<tr><td>Balance transfer</td><td><code>00000000</code></td><td>4 bytes for balance (no transfer in this case)</td></tr>
<tr><td>Origin</td><td><code>01</code></td><td>1 byte indicating the origin (Alice) (enabled in config)</td></tr>
<tr><td>Message selector 1</td><td><code>fa80c2f6</code></td><td>4 bytes for the first message selector</td></tr>
<tr><td>Parameters 1</td><td><code>00</code></td><td>SCALE-encoded parameters for the first message</td></tr>
<tr><td>Message delimiter</td><td><code>2a2a2a2a2a2a2a2a</code></td><td>Delimits the first and second messages (<code>********</code>)</td></tr>
<tr><td>Balance transfer</td><td><code>00000001</code></td><td>4 bytes for balance (1 unit transfered)</td></tr>
<tr><td>Origin</td><td><code>03</code></td><td>1 byte indicating the origin (Charlie) for the second message</td></tr>
<tr><td>Message selector 2</td><td><code>ba70c3aa</code></td><td>4 bytes for the second message selector</td></tr>
<tr><td>Parameters 2</td><td><code>18040008000f00100017002a00</code></td><td>SCALE-encoded vector: [4, 8, 15, 16, 23, 42]</td></tr>
</tbody></table>
</div>
<h3 id="explanation"><a class="header" href="#explanation">Explanation</a></h3>
<ul>
<li><strong>Balance transfer</strong>: The 4 bytes representing the balance transfer amount (set to <code>00000000</code> for the first message),
indicating no value is being transferred for either message.</li>
<li><strong>Origin</strong>: A single byte is used (<code>01</code> for the first message and <code>03</code> for the second) to specify the origin of the
call. This is useful for testing scenarios with different origins.</li>
<li><strong>Message selector</strong>: The first message, for example, begins with a 4-byte identifier (<code>fa80c2f6</code>), indicating which
message within the contract is being invoked.</li>
<li><strong>Parameters</strong>: Following the message selector, SCALE-encoded parameters are specified (example: <code>00</code>), representing
the input data for each message.</li>
<li><strong>Message delimiter</strong>: This seed uses the delimiter <code>********</code> (represented as <code>2a2a2a2a2a2a2a2a</code>) to separate
multiple messages within a single input, allowing more complex interactions to be tested.</li>
</ul>
<h1 id="running-one-seed"><a class="header" href="#running-one-seed">Running one seed</a></h1>
<p>To execute a single seed, use the following command:</p>
<pre><code class="language-bash">cargo run -- execute my_seed.bin
</code></pre>
<p>This command runs the specific seed <code>my_seed.bin</code>, providing targeted fuzzing for individual transaction testing.</p>
<h1 id="running-all-the-seeds"><a class="header" href="#running-all-the-seeds">Running all the seeds</a></h1>
<p>To run all seeds sequentially, use the following command:</p>
<pre><code class="language-bash">cargo run -- run
</code></pre>
<p>This command iterates over the <code>corpus</code> folder, executing each seed. This ensures a comprehensive fuzzing process that
covers
all previously discovered cases.</p>
<h1 id="minimizing-the-corpus"><a class="header" href="#minimizing-the-corpus">Minimizing the corpus</a></h1>
<p>To minimize the corpus folder containing seeds, use the following command:</p>
<pre><code class="language-bash">cargo run -- minimize
</code></pre>
<p>The goal of the corpus minimization process is to streamline the set of seeds in the corpus folder, reducing it to the
most essential and impactful test cases. Minimization makes fuzzing more efficient by eliminating
redundant seeds, speeding up the speed and focusing only on seeds that reveal new or unique coverage.</p>
<h3 id="what-it-does-1"><a class="header" href="#what-it-does-1">What it does</a></h3>
<p><code>cargo run -- minimize</code> analyzes the seeds within the corpus and identifies those that are redundant
or do not contribute additional value to the fuzzing campaign. It executes each seed to determine their individual
impact
and removes any seeds that do not enhance coverage or expose new bugs. This results in a minimized set of seeds, savind
time time and
also optimizing resource usage.</p>
<h1 id="generating-a-seed"><a class="header" href="#generating-a-seed">Generating a seed</a></h1>
<p>To generate a new seed, all you need to do is construct it using the prescribed format. Start with the required byte
sequences for
balance, origin, message selector, and parameters, and then save it in your designated seed directory.</p>
<h2 id="importance-of-seed-generation"><a class="header" href="#importance-of-seed-generation">Importance of seed generation</a></h2>
<p>How can we detect and fix more potential
vulnerabilities and edge cases faster? The ability to manually create seeds is crucial for enhancing the effectiveness
of the fuzz testing process. By creating
custom seeds, developers can guide the fuzzer to explore paths and scenarios that might not be easily discovered through
automated means. This, in turn, increases the overall coverage of the fuzzing campaign. If you need to generate the
SCALE-encoded parameters, itâs best to
utilize tools like <code>cargo contract</code>
or <a href="https://polkadot.js.org/apps/">Polkadot.js</a>.</p>
<h1 id="adding-a-seed-to-the-corpus"><a class="header" href="#adding-a-seed-to-the-corpus">Adding a seed to the corpus</a></h1>
<p>To add a custom seed to the corpus, use the following command:</p>
<pre><code class="language-bash">cargo ziggy add-seeds -i my_custom_seeds/ -z output/
</code></pre>
<ul>
<li><code>my_custom_seeds/</code>: Directory containing your custom seeds</li>
<li><code>output/</code>: Directory where the fuzzing output is stored</li>
</ul>
<p>Once added, the corpus will use these seeds in subsequent fuzzing processes.</p>
<h1 id="viewing-and-editing-seeds"><a class="header" href="#viewing-and-editing-seeds">Viewing and editing seeds</a></h1>
<p>To view the hexadecimal content of a seed, issue the following command:</p>
<pre><code class="language-bash">xxd -c 3000 -p output/phink/corpus/one_seed.bin > abc.out
</code></pre>
<p>This useful command converts the binary seed file into hex for easier reading and editing.</p>
<p>To edit a seed, complete these 3 easy tasks:</p>
<ol>
<li>
<p>Open the hex file in your preferred editor, and edit it</p>
<pre><code class="language-bash">vim abc.out
</code></pre>
</li>
<li>
<p>Save the changes and revert the hex file to binary</p>
<pre><code class="language-bash">rm seed.bin # Used to bypass cached seed
xxd -r -p abc.out seed.bin
</code></pre>
</li>
<li>
<p>Execute the updated seed</p>
<pre><code class="language-bash">cargo run -- execute seed.bin
</code></pre>
</li>
</ol>
<p>Congratulations! Weâre off to the races again.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="concepts-and-terminology"><a class="header" href="#concepts-and-terminology">Concepts and terminology</a></h1>
<h2 id="concepts"><a class="header" href="#concepts">Concepts</a></h2>
<h3 id="fuzzing"><a class="header" href="#fuzzing">Fuzzing</a></h3>
<p><strong>Fuzzing</strong> is an automated software testing technique that involves providing random data inputs to
a program. The primary goal is to uncover anomalies, such as crashes and assertion failures. These are intriguing
because they pinpoint
potential vulnerabilities.</p>
<h3 id="property-based-fuzzing"><a class="header" href="#property-based-fuzzing">Property-based fuzzing</a></h3>
<p><strong>Property-based testing</strong> involves specifying properties or invariants that your ink! contract should always satisfy.
In
Phink, these properties act as assertions. Phink makes it possible for developers to
define properties directly within ink! smart contracts. Such properties are then tested against varied
inputs. In this way, the contract maintains its invariants across all possible data conditions. But there is a final
twist in the fuzzing tale.</p>
<h3 id="coverage-guided-fuzzing-1"><a class="header" href="#coverage-guided-fuzzing-1">Coverage-guided fuzzing</a></h3>
<p><strong>Coverage-guided fuzzing</strong> is a fuzzing strategy that focuses on maximizing code coverage during testing. It uses
feedback from code execution paths to guide input generation, focusing on unexplored parts of the code.
Phink instruments ink! smart contracts to track code coverage. Optimizing fuzzing efforts by targeting less examined
paths is what makes the game worth playing.</p>
<h2 id="terminology"><a class="header" href="#terminology">Terminology</a></h2>
<h3 id="corpus"><a class="header" href="#corpus">Corpus</a></h3>
<p>A <strong>corpus</strong> refers to the collection of all input samples used during the testing process. It is
continuously updated with new inputs that lead to unique execution paths.</p>
<hr />
<h3 id="seed"><a class="header" href="#seed">Seed</a></h3>
<p>A <strong>seed</strong> is an initial input provided to the fuzzer to start the testing process. Seeds serve as the starting point
for generating new test cases and are crucial for initializing a diverse and effective fuzzing campaign. A strong set of
seed inputs can significantly enhance the fuzzing campaign.</p>
<hr />
<h3 id="invariants-1"><a class="header" href="#invariants-1">Invariants</a></h3>
<p><strong>Invariants</strong> are conditions or properties that must remain true at all times during the execution of a program or
contract. In property-based testing, invariants are used as assertions to verify the consistent behavior of smart
contracts under various input conditions. Breaking an invariant indicates a potential bug or vulnerability.</p>
<hr />
<h3 id="instrumentation"><a class="header" href="#instrumentation">Instrumentation</a></h3>
<p><strong>Instrumentation</strong> involves modifying a program to collect runtime information such as code coverage data. In fuzzing,
instrumentation traces execution paths, enabling coverage-guided techniques to generate more informed and
effective test cases.</p>
<hr />
<h3 id="coverage-1"><a class="header" href="#coverage-1">Coverage</a></h3>
<p><strong>Coverage</strong> measures how much of a programâs code is tested during fuzzing. High coverage corresponds to a
good assessment of the contractâs logic.</p>
<hr />
<h3 id="contract-selectors"><a class="header" href="#contract-selectors">Contract selectors</a></h3>
<p><strong>ink! contract selectors</strong> are unique identifiers for functions within ink! smart contracts. Selectors are derived from
function signatures and are used to call specific functions within a contract deployed on the blockchain.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="how-phink-works"><a class="header" href="#how-phink-works">How Phink works</a></h1>
<p>Phink is built on top of AFL++, leveraging its capabilities to provide effective fuzz testing for ink! smart contracts.
Hereâs an overview of how the fuzzer operates.</p>
<h2 id="afl-integration"><a class="header" href="#afl-integration">AFL++ integration</a></h2>
<p>Phink utilizes AFL++ through two key components:</p>
<ul>
<li><strong>ziggy</strong>: A multifuzzing crate that enables integration with multiple fuzzers.</li>
<li><strong>afl.rs</strong>: A crate that spawns AFL++ fuzzers, facilitating seamless mutation and coverage tracking.</li>
</ul>
<h3 id="afl-mechanics"><a class="header" href="#afl-mechanics">AFL++ mechanics</a></h3>
<p>AFL++ mutates the input bytes and evaluates whether these mutations increase code coverage. If a mutation results in new
execution paths, the modified seed is retained in the corpus. This iterative process enhances the likelihood of
discovering hidden vulnerabilities.</p>
<h3 id="monitoring-execution"><a class="header" href="#monitoring-execution">Monitoring execution</a></h3>
<p>Users can monitor the execution logs using familiar AFL++ tools. For instance, by using <code>tail</code>, you can view real-time
fuzzer logs and activity:</p>
<pre><code class="language-bash">tail -f output/phink/logs/afl.log
tail -f output/phink/logs/afl_1.log #if multi-threaded
</code></pre>
<p>Additionally, tools like <code>afl_showmap</code> allow developers to debug and visualize the coverage maps.</p>
<h2 id="coverage-guided-strategy"><a class="header" href="#coverage-guided-strategy">Coverage-guided strategy</a></h2>
<p>Currently, Phink employs a partially coverage-guided approach. While full coverage feedback from low-level
instrumentation is not available yet, plans are underway to integrate this capability
via <a href="https://github.com/wasmi-labs/wasmi">WASMI</a> or <a href="https://github.com/koute/polkavm">PolkaVM</a> in future
releases.</p>
<h2 id="execution-and-validation"><a class="header" href="#execution-and-validation">Execution and validation</a></h2>
<p>For each generated seed, Phink executes the associated input on a mock-emulated ânodeâ.
This setup ensures that invariants are verified: known selectors are checked to ensure that
invariants hold across different message calls.</p>
<h2 id="contract-instrumentation"><a class="header" href="#contract-instrumentation">Contract instrumentation</a></h2>
<p>Phink instruments contracts using the <code>syn</code> crate, allowing for precise modification and analysis of the smart contract
code. For each high-level Rust instructions, a feedback is returned via the <code>debug_message</code> map to the fuzzing engine, mapping each instruction to a unique <code>u64</code> identifier. This map is then âexpandedâ, instrumented by AFL++ compiler, and ultimately updated the AFL++ shared map everytime a new edge is hit.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="troubleshooting"><a class="header" href="#troubleshooting">Troubleshooting</a></h1>
<h2 id="debugging-phink"><a class="header" href="#debugging-phink">Debugging Phink</a></h2>
<h3 id="afl-logs"><a class="header" href="#afl-logs">AFL++ logs</a></h3>
<p>If you encounter unexpected behavior, examining the AFL++ logs can provide good insights. In most cases, developers
will find more information by executing:</p>
<pre><code class="language-sh">tail -f your_output/phink/logs/afl.log
</code></pre>
<p>Replace <code>your_output</code> with the directory defined in your <code>phink.toml</code> under <code>fuzz_output</code>. This will give you a
real-time view of the log output, helping you identify any issues during the fuzzing process.</p>
<h3 id="executing-a-single-seed"><a class="header" href="#executing-a-single-seed">Executing a Single Seed</a></h3>
<p>To debug specific cases where a contract crashes, you can execute a single seed. This method allows you to instantiate a
contract and identify crash points more easily:</p>
<pre><code class="language-sh">cargo run -- execute output/phink/corpus/selector_1.bin
</code></pre>
<p>This command runs a single fuzzing input, making it easier to pinpoint problems.</p>
<h3 id="harness-coverage"><a class="header" href="#harness-coverage">Harness coverage</a></h3>
<p>Use the harness coverage feature for debugging. You should only use it if you want to have a coverage of Phink itself.
For instance, if youâre planning to contribute to Phink, or to debug it.</p>
<pre><code class="language-sh">cargo run -- harness-cover
</code></pre>
<p>Be aware that this is primarily for those who want to dive deeper into the coverage of Phink and is not generally
necessary for regular debugging.</p>
<h3 id="support-channels"><a class="header" href="#support-channels">Support channels</a></h3>
<p>You can find us on <a href="https://discord.gg/4MakDGwFEK">Discord</a>. Alternatively, you can message me
on <a href="kevin%5B%F0%9F%8E%A9%5Dsrlabs.de">kevin[đŠ]srlabs.de</a>.</p>
<p>Happy fuzzing!</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="benchmarking"><a class="header" href="#benchmarking">Benchmarking</a></h1>
<p>Benchmarking provides insights into Phinkâs performance in real-world scenarios, in order to vizualise its efficiency
and
fuzzing ink! smart contracts. Below are the benchmark results for various smart contracts, detailing
coverage, speed, corpus size, and the potential usage of <code>generate-seed</code>.
Each contract were fuzzed for maximum a day.
Statistics (especially <em>average speed</em>) are given for <strong>one</strong> core only. The coverage percent is calculated using the
number of
lines covered divided the number of reachable lines, as a percentage.</p>
<blockquote>
<p>â ď¸ The point of the benchmark is to demonstrate how much coverage is reachable within a day of fuzzing without doing
proper seed creation. In a real fuzzing campaign, the developers would aim for 100% coverage, by creating seeds,
adding <code>GenesisConfig</code> values, more (E2E) tests extracted with <code>seed-generator</code>, etc.</p>
</blockquote>
<h3 id="benchmarks"><a class="header" href="#benchmarks">Benchmarks</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Contract name</th><th>Coverage percent</th><th>Average speed (<em>execs/sec</em>)</th><th>AFL++ corpus size</th><th>Using Phink seed generation</th></tr></thead><tbody>
<tr><td>abax_governor</td><td><strong>48%</strong></td><td>1500 (early phase) <strong>and</strong> 100 (late phase)</td><td>1639</td><td><strong>NO</strong> (no tests available)</td></tr>
<tr><td>erc1155</td><td><strong>89%</strong></td><td>1300 (early phase phase) <strong>and</strong> 140 (late phase)</td><td>949</td><td><strong>YES</strong> (without E2E)</td></tr>
<tr><td>multisig</td><td><strong>91%</strong></td><td>1400 (early phase phase) <strong>and</strong> 113 (late phase)</td><td>1524</td><td><strong>YES</strong> (without E2E)</td></tr>
</tbody></table>
</div>
<ul>
<li>Github for
<code>abax_governor</code> : <a href="https://github.com/AbaxFinance/dao-contracts/tree/main/src/contracts/abax_governor/">AbaxFinance/dao-contracts/tree/main/src/contracts/abax_governor</a></li>
<li>Github for
<code>multisig</code> : <a href="https://github.com/use-ink/ink-examples/blob/main/multisig/lib.rs">use-ink/ink-examples/blob/main/multisig/lib.rs</a></li>
<li>Github for
<code>erc1155</code> : <a href="https://github.com/use-ink/ink-examples/blob/main/erc1155/lib.rs">use-ink/ink-examples/blob/main/erc1155/lib.rs</a></li>
</ul>
<h5 id="dummy-benchmark"><a class="header" href="#dummy-benchmark">Dummy benchmark</a></h5>
<p>The <a href="https://github.com/srlabs/phink/blob/main/sample/dummy/lib.rs">dummy</a> benchmark involves a simple nested
if-condition. It acts as a reference to ensure that the fuzzer is
effectively coverage guided. The results for this benchmark are as follows:</p>
<ul>
<li><strong>Average speed</strong>: 7,500 executions per second in average</li>
<li><strong>Number of cores used</strong>: 10</li>
<li><strong>Time until invariant triggered</strong>: 48 seconds</li>
<li><strong>Stability</strong>: 99.43%</li>
<li><strong>Fuzzing origin</strong>: false</li>
<li><strong>Final corpus size</strong>: 12 seeds</li>
</ul>
<h6 id="dummy-logic"><a class="header" href="#dummy-logic">Dummy logic</a></h6>
<p>The logic tested in the dummy benchmark can simply be represented that way:</p>
<pre><code class="language-rust ignore">if data.len() > 3 && data.len() < 7 {
if data.chars().nth(0).unwrap() == 'f' {
if data.chars().nth(1).unwrap() == 'u' {
if data.chars().nth(2).unwrap() == 'z' {
if data.chars().nth(3).unwrap() == 'z' {
self.forbidden_number = 42;
}
}
}
}
}</code></pre>
<h4 id="contracts"><a class="header" href="#contracts">Contracts</a></h4>
<h6 id="erc-1155"><a class="header" href="#erc-1155">ERC-1155</a></h6>
<blockquote>
<p>The ERC-1155 contract is a standard for creating multiple token types within a single contract. It allows for the
creation of both fungible and non-fungible tokens and enables batch transfers, making it easy to transfer multiple
tokens at once.</p>
</blockquote>
<h6 id="multisig-wallet"><a class="header" href="#multisig-wallet">Multisig Wallet</a></h6>
<blockquote>
<p>The Multisig Wallet contract is a multi-owner wallet that requires a certain number of owners to agree on a
transaction before it can be executed. Each owner can submit a transaction, and when enough owners confirm, it can be
executed.</p>
</blockquote>
<h6 id="abaxgovernor"><a class="header" href="#abaxgovernor">AbaxGovernor</a></h6>
<blockquote>
<p>The Abax Governor contract is a governance contract that allows for staking of PSP22 tokens in exchange for
non-transferrable PSP22Vault shares (votes). It enables users to propose and vote on proposals, with the number of
shares held by a user determining their voting power.</p>
</blockquote>
<h3 id="explanation-of-terms"><a class="header" href="#explanation-of-terms">Explanation of terms</a></h3>
<ul>
<li>
<p><strong>Coverage</strong>: Represents the percentage of the code that have been executed during the fuzzing campaign. Higher
coverage
indicates more thorough testing (<em>the higher the better</em>).</p>
</li>
<li>
<p><strong>Average speed (for 1 core)</strong>: The number of executions per second that the fuzzer can handle on a single CPU core.
As a reminder, one execution contains multiple calls up to
<code>max_messages_per_exec</code>.</p>
</li>
<li>
<p><strong>AFL++ corpus size</strong>: The size of the corpus generated by AFL++ during fuzzing. A larger
corpus implies a diverse set of inputs to test the contract.</p>
</li>
<li>
<p><strong>generate-seed usage</strong>: Indicates whether <code>generate-seed</code> was used to seed the initial tests. This depends if the
contract include tests or not.</p>
</li>
</ul>
<h3 id="environment-details"><a class="header" href="#environment-details">Environment details</a></h3>
<ul>
<li><strong>CPU</strong>: AMD EPYC 7282 16-Cores</li>
<li><strong>Operating System</strong>: Linux 5.4.0-189-generic #209-Ubuntu x86_64</li>
<li><strong>Phink Version</strong>: 0.1.4</li>
</ul>
<h3 id="contributing-to-the-benchmarks"><a class="header" href="#contributing-to-the-benchmarks">Contributing to the benchmarks</a></h3>
<p>We encourage contributions to our benchmarks! If you have a contract you would like to see benchmarked, please submit a
pull request to our repository.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="faq"><a class="header" href="#faq">FAQ</a></h1>
<h4 id="why-the-name-phink-"><a class="header" href="#why-the-name-phink-">Why the name â<em>Phink</em>â ?</a></h4>
<p>Mystère et boule de gomme.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js"></script>
<script src="mark.min.js"></script>
<script src="searcher.js"></script>
<script src="clipboard.min.js"></script>
<script src="highlight.js"></script>
<script src="book.js"></script>
<!-- Custom JS scripts -->
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</div>
</body>
</html>