9 - Security II.
Tutorial objectives
- Advanced techniques in security auditing
- Static analysis tools
- Fuzz and property-based testing
Tutorial pre-requisites
- Solidity fundamentals
- Slither (
pip3 install slither-analyzer
) - Woke incl. fuzzer (
pip3 install "woke[fuzzer]"
) - (optional) Docker (https://docs.docker.com/get-docker/)
- Echidna (https://github.com/crytic/echidna)
- Easiest way is to pull docker image
trailofbits/echidna
with fixed compiler version or more universal from Ackee Blockchainabch/toolset
- Easiest way is to pull docker image
Caution:
Computers in the laboratory don’t allow to install programs and save their state between login sessions. Please bring your own hardware for these tutorials.
Slither
Solidity static code analysis framework
Whitepaper: https://www.researchgate.net/publication/333700886_Slither_A_Static_Analysis_Framework_For_Smart_Contracts
Detectors
$ slither .
Useful printers
contract-summary
- Print a summary of the contractsfunction-summary
- Print a summary of the functionshuman-summary
- Print a human-readable summary of the contractsinheritance
- Print the inheritance relations between contractsmodifiers
- Print the modifiers called by each functionrequire
- Print the require and assert calls of each functionvariable-order
- Print the storage order of the state variables
Example
$ slither . --print contract-summary
Woke
Detectors
$ woke detect .
or automatically in the Tools for Solidity VS Code extension.
Property-based fuzz testing
- multiprocessing implementation
- can automatically launch ipdb (IPython-enhanced debugger) on exception
Documentation: https://ackeeblockchain.com/woke/docs/1.2.1/fuzzer
Caution:
Woke fuzzer currently relies on the Brownie testing framework. Brownie is expected to be replaced by the Woke testing framework in Woke 2.0.
Terminology
- flow - a single operation (a function) randomly picked from a set of operations by the fuzzer
- invariant - a function executed after each flow, usually containing assertions
- sequence - a sequence of flows and invariants; after each sequence, the blockchain state is reset
- campaign - an object allowing to define number of sequences and number of flows in each sequence; automatically runs flows and invariants
Each flow can be assigned a weight, which determines the probability of the flow being picked. The default weight is 100.
Example
contract Counter {
uint256 public count;
function increment() public {
count += 1;
}
function decrement() public {
count -= 1;
}
}
import brownie
from woke.fuzzer.campaign import Campaign
from woke.fuzzer.decorators import flow, invariant, weight
from woke.fuzzer.random import random_account
class Test:
def __init__(self, contract_type) -> None:
self.owner = random_account()
self.contract = contract_type.deploy({"from": self.owner})
self.counter_value = 0
@flow
def flow_increment(self):
self.contract.increment({"from": random_account()})
self.counter_value += 1
@flow
@weight(101)
def flow_decrement(self):
if self.counter_value == 0:
with brownie.reverts():
self.contract.decrement({"from": random_account()})
else:
self.contract.decrement({"from": random_account()})
self.counter_value -= 1
@invariant
def invariant_counter_value(self):
assert self.contract.count() == self.counter_value
def test_counter(Counter):
campaign = Campaign(lambda: Test(Counter))
campaign.run(100, 40)
Echidna
Haskell program designed for fuzzy testing of Ethereum smart contracts
Example
$ echidna-test helloworld.sol --contract TEST --config echidna-config.yaml