Ityfuzz

Let's start with a simple example to understand how the fuzzer works.

Check section 3 Figure 2 for the pseudo-code of the contract.

Here, we reproduce the contract in ink!:

#![allow(unused)]
#![cfg_attr(not(feature = "std"), no_std, no_main)]

fn main() {
#[ink::contract]
mod ityfuzz {

    const BUG_VALUE: u128 = 15;

    #[ink(storage)]
    pub struct Ityfuzz {
        counter: u128,
        bug_flag: bool,
    }

    impl Ityfuzz {
        #[ink(constructor)]
        pub fn default() -> Self {
            Self {
                counter: 0,
                bug_flag: true,
            }
        }

        #[ink(message)]
        pub fn incr(&mut self, value: u128) -> Result<(), ()> {
            if value > self.counter {
                return Err(());
            }
            self.counter = self.counter.checked_add(1).ok_or(())?;
            Ok(())
        }

        #[ink(message)]
        pub fn decr(&mut self, value: u128) -> Result<(), ()> {
            if value < self.counter {
                return Err(());
            }
            self.counter = self.counter.checked_sub(1).ok_or(())?;
            Ok(())
        }

        #[ink(message)]
        pub fn buggy(&mut self) {
            if self.counter == BUG_VALUE {
                self.bug_flag = false;
            }
        }

        #[ink(message)]
        pub fn get_counter(&self) -> u128 {
            self.counter
        }
    }
}
}

In this contract, the incr and decr functions increment and decrement the counter variable based on the condition compared to the provided value, respectively. The buggy function sets the bug_flag variable to false if the counter variable is equal to BUG_VALUE.

Fuzzing the contract

To test the contract we will write a property as an ink! message that checks the value of the bug_flag variable. If the message returns 'false', it means that property was violated, and the fuzzer will print the execution trace and the violated property.

Note that this message is wrapped in a #[cfg(feature = "fuzz-testing")] attribute to avoid compiling it in the final contract. In order for this to work, the fuzz-testing feature must be enabled in the Cargo.toml file.

[features]
...
fuzz-testing = []

And this is how the property looks like in the ink! contract:

#![allow(unused)]
#![cfg_attr(not(feature = "std"), no_std, no_main)]

fn main() {
#[ink::contract]
mod ityfuzz {
    ...

    #[cfg(feature = "fuzz-testing")]
    #[ink(impl)]
    impl Ityfuzz {
        #[cfg(feature = "fuzz-testing")]
        #[ink(message)]
        pub fn inkscope_bug(&self) -> bool {
            self.bug_flag
        }
    }
}
}

Once the property is written, we can compile the contract:

    cd test-contracts/ityfuzz
    cargo contract build --features fuzz-testing

And then, execute the fuzzer against it and check the output

    ./target/release/inkscope-fuzzer ./test-contracts/ityfuzz/target/ink/ityfuzz.contract fuzz

If the fuzzer finds a property violation, it will print the execution trace and the violated property as shown below:

Property check failed ❌
  Deploy: default()
  Message0:   Deploy: incr(UInt(0))
  Message1:   Deploy: buggy()
  Message2:   Deploy: incr(UInt(1))
  Message3:   Deploy: buggy()
  Message4:   Deploy: incr(UInt(1))
  Message5:   Deploy: buggy()
  Message6:   Deploy: buggy()
  Message7:   Deploy: buggy()
  Message8:   Deploy: buggy()
  Message9:   Deploy: incr(UInt(1))
  Message10:   Deploy: buggy()
  Message11:   Deploy: incr(UInt(0))
  Message12:   Deploy: incr(UInt(1))
  Message13:   Deploy: buggy()
  Message14:   Deploy: incr(UInt(1))
  Message15:   Deploy: incr(UInt(2))
  Message16:   Deploy: buggy()
  Message17:   Deploy: buggy()
  Message18:   Deploy: incr(UInt(1))
  Message19:   Deploy: incr(UInt(0))
  Message20:   Deploy: incr(UInt(2))
  Message21:   Deploy: incr(UInt(1))
  Message22:   Deploy: buggy()
  Message23:   Deploy: buggy()
  Message24:   Deploy: buggy()
  Message25:   Deploy: incr(UInt(2))
  Message26:   Deploy: incr(UInt(1))
  Message27:   Deploy: decr(UInt(340282366920938463463374607431768211455))
  Message28:   Deploy: incr(UInt(1))
  Message29:   Deploy: incr(UInt(1))
  Message30:   Deploy: buggy()
  Property: inkscope_bug()