Static_Analysis
Smart Contract Security Analysis with Slither
Overview
In this security research exercise, I analyzed three example Solidity contracts using Slither — a static analysis tool for smart contracts.
The goal was to:
- Detect vulnerabilities
- Understand the risks
- Show how to write safer smart contracts
- Practice presenting findings to recruiters
Contracts Under Review
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/*
Your original Counter — unchanged
*/
contract Counter {
int256 private count;
address public owner;
constructor() payable {
count = 0;
owner = msg.sender;
}
function increment() public {
count += 1;
owner = msg.sender;
}
function decrement() public {
count -= 1;
owner = msg.sender;
}
function getCount() public view returns (int256) {
return count;
}
}
/*
Simple DeFi: a minimal deposit/withdraw vault — INTENTIONALLY VULNERABLE
- Uses .call and updates state AFTER sending ETH → classic reentrancy pattern
- Public mapping shows balances
*/
contract SimpleVault {
mapping(address => uint256) public balanceOf;
function deposit() external payable {
balanceOf[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balanceOf[msg.sender] >= amount, "insufficient");
// Vulnerable: external call before state update
(bool ok, ) = payable(msg.sender).call{value: amount}("");
require(ok, "send failed");
balanceOf[msg.sender] -= amount; // state update after external call
}
}
/*
Safer DeFi version:
- Checks-Effects-Interactions pattern (state update BEFORE external call)
*/
contract SafeVault {
mapping(address => uint256) public balanceOf;
function deposit() external payable {
balanceOf[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balanceOf[msg.sender] >= amount, "insufficient");
// Effects first
balanceOf[msg.sender] -= amount;
// Interaction after effects
(bool ok, ) = payable(msg.sender).call{value: amount}("");
require(ok, "send failed");
}
}
My Command Line Process
I used Slither to run the analysis in my local environment:
root@lenova:~/practice# slither .
'forge clean' running (wd: /root/practice)
'forge config --json' running
'forge build --build-info --skip */test/** */script/** --force' running (wd: /root/practice)
INFO:Detectors:
Reentrancy in SimpleVault.withdraw(uint256) (src/Counter.sol#44-50):
External calls:
- (ok,None) = address(msg.sender).call{value: amount}() (src/Counter.sol#47)
State variables written after the call(s):
- balanceOf[msg.sender] -= amount (src/Counter.sol#49)
SimpleVault.balanceOf can be used in cross function reentrancies.
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities
INFO:Detectors:
Contract locking ether found:
Contract Counter has payable functions but no way to withdraw the ether.
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether
INFO:Detectors:
Version constraint ^0.8.0 contains known severe issues.
INFO:Detectors:
Low level call in SimpleVault.withdraw(uint256)
Low level call in SafeVault.withdraw(uint256)
INFO:Slither:. analyzed (3 contracts with 93 detectors), 5 result(s) found
Findings & Analysis
1. Reentrancy Vulnerability — SimpleVault (High Risk)
- Found in:
(bool ok, ) = payable(msg.sender).call{value: amount}("");
balanceOf[msg.sender] -= amount; - Why bad: State change happens after sending ETH, allowing malicious fallback calls to exploit the function.
- Fix: Use Checks-Effects-Interactions pattern or ReentrancyGuard.
2. Locked Ether — Counter (Medium Risk)
- Contract can receive ETH (payable constructor) but no function to withdraw it.
- Effect: ETH sent can never be recovered.
3. Outdated Solidity Version (Medium Risk)
^0.8.0includes known compiler bugs.- Fix: Upgrade to latest stable version (e.g.,
^0.8.21).
4. Low-Level Call Usage (Low Risk / Informational)
.callis used in both withdraw functions.- Risk: Can trigger arbitrary fallback logic.
- Recommendation: Use cautiously, always handle return values.
Summary Table
| Issue Detected | Contract | Severity | Impact |
|---|---|---|---|
| Reentrancy | SimpleVault | High | Theft of ETH |
| Locked Ether | Counter | Medium | Funds locked |
| Outdated Solidity Version | All | Medium | Compiler bugs |
| Low-Level Call Usage | Vaults | Low | Unexpected behavior |
Lessons Learned
- Always update state before external calls.
- Provide ETH recovery methods for payable contracts.
- Use the latest compiler versions.
- Treat
.callwith caution.