Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Audit Solidity smart contracts for reentrancy, integer overflow, access control, and oracle manipulation.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: solidity-security3description: Master smart contract security best practices to prevent common vulnerabilities and implement secure Solidity patterns. Use when writing smart contracts, auditing existing contracts, or implementing security measures for blockchain applications.4---56# Solidity Security78Master smart contract security best practices, vulnerability prevention, and secure Solidity development patterns.910## When to Use This Skill1112- Writing secure smart contracts13- Auditing existing contracts for vulnerabilities14- Implementing secure DeFi protocols15- Preventing reentrancy, overflow, and access control issues16- Optimizing gas usage while maintaining security17- Preparing contracts for professional audits18- Understanding common attack vectors1920## Critical Vulnerabilities2122### 1. Reentrancy2324Attacker calls back into your contract before state is updated.2526**Vulnerable Code:**2728```solidity29// VULNERABLE TO REENTRANCY30contract VulnerableBank {31mapping(address => uint256) public balances;3233function withdraw() public {34uint256 amount = balances[msg.sender];3536// DANGER: External call before state update37(bool success, ) = msg.sender.call{value: amount}("");38require(success);3940balances[msg.sender] = 0; // Too late!41}42}43```4445**Secure Pattern (Checks-Effects-Interactions):**4647```solidity48contract SecureBank {49mapping(address => uint256) public balances;5051function withdraw() public {52uint256 amount = balances[msg.sender];53require(amount > 0, "Insufficient balance");5455// EFFECTS: Update state BEFORE external call56balances[msg.sender] = 0;5758// INTERACTIONS: External call last59(bool success, ) = msg.sender.call{value: amount}("");60require(success, "Transfer failed");61}62}63```6465**Alternative: ReentrancyGuard**6667```solidity68import "@openzeppelin/contracts/security/ReentrancyGuard.sol";6970contract SecureBank is ReentrancyGuard {71mapping(address => uint256) public balances;7273function withdraw() public nonReentrant {74uint256 amount = balances[msg.sender];75require(amount > 0, "Insufficient balance");7677balances[msg.sender] = 0;7879(bool success, ) = msg.sender.call{value: amount}("");80require(success, "Transfer failed");81}82}83```8485### 2. Integer Overflow/Underflow8687**Vulnerable Code (Solidity < 0.8.0):**8889```solidity90// VULNERABLE91contract VulnerableToken {92mapping(address => uint256) public balances;9394function transfer(address to, uint256 amount) public {95// No overflow check - can wrap around96balances[msg.sender] -= amount; // Can underflow!97balances[to] += amount; // Can overflow!98}99}100```101102**Secure Pattern (Solidity >= 0.8.0):**103104```solidity105// Solidity 0.8+ has built-in overflow/underflow checks106contract SecureToken {107mapping(address => uint256) public balances;108109function transfer(address to, uint256 amount) public {110// Automatically reverts on overflow/underflow111balances[msg.sender] -= amount;112balances[to] += amount;113}114}115```116117**For Solidity < 0.8.0, use SafeMath:**118119```solidity120import "@openzeppelin/contracts/utils/math/SafeMath.sol";121122contract SecureToken {123using SafeMath for uint256;124mapping(address => uint256) public balances;125126function transfer(address to, uint256 amount) public {127balances[msg.sender] = balances[msg.sender].sub(amount);128balances[to] = balances[to].add(amount);129}130}131```132133### 3. Access Control134135**Vulnerable Code:**136137```solidity138// VULNERABLE: Anyone can call critical functions139contract VulnerableContract {140address public owner;141142function withdraw(uint256 amount) public {143// No access control!144payable(msg.sender).transfer(amount);145}146}147```148149**Secure Pattern:**150151```solidity152import "@openzeppelin/contracts/access/Ownable.sol";153154contract SecureContract is Ownable {155function withdraw(uint256 amount) public onlyOwner {156payable(owner()).transfer(amount);157}158}159160// Or implement custom role-based access161contract RoleBasedContract {162mapping(address => bool) public admins;163164modifier onlyAdmin() {165require(admins[msg.sender], "Not an admin");166_;167}168169function criticalFunction() public onlyAdmin {170// Protected function171}172}173```174175### 4. Front-Running176177**Vulnerable:**178179```solidity180// VULNERABLE TO FRONT-RUNNING181contract VulnerableDEX {182function swap(uint256 amount, uint256 minOutput) public {183// Attacker sees this in mempool and front-runs184uint256 output = calculateOutput(amount);185require(output >= minOutput, "Slippage too high");186// Perform swap187}188}189```190191**Mitigation:**192193```solidity194contract SecureDEX {195mapping(bytes32 => bool) public usedCommitments;196197// Step 1: Commit to trade198function commitTrade(bytes32 commitment) public {199usedCommitments[commitment] = true;200}201202// Step 2: Reveal trade (next block)203function revealTrade(204uint256 amount,205uint256 minOutput,206bytes32 secret207) public {208bytes32 commitment = keccak256(abi.encodePacked(209msg.sender, amount, minOutput, secret210));211require(usedCommitments[commitment], "Invalid commitment");212// Perform swap213}214}215```216217## Security Best Practices218219### Checks-Effects-Interactions Pattern220221```solidity222contract SecurePattern {223mapping(address => uint256) public balances;224225function withdraw(uint256 amount) public {226// 1. CHECKS: Validate conditions227require(amount <= balances[msg.sender], "Insufficient balance");228require(amount > 0, "Amount must be positive");229230// 2. EFFECTS: Update state231balances[msg.sender] -= amount;232233// 3. INTERACTIONS: External calls last234(bool success, ) = msg.sender.call{value: amount}("");235require(success, "Transfer failed");236}237}238```239240### Pull Over Push Pattern241242```solidity243// Prefer this (pull)244contract SecurePayment {245mapping(address => uint256) public pendingWithdrawals;246247function recordPayment(address recipient, uint256 amount) internal {248pendingWithdrawals[recipient] += amount;249}250251function withdraw() public {252uint256 amount = pendingWithdrawals[msg.sender];253require(amount > 0, "Nothing to withdraw");254255pendingWithdrawals[msg.sender] = 0;256payable(msg.sender).transfer(amount);257}258}259260// Over this (push)261contract RiskyPayment {262function distributePayments(address[] memory recipients, uint256[] memory amounts) public {263for (uint i = 0; i < recipients.length; i++) {264// If any transfer fails, entire batch fails265payable(recipients[i]).transfer(amounts[i]);266}267}268}269```270271### Input Validation272273```solidity274contract SecureContract {275function transfer(address to, uint256 amount) public {276// Validate inputs277require(to != address(0), "Invalid recipient");278require(to != address(this), "Cannot send to contract");279require(amount > 0, "Amount must be positive");280require(amount <= balances[msg.sender], "Insufficient balance");281282// Proceed with transfer283balances[msg.sender] -= amount;284balances[to] += amount;285}286}287```288289### Emergency Stop (Circuit Breaker)290291```solidity292import "@openzeppelin/contracts/security/Pausable.sol";293294contract EmergencyStop is Pausable, Ownable {295function criticalFunction() public whenNotPaused {296// Function logic297}298299function emergencyStop() public onlyOwner {300_pause();301}302303function resume() public onlyOwner {304_unpause();305}306}307```308309## Gas Optimization310311### Use `uint256` Instead of Smaller Types312313```solidity314// More gas efficient315contract GasEfficient {316uint256 public value; // Optimal317318function set(uint256 _value) public {319value = _value;320}321}322323// Less efficient324contract GasInefficient {325uint8 public value; // Still uses 256-bit slot326327function set(uint8 _value) public {328value = _value; // Extra gas for type conversion329}330}331```332333### Pack Storage Variables334335```solidity336// Gas efficient (3 variables in 1 slot)337contract PackedStorage {338uint128 public a; // Slot 0339uint64 public b; // Slot 0340uint64 public c; // Slot 0341uint256 public d; // Slot 1342}343344// Gas inefficient (each variable in separate slot)345contract UnpackedStorage {346uint256 public a; // Slot 0347uint256 public b; // Slot 1348uint256 public c; // Slot 2349uint256 public d; // Slot 3350}351```352353### Use `calldata` Instead of `memory` for Function Arguments354355```solidity356contract GasOptimized {357// More gas efficient358function processData(uint256[] calldata data) public pure returns (uint256) {359return data[0];360}361362// Less efficient363function processDataMemory(uint256[] memory data) public pure returns (uint256) {364return data[0];365}366}367```368369### Use Events for Data Storage (When Appropriate)370371```solidity372contract EventStorage {373// Emitting events is cheaper than storage374event DataStored(address indexed user, uint256 indexed id, bytes data);375376function storeData(uint256 id, bytes calldata data) public {377emit DataStored(msg.sender, id, data);378// Don't store in contract storage unless needed379}380}381```382383## Common Vulnerabilities Checklist384385```solidity386// Security Checklist Contract387contract SecurityChecklist {388/**389* [ ] Reentrancy protection (ReentrancyGuard or CEI pattern)390* [ ] Integer overflow/underflow (Solidity 0.8+ or SafeMath)391* [ ] Access control (Ownable, roles, modifiers)392* [ ] Input validation (require statements)393* [ ] Front-running mitigation (commit-reveal if applicable)394* [ ] Gas optimization (packed storage, calldata)395* [ ] Emergency stop mechanism (Pausable)396* [ ] Pull over push pattern for payments397* [ ] No delegatecall to untrusted contracts398* [ ] No tx.origin for authentication (use msg.sender)399* [ ] Proper event emission400* [ ] External calls at end of function401* [ ] Check return values of external calls402* [ ] No hardcoded addresses403* [ ] Upgrade mechanism (if proxy pattern)404*/405}406```407408## Testing for Security409410```javascript411// Hardhat test example412const { expect } = require("chai");413const { ethers } = require("hardhat");414415describe("Security Tests", function () {416it("Should prevent reentrancy attack", async function () {417const [attacker] = await ethers.getSigners();418419const VictimBank = await ethers.getContractFactory("SecureBank");420const bank = await VictimBank.deploy();421422const Attacker = await ethers.getContractFactory("ReentrancyAttacker");423const attackerContract = await Attacker.deploy(bank.address);424425// Deposit funds426await bank.deposit({ value: ethers.utils.parseEther("10") });427428// Attempt reentrancy attack429await expect(430attackerContract.attack({ value: ethers.utils.parseEther("1") }),431).to.be.revertedWith("ReentrancyGuard: reentrant call");432});433434it("Should prevent integer overflow", async function () {435const Token = await ethers.getContractFactory("SecureToken");436const token = await Token.deploy();437438// Attempt overflow439await expect(token.transfer(attacker.address, ethers.constants.MaxUint256))440.to.be.reverted;441});442443it("Should enforce access control", async function () {444const [owner, attacker] = await ethers.getSigners();445446const Contract = await ethers.getContractFactory("SecureContract");447const contract = await Contract.deploy();448449// Attempt unauthorized withdrawal450await expect(contract.connect(attacker).withdraw(100)).to.be.revertedWith(451"Ownable: caller is not the owner",452);453});454});455```456457## Audit Preparation458459```solidity460contract WellDocumentedContract {461/**462* @title Well Documented Contract463* @dev Example of proper documentation for audits464* @notice This contract handles user deposits and withdrawals465*/466467/// @notice Mapping of user balances468mapping(address => uint256) public balances;469470/**471* @dev Deposits ETH into the contract472* @notice Anyone can deposit funds473*/474function deposit() public payable {475require(msg.value > 0, "Must send ETH");476balances[msg.sender] += msg.value;477}478479/**480* @dev Withdraws user's balance481* @notice Follows CEI pattern to prevent reentrancy482* @param amount Amount to withdraw in wei483*/484function withdraw(uint256 amount) public {485// CHECKS486require(amount <= balances[msg.sender], "Insufficient balance");487488// EFFECTS489balances[msg.sender] -= amount;490491// INTERACTIONS492(bool success, ) = msg.sender.call{value: amount}("");493require(success, "Transfer failed");494}495}496```497