Vulnerabilities
Missing Access Control: The #1 Smart Contract Vulnerability
According to Chainalysis, missing or broken access control is the single most exploited vulnerability category in DeFi history — responsible for hundreds of millions in losses. It's also one of the easiest to prevent.
Access control simply means: making sure only authorized addresses can call sensitive functions. When this is missing or broken, anyone can mint tokens, drain funds, or take over the contract.
The Most Common Mistakes
Mistake 1: No modifier on sensitive functions
❌ Anyone can mint
function mint(address to, uint256 amount) public {
// No access control — ANYONE can call this!
totalSupply += amount;
balanceOf[to] += amount;
}
✅ Fixed with onlyOwner
function mint(address to, uint256 amount) public onlyOwner {
totalSupply += amount;
balanceOf[to] += amount;
}
Mistake 2: Using tx.origin instead of msg.sender
❌ Vulnerable to phishing
modifier onlyOwner() {
require(tx.origin == owner, "Not owner");
// tx.origin is the original EOA, not the caller
// A malicious contract can exploit this!
_;
}
✅ Use msg.sender always
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
Why tx.origin is dangerous: If a user interacts with a malicious contract, that contract can call your function and tx.origin will still be the user's address — bypassing your check. msg.sender is always the direct caller.
Mistake 3: No zero address check on ownership transfer
❌ Can accidentally burn ownership
function transferOwnership(address newOwner) public onlyOwner {
owner = newOwner; // What if newOwner is address(0)?
}
✅ Always check for zero address
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Zero address");
owner = newOwner;
}
Use OpenZeppelin's Ownable
Don't write your own access control from scratch. Use OpenZeppelin's battle-tested Ownable contract which handles all edge cases correctly.
✅ Best practice
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is Ownable {
function mint(address to, uint256 amount) public onlyOwner {
// Fully protected
_mint(to, amount);
}
}
Role-Based Access Control for Complex Contracts
For contracts with multiple roles (admin, minter, pauser), use OpenZeppelin's AccessControl for granular permissions.
✅ Role-based access
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to, uint256 amount) public {
require(hasRole(MINTER_ROLE, msg.sender), "Not minter");
// ...
}
}
Check your contract for access control issues
AuditAI detects missing modifiers, tx.origin usage, unprotected functions and more. Free for any Solidity contract.
⬡ Run Free Audit