How to use slither to audit smart contracts

bugbountydegen
5 min readAug 25, 2023

--

Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.

Prerequisites:

  • You will need to compile and install solc from Ethereum (Not solc-js!)

If you just want to scan on-chain contracts online without installing slither in your system, you can try my free on-chain scanner

To install the latest version, the best option is to compile it directly from the official Github repository:

Now we can install slither directly using pip:

# pip3 install slither-analyzer

Let’s see how it works, the syntax in very easy, just point to the directory where the smart contracts are or pass solidity files as argument.

~/simple-auction$ slither . 
Compilation warnings/errors on ./simple-auction.sol:
Warning: This is a pre-release compiler version, please do not use it in production.



SimpleAuction.constructor(uint256,address).beneficiaryAddress (simple-auction.sol#46) lacks a zero-check on :
- beneficiary = beneficiaryAddress (simple-auction.sol#48)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation

SimpleAuction.bid() (simple-auction.sol#56-87) uses timestamp for comparisons
Dangerous comparisons:
- block.timestamp > auctionEndTime (simple-auction.sol#65)
SimpleAuction.auctionEnd() (simple-auction.sol#112-138) uses timestamp for comparisons
Dangerous comparisons:
- block.timestamp < auctionEndTime (simple-auction.sol#127)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp

solc-0.8.14 is not recommended for deployment
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity
. analyzed (1 contracts with 77 detectors), 4 result(s) found

As we see above it has found 4 issues:

  1. Line 48: Missing zero address validation in the beneficiary address variable
  2. Line 65: block.timestamp is being used for timestamp comparisons
  3. Line 127: block.timestamp is being used for timestamp comparisons
  4. solc v0.8.14 it is not recommended for production deployments

Pros:

  • Good recommendations using an automatic tool

Cons:

  • Difficult setup: Relies on underlying solc version and can present some incompatibilities with python libraries
  • Confusing UI: Despite the severity of findings is coloured, sometimes it is difficult to differentiate findings or details due that too many details

Bonus track: How to avoid @openzeppelin import errors in Slither:

slither contract.sol --solc-remaps @=../node_modules/@

Annex

Code used in the example (simple-auction.sol):

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract SimpleAuction {
// Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01)
// or time periods in seconds.
address payable public beneficiary;
uint public auctionEndTime;

// Current state of the auction.
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
// Set to true at the end, disallows any change.
// By default initialized to `false`.
bool ended;
// Events that will be emitted on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// Errors that describe failures.
// The triple-slash comments are so-called natspec
// comments. They will be shown when the user
// is asked to confirm a transaction or
// when an error is displayed.
/// The auction has already ended.
error AuctionAlreadyEnded();
/// There is already a higher or equal bid.
error BidNotHighEnough(uint highestBid);
/// The auction has not ended yet.
error AuctionNotYetEnded();
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
/// Create a simple auction with `biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `beneficiaryAddress`.
constructor(
uint biddingTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
auctionEndTime = block.timestamp + biddingTime;
}
/// Bid on the auction with the value sent
/// together with this transaction.
/// The value will only be refunded if the
/// auction is not won.
function bid() external payable {
// No arguments are necessary, all
// information is already part of
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
// Revert the call if the bidding
// period is over.
if (block.timestamp > auctionEndTime)
revert AuctionAlreadyEnded();
// If the bid is not higher, send the
// Ether back (the revert statement
// will revert all changes in this
// function execution including
// it having received the Ether).
if (msg.value <= highestBid)
revert BidNotHighEnough(highestBid);
if (highestBid != 0) {
// Sending back the Ether by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their Ether themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
function withdraw() external returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
// msg.sender is not of type `address payable` and must be
// explicitly converted using `payable(msg.sender)` in order
// use the member function `send()`.
if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd() external {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
if (block.timestamp < auctionEndTime)
revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
// 2. Effects
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
}
}

Subscribe to the BugBountyDegen Newsletter

Follow me on Twitter: @bugbountydegen

--

--