
Solidity Auditor
- 178 installs
- 15 repo stars
- Updated January 19, 2026
- schwepps/skills
Review Solidity smart contracts for vulnerabilities, unsafe patterns, and deployment risks before mainnet or production wallet integrations ship.
About
Audits Solidity smart contracts for common and subtle vulnerabilities—including reentrancy, access control, and external call risks—so Web3 protocols can ship contracts with fewer exploitable defects.
- Reentrancy and access-control review
- Integer overflow and logic flaws
- Oracle and external call risks
- Gas and upgradeability pitfalls
- Pre-deployment audit checklist
Solidity Auditor by the numbers
- 178 all-time installs (skills.sh)
- +2 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #112 of 479 Web3 & Blockchain skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/schwepps/skills --skill solidity-auditorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 178 |
|---|---|
| repo stars | ★ 15 |
| Last updated | January 19, 2026 |
| Repository | schwepps/skills ↗ |
What it does
Review Solidity smart contracts for vulnerabilities, unsafe patterns, and deployment risks before mainnet or production wallet integrations ship.
Files
Solidity Smart Contract Auditor
A professional-grade smart contract audit skill covering security vulnerabilities, gas optimization, storage patterns, and code architecture. Adapted to Solidity version specifics.
Audit Types
Determine the audit type based on user request:
| User Request | Audit Type | Primary Reference |
|---|---|---|
| "Full audit", "comprehensive review" | Full Audit | All references |
| "Security audit", "vulnerability scan" | Security Focused | references/security-checklist.md |
| "Gas optimization", "reduce gas costs" | Gas Optimization | references/gas-optimization.md |
| "Storage optimization", "storage patterns" | Storage Optimization | references/storage-optimization.md |
| "Code review", "architecture review" | Architecture Review | references/architecture-review.md |
| "DeFi audit", "protocol review" | DeFi Protocol | Security + Architecture references |
Core Audit Workflow
Phase 1: Preparation
1. Identify Solidity Version: Check pragma statement. Read references/version-specific.md for version-specific considerations:
- Pre-0.8.0: Check for SafeMath usage, arithmetic vulnerabilities
- 0.8.0+: Review
uncheckedblocks, check custom errors usage
2. Understand Scope:
- List all contracts, interfaces, libraries
- Identify external dependencies (OpenZeppelin, etc.)
- Note inheritance hierarchy
- Document entry points (external/public functions)
3. Gather Context: Ask if not provided:
- Protocol purpose and intended behavior
- Deployment chain(s)
- Expected user flows
- Admin roles and privileges
Phase 2: Static Analysis
1. Run automated checks mentally using patterns from the security checklist:
- Access control patterns
- State-changing operations flow (checks-effects-interactions)
- External call patterns
- Arithmetic operations (especially in
uncheckedblocks)
2. Map attack surface:
- External/public functions
- Functions handling ETH/tokens
- Functions with access control
- Upgrade mechanisms
Phase 3: Vulnerability Assessment
Read references/security-checklist.md and evaluate each category:
Critical Priority (check first): 1. Access Control Vulnerabilities (OWASP SC-01) - $953M+ in losses 2. Logic Errors (OWASP SC-02) - $64M+ in losses 3. Reentrancy (OWASP SC-03) - $36M+ in losses
High Priority: 4. Flash Loan Attack Vectors (OWASP SC-04) 5. Input Validation (OWASP SC-05) 6. Oracle Manipulation (OWASP SC-06) 7. Unchecked External Calls (OWASP SC-07)
Medium Priority: 8. Integer Overflow/Underflow (version-dependent) 9. Denial of Service vectors 10. Front-running vulnerabilities
Phase 4: Optimization Analysis (if requested)
For gas optimization: Read references/gas-optimization.md For storage optimization: Read references/storage-optimization.md
Phase 5: Report Generation
Use the template in references/report-template.md to structure findings.
Severity Classification
| Severity | Criteria | Action |
|---|---|---|
| Critical | Direct fund loss possible, no user interaction needed | Immediate fix required, do not deploy |
| High | Fund loss possible with specific conditions, significant impact | Must fix before deployment |
| Medium | Limited impact, unlikely exploitation, or governance issue | Should fix, assess risk |
| Low | Minor issue, best practice violation | Recommended fix |
| Informational | Code quality, gas optimization, suggestions | Optional improvement |
Quick Reference: Top Attack Vectors (2024-2025)
From OWASP Smart Contract Top 10 (2025) with real losses:
1. Access Control ($953.2M): Missing/incorrect modifiers, exposed admin functions 2. Logic Errors ($63.8M): Flawed business logic, incorrect calculations 3. Reentrancy ($35.7M): State updates after external calls 4. Flash Loans ($33.8M): Price manipulation, governance attacks 5. Input Validation ($14.6M): Missing bounds checks, unchecked parameters 6. Oracle Manipulation ($8.8M): TWAP manipulation, stale prices
Output Guidelines
Always provide: 1. Clear finding title with severity 2. Location: Contract name, function, line numbers 3. Description: What the issue is 4. Impact: Potential consequences 5. Proof of Concept: How it could be exploited (when applicable) 6. Recommendation: Specific fix with code example
Format recommendations as actionable code changes when possible.
Reference Files
Load these as needed based on audit type:
references/security-checklist.md- Complete vulnerability checklist with detection patternsreferences/gas-optimization.md- Gas optimization techniques and patternsreferences/storage-optimization.md- Storage layout and optimizationreferences/architecture-review.md- Code architecture best practicesreferences/version-specific.md- Solidity version considerationsreferences/report-template.md- Professional audit report template
Code Architecture Review Guide
Best practices for smart contract architecture, design patterns, and code quality.
Contract Structure
Recommended Order
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// 1. Imports
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
// 2. Interfaces
interface ICustom {
function doSomething() external;
}
// 3. Libraries
library MathLib {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}
}
// 4. Contract declaration with inheritance
contract MyContract is ReentrancyGuard {
// 5. Type declarations
using MathLib for uint256;
struct UserInfo {
uint256 balance;
uint256 lastUpdate;
}
enum Status { Pending, Active, Completed }
// 6. State variables (order by visibility, then by type)
// Constants
uint256 public constant MAX_FEE = 1000;
// Immutables
address public immutable owner;
// Storage variables
mapping(address => UserInfo) public users;
uint256 private _totalSupply;
// 7. Events
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
// 8. Errors (0.8.4+)
error Unauthorized();
error InvalidAmount(uint256 provided, uint256 required);
// 9. Modifiers
modifier onlyOwner() {
if (msg.sender != owner) revert Unauthorized();
_;
}
// 10. Constructor
constructor() {
owner = msg.sender;
}
// 11. Receive/Fallback
receive() external payable {}
fallback() external payable {}
// 12. External functions
function deposit() external payable { }
// 13. Public functions
function getBalance(address user) public view returns (uint256) { }
// 14. Internal functions
function _updateBalance(address user, uint256 amount) internal { }
// 15. Private functions
function _validate() private view { }
}---
Design Patterns
Access Control Patterns
Single Owner (Simple):
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "Invalid address");
owner = newOwner;
}Two-Step Ownership Transfer (Safer):
address public owner;
address public pendingOwner;
function transferOwnership(address newOwner) external onlyOwner {
pendingOwner = newOwner;
}
function acceptOwnership() external {
require(msg.sender == pendingOwner, "Not pending owner");
owner = pendingOwner;
pendingOwner = address(0);
}Role-Based Access (OpenZeppelin AccessControl):
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
contract MyContract is AccessControl {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { }
}Reentrancy Protection
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MyContract is ReentrancyGuard {
function withdraw() external nonReentrant {
// Safe from reentrancy
}
}Pausability
import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
contract MyContract is Pausable {
function deposit() external whenNotPaused { }
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
}Pull Over Push
// PUSH (vulnerable to DoS):
function distribute() external {
for (uint i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(amounts[i]); // Can fail and block all
}
}
// PULL (safe):
mapping(address => uint256) public pendingWithdrawals;
function claimReward() external {
uint256 amount = pendingWithdrawals[msg.sender];
pendingWithdrawals[msg.sender] = 0;
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}Checks-Effects-Interactions (CEI)
function withdraw(uint256 amount) external {
// 1. CHECKS
require(balances[msg.sender] >= amount, "Insufficient balance");
// 2. EFFECTS (state changes)
balances[msg.sender] -= amount;
// 3. INTERACTIONS (external calls)
(bool success,) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}---
Upgradeability Patterns
UUPS (Recommended)
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract MyContractV1 is Initializable, UUPSUpgradeable {
uint256 public value;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(uint256 _value) external initializer {
value = _value;
}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}Transparent Proxy
// Deployment via OpenZeppelin Hardhat upgrades plugin
// Admin functions handled by separate ProxyAdmin contractStorage Layout for Upgrades
// V1 Storage
contract V1 {
uint256 public valueA; // Slot 0
uint256 public valueB; // Slot 1
}
// V2 Storage - CORRECT (append only)
contract V2 {
uint256 public valueA; // Slot 0 - unchanged
uint256 public valueB; // Slot 1 - unchanged
uint256 public valueC; // Slot 2 - new variable
}
// V2 Storage - WRONG (reordering breaks storage)
contract V2Wrong {
uint256 public valueC; // Slot 0 - COLLISION with valueA!
uint256 public valueA; // Slot 1
uint256 public valueB; // Slot 2
}---
Error Handling
Custom Errors (0.8.4+)
// Define at contract level
error Unauthorized(address caller);
error InsufficientBalance(uint256 available, uint256 required);
error InvalidAddress();
// Usage
function withdraw(uint256 amount) external {
if (msg.sender != owner) revert Unauthorized(msg.sender);
if (balance < amount) revert InsufficientBalance(balance, amount);
// ...
}Try-Catch for External Calls
function safeTransfer(address token, address to, uint256 amount) internal returns (bool) {
try IERC20(token).transfer(to, amount) returns (bool success) {
return success;
} catch {
return false;
}
}---
Code Quality Checklist
Naming Conventions
- [ ] Contracts: PascalCase (
MyContract) - [ ] Functions: camelCase (
getBalance) - [ ] Variables: camelCase (
totalSupply) - [ ] Constants: UPPER_SNAKE_CASE (
MAX_SUPPLY) - [ ] Private/internal state: leading underscore (
_totalSupply) - [ ] Events: PascalCase past tense (
Transferred) - [ ] Errors: PascalCase (
InsufficientBalance)
Documentation
- [ ] NatSpec comments on all public/external functions
- [ ] Contract-level documentation
- [ ] Complex logic explained
- [ ] Parameter descriptions
/// @notice Deposits tokens into the vault
/// @dev Emits a Deposited event
/// @param amount The amount of tokens to deposit
/// @return shares The number of shares minted
function deposit(uint256 amount) external returns (uint256 shares) {
// ...
}Security Practices
- [ ] No floating pragmas in production (
^0.8.0→0.8.20) - [ ] Latest stable compiler version used
- [ ] OpenZeppelin contracts used for standard functionality
- [ ] Reentrancy guards on external-calling functions
- [ ] Access control on sensitive functions
- [ ] Events emitted for state changes
- [ ] Zero-address checks on initialization
- [ ] Input validation
Testing Coverage
- [ ] Unit tests for all functions
- [ ] Integration tests for workflows
- [ ] Edge case coverage
- [ ] Fuzz testing for arithmetic
- [ ] Fork tests for mainnet interactions
---
Architecture Audit Checklist
Contract Organization:
- [ ] Logical separation of concerns
- [ ] Appropriate use of libraries
- [ ] Clear inheritance hierarchy
- [ ] Consistent code style
Access Control:
- [ ] Roles clearly defined
- [ ] No over-privileged functions
- [ ] Emergency mechanisms appropriate
- [ ] Multi-sig for critical operations
Upgradeability (if used):
- [ ] Storage layout documented
- [ ] Initializer protected
- [ ] Implementation cannot be initialized
- [ ] Upgrade authorization appropriate
Integration:
- [ ] External dependencies audited
- [ ] Oracle usage appropriate
- [ ] Token standards followed correctly
- [ ] Gas limits considered for loops
Gas Optimization Techniques
Comprehensive guide to reducing gas costs in Solidity smart contracts. Techniques sorted by impact.
High Impact Optimizations
1. Storage vs Memory vs Calldata
Storage is the most expensive operation (~20,000 gas for SSTORE).
// EXPENSIVE: Multiple storage reads
function badExample() external {
for (uint i = 0; i < users.length; i++) {
total += balances[users[i]]; // Storage read each iteration
}
}
// OPTIMIZED: Cache in memory
function goodExample() external {
uint256 _total = total; // Single storage read
address[] memory _users = users; // Cache array
for (uint i = 0; i < _users.length; i++) {
_total += balances[_users[i]];
}
total = _total; // Single storage write
}Guidelines:
- Cache storage variables in memory/stack for multiple reads
- Use
calldatafor external function array/struct parameters (read-only) - Use
memoryfor internal function parameters - Minimize storage writes (batch updates)
2. Variable Packing
EVM operates on 32-byte slots. Pack smaller types together.
// EXPENSIVE: 3 storage slots
contract Bad {
uint8 a; // Slot 0 (wastes 31 bytes)
uint256 b; // Slot 1
uint8 c; // Slot 2 (wastes 31 bytes)
}
// OPTIMIZED: 2 storage slots
contract Good {
uint8 a; // Slot 0
uint8 c; // Slot 0 (packed with a)
uint256 b; // Slot 1
}Packing Rules:
- Declare smaller types consecutively
- Structs follow same packing rules
- Mappings/arrays always start new slot
- Pack to fill 32 bytes when possible
3. Use Mappings Over Arrays
Mappings are generally cheaper for key-value lookups.
// EXPENSIVE: Array iteration O(n)
address[] public whitelist;
function isWhitelisted(address user) public view returns (bool) {
for (uint i = 0; i < whitelist.length; i++) {
if (whitelist[i] == user) return true;
}
return false;
}
// OPTIMIZED: Mapping O(1)
mapping(address => bool) public whitelist;
function isWhitelisted(address user) public view returns (bool) {
return whitelist[user];
}When to use arrays: When iteration is required or packing small types.
4. Short-Circuit Evaluation
Order conditions by failure probability and cost.
// EXPENSIVE: Expensive check first
require(expensiveComputation() && simpleCheck, "Failed");
// OPTIMIZED: Cheap check first (short-circuits)
require(simpleCheck && expensiveComputation(), "Failed");5. Use Constants and Immutables
// EXPENSIVE: Regular storage
uint256 public fee = 100;
// OPTIMIZED: Constant (compile-time, no storage)
uint256 public constant FEE = 100;
// OPTIMIZED: Immutable (set once in constructor, no storage read)
uint256 public immutable deployTime;
constructor() {
deployTime = block.timestamp;
}---
Medium Impact Optimizations
6. Function Visibility
external is cheaper than public for external calls.
// EXPENSIVE: public copies calldata to memory
function transfer(address to, uint256 amount) public { }
// OPTIMIZED: external reads directly from calldata
function transfer(address to, uint256 amount) external { }Guideline: Use external unless function is called internally.
7. Unchecked Arithmetic (0.8.0+)
When overflow is impossible, skip checks to save ~30-40 gas per operation.
// STANDARD: Overflow checks (safe but costly)
for (uint256 i = 0; i < length; i++) { }
// OPTIMIZED: Safe because i < length prevents overflow
for (uint256 i = 0; i < length;) {
// ... loop body
unchecked { ++i; }
}Use unchecked when:
- Loop counters with known bounds
- Math where overflow is mathematically impossible
- Post-validation arithmetic
Never use unchecked for:
- User input arithmetic
- Token balance operations without prior validation
8. Custom Errors (0.8.4+)
// EXPENSIVE: String error messages
require(balance >= amount, "Insufficient balance");
// OPTIMIZED: Custom errors
error InsufficientBalance(uint256 available, uint256 required);
if (balance < amount) revert InsufficientBalance(balance, amount);Saves ~50 gas per revert and reduces deployment cost.
9. Pre-increment vs Post-increment
// SLIGHTLY MORE EXPENSIVE
i++; // Creates temporary copy
// OPTIMIZED
++i; // Direct increment~5 gas savings per operation. Significant in loops.
10. Use bytes32 Over string
// EXPENSIVE: Dynamic string
string public name = "MyToken";
// OPTIMIZED: Fixed bytes32
bytes32 public constant NAME = "MyToken";---
Low Impact but Good Practice
11. Optimizer Settings
Enable Solidity optimizer in compiler settings:
// hardhat.config.js
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200 // Optimize for deployment (low) vs runtime (high)
}
}
}- Low runs (200): Smaller deployment cost
- High runs (10000): Lower runtime cost
12. Event Indexing
Index only fields that need filtering. Each indexed field costs extra.
// Over-indexed (wastes gas)
event Transfer(address indexed from, address indexed to, uint256 indexed amount);
// Appropriately indexed
event Transfer(address indexed from, address indexed to, uint256 amount);13. Avoid Zero to Non-Zero Storage
First write to storage slot costs 20,000 gas. Subsequent: 5,000.
// EXPENSIVE: Zero to non-zero
mapping(address => uint256) balances;
balances[user] = 100; // 20,000 gas first time
// TECHNIQUE: Initialize to 1 if appropriate
// Useful for "exists" flags or counters14. Use Bitmaps for Flags
// EXPENSIVE: Separate storage slots
mapping(address => bool) public claimed;
// OPTIMIZED: Pack 256 flags per slot
mapping(uint256 => uint256) private claimedBitmap;
function isClaimed(uint256 index) public view returns (bool) {
uint256 wordIndex = index / 256;
uint256 bitIndex = index % 256;
return (claimedBitmap[wordIndex] >> bitIndex) & 1 == 1;
}---
Gas Cost Reference
| Operation | Gas Cost |
|---|---|
| SSTORE (zero to non-zero) | 20,000 |
| SSTORE (non-zero to non-zero) | 5,000 |
| SSTORE (non-zero to zero) | 5,000 + 15,000 refund |
| SLOAD | 2,100 (cold) / 100 (warm) |
| MLOAD/MSTORE | 3 |
| CALLDATALOAD | 3 |
| Memory expansion | Quadratic |
| External call | 2,600 (cold) + execution |
| LOG0-LOG4 | 375-1,875 + data |
---
Audit Checklist for Gas
- [ ] Storage variables cached in memory for multiple reads
- [ ] Variables packed efficiently
- [ ]
externalused instead ofpublicwhere appropriate - [ ]
calldataused for external function array parameters - [ ] Constants/immutables used for fixed values
- [ ]
uncheckedblocks used safely for bounded arithmetic - [ ] Custom errors used (0.8.4+)
- [ ] Optimizer enabled with appropriate runs value
- [ ] No unbounded loops
- [ ] Mappings preferred over arrays for lookups
- [ ] Events appropriately indexed
- [ ]
++ipreferred overi++
Audit Report Template
Professional smart contract audit report format.
Report Structure
# Smart Contract Security Audit Report
[Project Name]
## Executive Summary
**Project:** [Project Name]
**Auditor:** [Auditor Name/AI Assistant]
**Date:** [Date]
**Commit:** [Commit Hash]
**Scope:** [List of contracts]
**Solidity Version:** [Version]
### Overview
[1-2 paragraph summary of what was audited and key findings]
### Risk Summary
| Severity | Count |
|----------|-------|
| Critical | X |
| High | X |
| Medium | X |
| Low | X |
| Info | X |
### Key Findings
- [Most critical finding summary]
- [Second most critical finding summary]
- [Other notable findings]
---
## Table of Contents
1. [Executive Summary](#executive-summary)
2. [Scope](#scope)
3. [Methodology](#methodology)
4. [Findings](#findings)
5. [Gas Optimizations](#gas-optimizations)
6. [Recommendations](#recommendations)
---
## Scope
### Contracts in Scope
| Contract | Lines | Description |
|----------|-------|-------------|
| Contract1.sol | XXX | Brief description |
| Contract2.sol | XXX | Brief description |
### Out of Scope
- [List any excluded contracts/files]
- External dependencies (e.g., OpenZeppelin)
### Deployment Information
- **Network:** [Mainnet/Testnet/Multi-chain]
- **Expected TVL:** [If applicable]
---
## Methodology
### Tools Used
- Manual code review
- Static analysis (Slither patterns)
- Logic analysis
### Review Process
1. Code understanding and documentation review
2. Static analysis for common vulnerabilities
3. Manual review following OWASP Smart Contract Top 10
4. Business logic verification
5. Gas optimization analysis
---
## Findings
### [CRITICAL-01] [Finding Title]
**Severity:** Critical
**Status:** [Open/Acknowledged/Fixed]
**File:** `Contract.sol`
**Lines:** XX-YY
#### Description
[Detailed explanation of the vulnerability]
#### Impact
[What could happen if exploited]
#### Proof of Concept// Vulnerable code function vulnerableFunction() external { // problematic code }
Attack scenario:
1. Attacker calls function with malicious input
2. State corruption occurs
3. Funds are drained
#### Recommendation// Fixed code function fixedFunction() external { // corrected code with proper checks }
---
### [HIGH-01] [Finding Title]
**Severity:** High
**Status:** [Open/Acknowledged/Fixed]
**File:** `Contract.sol`
**Lines:** XX-YY
#### Description
[Explanation]
#### Impact
[Impact description]
#### Recommendation
[Fix with code example]
---
### [MEDIUM-01] [Finding Title]
**Severity:** Medium
**Status:** [Open/Acknowledged/Fixed]
**File:** `Contract.sol`
**Lines:** XX-YY
#### Description
[Explanation]
#### Impact
[Impact description]
#### Recommendation
[Fix suggestion]
---
### [LOW-01] [Finding Title]
**Severity:** Low
**Status:** [Open/Acknowledged/Fixed]
**File:** `Contract.sol`
**Lines:** XX-YY
#### Description
[Explanation]
#### Recommendation
[Fix suggestion]
---
### [INFO-01] [Finding Title]
**Severity:** Informational
**File:** `Contract.sol`
#### Description
[Observation or suggestion]
#### Recommendation
[Optional improvement]
---
## Gas Optimizations
### [GAS-01] [Optimization Title]
**File:** `Contract.sol`
**Lines:** XX-YY
**Estimated Savings:** ~XXX gas per call
#### Current Implementation// Current code
#### Recommended Implementation// Optimized code
---
## Recommendations Summary
### Immediate Actions (Critical/High)
1. [Action item 1]
2. [Action item 2]
### Short-term Improvements (Medium)
1. [Action item 1]
2. [Action item 2]
### Best Practice Improvements (Low/Info)
1. [Action item 1]
2. [Action item 2]
---
## Conclusion
[Summary paragraph about overall security posture and recommendations]
---
## Disclaimer
This audit report is not investment advice. The findings represent the auditor's assessment at the time of review. Smart contracts may contain undiscovered vulnerabilities, and users should exercise their own due diligence.---
Finding Template (Quick Reference)
### [SEVERITY-##] [Descriptive Title]
**Severity:** [Critical/High/Medium/Low/Info]
**Status:** [Open/Acknowledged/Fixed]
**File:** `ContractName.sol`
**Lines:** XX-YY
#### Description
[Clear explanation of the issue]
#### Impact
[Consequences if exploited/unfixed]
#### Proof of Concept (if applicable)
[Attack vector or test case]
#### Recommendation
[Specific fix with code]---
Severity Examples
Critical
- Unrestricted minting function
- Missing access control on fund withdrawal
- Reentrancy allowing complete fund drain
- Unprotected upgrade mechanism
High
- Reentrancy with limited impact
- Price oracle manipulation vector
- Logic error causing partial fund loss
- Access control bypass under conditions
Medium
- Centralization risks
- Incomplete validation
- DoS vectors
- Front-running vulnerabilities
Low
- Missing event emissions
- Inconsistent error messages
- Minor gas inefficiencies
- Code style issues
Informational
- Best practice suggestions
- Documentation improvements
- Test coverage gaps
- Upgrade recommendations
Security Vulnerability Checklist
Complete checklist based on OWASP Smart Contract Top 10 (2025) and real-world exploits.
SC-01: Access Control Vulnerabilities [$953.2M+ in losses]
Detection Patterns
Missing Access Control:
// VULNERABLE: No access control on sensitive function
function setPrice(uint256 _price) external {
price = _price;
}
// SECURE: Proper access control
function setPrice(uint256 _price) external onlyOwner {
price = _price;
}Checklist:
- [ ] All state-changing functions have appropriate access control
- [ ]
onlyOwner/role modifiers on admin functions - [ ]
initializermodifier on initialization functions (upgradeable) - [ ] No exposed
selfdestructordelegatecall - [ ] Constructor properly sets owner/admin
- [ ] Two-step ownership transfer pattern used
- [ ] No
tx.originfor authorization (usemsg.sender)
Common Vulnerabilities:
- Unprotected
initialize()functions - Missing modifiers on mint/burn functions
- Exposed upgrade mechanisms
- Default visibility (pre-0.5.0 was public)
Recent Exploits
- zkSync (April 2025): Admin key leak in airdrop contract
- Penpie (2024): Unauthorized access in DeFi protocol
---
SC-02: Logic Errors [$63.8M+ in losses]
Detection Patterns
Flawed Business Logic:
// VULNERABLE: Rewards calculated incorrectly
function calculateReward(uint256 amount) public view returns (uint256) {
return amount * rewardRate; // Missing precision handling
}
// SECURE: Proper precision handling
function calculateReward(uint256 amount) public view returns (uint256) {
return (amount * rewardRate) / PRECISION;
}Checklist:
- [ ] Division before multiplication avoided
- [ ] Precision loss handled correctly
- [ ] Edge cases handled (zero values, max values)
- [ ] State transitions are valid
- [ ] Accounting logic verified (credits = debits)
- [ ] Percentage calculations correct (basis points)
- [ ] Fee calculations don't exceed 100%
Common Patterns:
- Rounding errors in favor of attacker
- Missing zero-address checks
- Off-by-one errors in loops
- Incorrect token decimal handling
---
SC-03: Reentrancy [$35.7M+ in losses]
Detection Patterns
Classic Reentrancy:
// VULNERABLE: State updated after external call
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success,) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount; // State update AFTER call
}
// SECURE: Checks-Effects-Interactions pattern
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // State update BEFORE call
(bool success,) = msg.sender.call{value: amount}("");
require(success);
}Types of Reentrancy: 1. Single-function: Same function called recursively 2. Cross-function: Different functions sharing state 3. Cross-contract: Multiple contracts involved 4. Read-only: View functions returning stale state during callback
Checklist:
- [ ] Checks-Effects-Interactions (CEI) pattern followed
- [ ]
nonReentrantmodifier on state-changing functions - [ ] State updated before external calls
- [ ] ERC-777 token callback risks assessed
- [ ] Cross-contract reentrancy considered
Recent Exploits
- Penpie (2024): Reentrancy in DeFi lending
- Minterest (2024): $1.5M flash loan + reentrancy
---
SC-04: Flash Loan Attack Vectors [$33.8M+ in losses]
Detection Patterns
Governance Manipulation:
// VULNERABLE: No flash loan protection in governance
function vote(uint256 proposalId) external {
uint256 votingPower = token.balanceOf(msg.sender);
proposals[proposalId].votes += votingPower;
}
// SECURE: Snapshot-based voting
function vote(uint256 proposalId) external {
uint256 votingPower = token.getPastVotes(msg.sender, proposals[proposalId].snapshotBlock);
proposals[proposalId].votes += votingPower;
}Checklist:
- [ ] Governance uses snapshot voting
- [ ] Price oracles use TWAP, not spot prices
- [ ] Collateral ratios resistant to manipulation
- [ ] Loan/borrow functions check for same-block attacks
- [ ] Time locks on sensitive operations
Common Attack Patterns:
- Borrow large amount → manipulate price → profit → repay
- Flash loan → gain voting majority → pass malicious proposal
- Manipulate TWAP oracle with large swaps
Recent Exploits
- Sonne Finance (May 2024): $20M via flash loan in Compound V2 fork
- Beanstalk (2022): $182M governance attack
---
SC-05: Input Validation [$14.6M+ in losses]
Detection Patterns
// VULNERABLE: No input validation
function transfer(address to, uint256 amount) external {
balances[msg.sender] -= amount;
balances[to] += amount;
}
// SECURE: Proper validation
function transfer(address to, uint256 amount) external {
require(to != address(0), "Invalid address");
require(amount > 0, "Amount must be positive");
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
}Checklist:
- [ ] Zero address checks on all address parameters
- [ ] Amount bounds validation
- [ ] Array length limits (DoS prevention)
- [ ] Deadline parameters not expired
- [ ] Slippage parameters reasonable
- [ ] Signature parameters validated
- [ ] Callback data validated
---
SC-06: Oracle Manipulation [$8.8M+ in losses]
Detection Patterns
// VULNERABLE: Spot price from AMM
function getPrice() public view returns (uint256) {
return uniswapPair.getReserves()[0] / uniswapPair.getReserves()[1];
}
// SECURE: TWAP or Chainlink
function getPrice() public view returns (uint256) {
(,int256 price,, uint256 updatedAt,) = chainlinkFeed.latestRoundData();
require(block.timestamp - updatedAt < MAX_STALENESS, "Stale price");
require(price > 0, "Invalid price");
return uint256(price);
}Checklist:
- [ ] No spot prices from AMMs
- [ ] TWAP implemented correctly (sufficient window)
- [ ] Price staleness checks
- [ ] Price bounds validation (circuit breakers)
- [ ] Multiple oracle sources considered
- [ ] Fallback oracle mechanism
Recent Exploits
- Moby (January 2025): Price oracle manipulation via flash loan
---
SC-07: Unchecked External Calls
Detection Patterns
// VULNERABLE: Unchecked low-level call
function transferETH(address to, uint256 amount) external {
to.call{value: amount}(""); // Return value ignored
}
// SECURE: Checked return value
function transferETH(address to, uint256 amount) external {
(bool success,) = to.call{value: amount}("");
require(success, "Transfer failed");
}Checklist:
- [ ] All
.call()return values checked - [ ] All
.transfer()success verified (or use call) - [ ] External contract calls wrapped in try-catch when appropriate
- [ ] Untrusted contracts handled carefully
- [ ] Return data validated
---
SC-08: Integer Overflow/Underflow
Version-Specific
Pre-0.8.0: Vulnerable by default
// VULNERABLE (pre-0.8.0)
uint256 balance = 100;
balance -= 200; // Underflows to huge number
// SECURE (pre-0.8.0): Use SafeMath
using SafeMath for uint256;
balance = balance.sub(200); // Reverts0.8.0+: Protected by default, but watch for:
// VULNERABLE: unchecked block bypasses protection
unchecked {
balance -= amount; // Can underflow!
}Checklist:
- [ ] Pre-0.8.0: SafeMath used for all arithmetic
- [ ] 0.8.0+:
uncheckedblocks reviewed carefully - [ ] Type casting checked (uint256 to uint128, etc.)
- [ ] Multiplication before division (precision)
Recent Exploits
- Cetus DEX (May 2025): $223M via missing overflow check
---
SC-09: Denial of Service
Detection Patterns
Unbounded Loops:
// VULNERABLE: Unbounded iteration
function distributeRewards() external {
for (uint i = 0; i < holders.length; i++) { // Can run out of gas
payable(holders[i]).transfer(rewards);
}
}
// SECURE: Pull pattern
function claimRewards() external {
uint256 reward = pendingRewards[msg.sender];
pendingRewards[msg.sender] = 0;
payable(msg.sender).transfer(reward);
}Checklist:
- [ ] No unbounded loops over dynamic arrays
- [ ] Pull-over-push pattern for payments
- [ ] Gas limits considered for external calls
- [ ] Block gas limit cannot prevent critical operations
- [ ] No dependency on external contract state for critical functions
---
SC-10: Front-Running / MEV
Detection Patterns
// VULNERABLE: No slippage protection
function swap(uint256 amountIn) external {
uint256 amountOut = router.swap(amountIn); // Can be sandwiched
}
// SECURE: Slippage protection
function swap(uint256 amountIn, uint256 minAmountOut, uint256 deadline) external {
require(block.timestamp <= deadline, "Expired");
uint256 amountOut = router.swap(amountIn);
require(amountOut >= minAmountOut, "Slippage exceeded");
}Checklist:
- [ ] Slippage parameters on swaps (0.1%-5% typical)
- [ ] Deadline parameters to prevent stale transactions
- [ ] Commit-reveal schemes for sensitive operations
- [ ] Private transaction options considered
---
Additional Security Checks
Upgradeability (if applicable)
- [ ] Storage layout preserved across upgrades
- [ ] Initializers cannot be called twice
- [ ] Implementation cannot be initialized directly
- [ ] Upgrade access properly controlled
- [ ] No storage collisions with proxy
Token Security
- [ ] ERC-20:
approverace condition handled (use increaseAllowance) - [ ] ERC-721:
safeTransferFromcallbacks considered - [ ] ERC-777: Hooks create reentrancy risk
- [ ] Fee-on-transfer tokens handled
- [ ] Rebasing tokens handled
- [ ] Token decimal assumptions verified
Signature Verification
- [ ] Replay protection (nonce or deadline)
- [ ] Chain ID included in signature
- [ ] Zero address check on ecrecover result
- [ ] EIP-712 structured data used
Randomness
- [ ] No
block.timestampfor randomness - [ ] No
block.difficulty/prevrandaofor randomness - [ ] Chainlink VRF or commit-reveal used
Storage Optimization Patterns
Guide to efficient storage layout and patterns in Solidity smart contracts.
EVM Storage Model
- Storage organized in 32-byte (256-bit) slots
- Each slot costs 20,000 gas to initialize
- Slots are addressed by uint256 keys (0, 1, 2, ...)
- Variables packed left-to-right within slots
Slot Layout Rules
Basic Types
| Type | Size | Slot Behavior |
|---|---|---|
| uint256/int256 | 32 bytes | Full slot |
| uint128/int128 | 16 bytes | Packable |
| uint64/int64 | 8 bytes | Packable |
| uint32/int32 | 4 bytes | Packable |
| uint8/int8 | 1 byte | Packable |
| bool | 1 byte | Packable |
| address | 20 bytes | Packable |
| bytes1-bytes32 | 1-32 bytes | Packable |
| bytes/string | 32 bytes* | Dynamic |
| mapping | 32 bytes | Empty slot, data at hash |
| array (dynamic) | 32 bytes | Length at slot, data at hash |
| array (fixed) | N * element | Consecutive slots |
Packing Examples
// INEFFICIENT: 4 slots used
contract BadLayout {
uint8 a; // Slot 0 (1 byte, 31 wasted)
uint256 b; // Slot 1 (full slot)
uint8 c; // Slot 2 (1 byte, 31 wasted)
address d; // Slot 3 (20 bytes, 12 wasted)
}
// OPTIMIZED: 2 slots used
contract GoodLayout {
uint8 a; // Slot 0 (1 byte)
uint8 c; // Slot 0 (1 byte, packed)
address d; // Slot 0 (20 bytes, packed - total 22/32)
uint256 b; // Slot 1 (full slot)
}Struct Packing
// INEFFICIENT: 3 slots
struct BadStruct {
uint64 timestamp; // Slot 0
address user; // Slot 1 (starts new slot)
uint128 amount; // Slot 2
}
// OPTIMIZED: 2 slots
struct GoodStruct {
address user; // Slot 0 (20 bytes)
uint64 timestamp; // Slot 0 (8 bytes, packed - 28 total)
uint128 amount; // Slot 1 (16 bytes)
}---
Storage Patterns
Pattern 1: Mapping + Struct
Common pattern for user data:
struct UserInfo {
uint128 balance; // 16 bytes
uint64 lastUpdate; // 8 bytes
uint64 nonce; // 8 bytes = 32 bytes total (1 slot)
}
mapping(address => UserInfo) public users;Pattern 2: Bitmap for Flags
Store 256 boolean flags in one slot:
uint256 private flags;
function setFlag(uint8 index) external {
flags |= (1 << index);
}
function getFlag(uint8 index) external view returns (bool) {
return (flags >> index) & 1 == 1;
}
function clearFlag(uint8 index) external {
flags &= ~(1 << index);
}Pattern 3: Packed Timestamps + Amounts
// Pack timestamp (40 bits = ~34,000 years) + amount (216 bits)
struct PackedData {
uint40 timestamp;
uint216 amount;
}
// Fits in single slotPattern 4: EnumerableSet Alternative
Instead of OpenZeppelin's EnumerableSet (expensive), consider:
// Cheaper: Mapping + Array with index tracking
mapping(address => uint256) private _index;
address[] private _values;
function add(address value) internal returns (bool) {
if (_index[value] != 0) return false;
_values.push(value);
_index[value] = _values.length; // 1-indexed
return true;
}
function remove(address value) internal returns (bool) {
uint256 valueIndex = _index[value];
if (valueIndex == 0) return false;
uint256 lastIndex = _values.length;
if (valueIndex != lastIndex) {
address lastValue = _values[lastIndex - 1];
_values[valueIndex - 1] = lastValue;
_index[lastValue] = valueIndex;
}
_values.pop();
delete _index[value];
return true;
}---
Dynamic Storage
Mappings
mapping(KeyType => ValueType) map;
// Slot of map = p
// Value location = keccak256(key . p)Key points:
- Mapping slot itself stores nothing
- Values stored at
keccak256(key, slot) - Cannot iterate mappings
- Cannot get length of mappings
Dynamic Arrays
uint256[] arr;
// Slot p stores length
// Element i at keccak256(p) + iNested Mappings
mapping(address => mapping(uint256 => uint256)) nested;
// Value at keccak256(innerKey, keccak256(outerKey, slot))---
Storage Collision Prevention (Upgradeable Contracts)
Diamond Storage Pattern
library DiamondStorage {
bytes32 constant STORAGE_POSITION = keccak256("diamond.storage.mycontract");
struct Data {
uint256 value;
mapping(address => uint256) balances;
}
function data() internal pure returns (Data storage d) {
bytes32 position = STORAGE_POSITION;
assembly {
d.slot := position
}
}
}
contract MyContract {
function getValue() external view returns (uint256) {
return DiamondStorage.data().value;
}
}ERC-7201: Namespaced Storage
// @custom:storage-location erc7201:example.main
struct MainStorage {
uint256 value;
mapping(address => uint256) balances;
}
// keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant MAIN_STORAGE_LOCATION =
0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500;
function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_LOCATION
}
}---
Storage Anti-Patterns
Anti-Pattern 1: Unnecessary State Variables
// BAD: Stored but computable
uint256 public totalValue;
uint256[] public values;
// totalValue always equals sum(values)
// GOOD: Compute on demand
function getTotalValue() public view returns (uint256 total) {
for (uint i = 0; i < values.length; i++) {
total += values[i];
}
}
// Or maintain running total only when gas is criticalAnti-Pattern 2: String Storage
// BAD: Dynamic string storage (expensive)
string public name = "My Long Token Name";
// GOOD: bytes32 or constant
bytes32 public constant NAME = "My Long Token Name";
// Or if truly dynamic, consider events for logging onlyAnti-Pattern 3: Redundant Data
// BAD: Storing derivable data
mapping(address => uint256) public deposits;
mapping(address => uint256) public withdrawals;
mapping(address => uint256) public balance; // Redundant!
// GOOD: Compute balance
function getBalance(address user) public view returns (uint256) {
return deposits[user] - withdrawals[user];
}---
Audit Checklist for Storage
- [ ] Variables ordered for optimal packing
- [ ] Structs packed efficiently
- [ ] No wasted bytes between variables
- [ ] Mappings used for O(1) lookups
- [ ] Bitmaps used for multiple boolean flags
- [ ] Constants used for fixed values
- [ ] Dynamic strings minimized
- [ ] No redundant stored data
- [ ] Upgradeable contracts use namespaced storage
- [ ] Storage layout documented for upgrades
- [ ] Array lengths bounded or iterations limited
- [ ] Delete unused storage for gas refunds
Solidity Version-Specific Considerations
Security and feature differences across Solidity versions. Critical for auditing legacy and modern contracts.
Version Overview
| Version Range | Status | Key Security Features |
|---|---|---|
| < 0.5.0 | Legacy | No default visibility, var keyword |
| 0.5.x | Legacy | Explicit visibility required |
| 0.6.x | Legacy | try/catch, immutable introduced |
| 0.7.x | Legacy | Better calldata handling |
| 0.8.0+ | Current | Built-in overflow checks |
| 0.8.4+ | Current | Custom errors |
| 0.8.20+ | Current | Paris/Shanghai EVM features |
---
Pre-0.8.0: Critical Vulnerabilities
Integer Overflow/Underflow
CRITICAL: Pre-0.8.0 has NO overflow protection.
// VULNERABLE (pre-0.8.0)
pragma solidity ^0.7.0;
contract Vulnerable {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) external {
require(balances[msg.sender] - amount >= 0); // ALWAYS TRUE for uint!
balances[msg.sender] -= amount; // Can underflow to MAX_UINT
payable(msg.sender).transfer(amount);
}
}Audit Action: Verify SafeMath used for ALL arithmetic:
// SECURE (pre-0.8.0)
pragma solidity ^0.7.0;
import "@openzeppelin/contracts/math/SafeMath.sol";
contract Secure {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function withdraw(uint256 amount) external {
balances[msg.sender] = balances[msg.sender].sub(amount); // Reverts on underflow
payable(msg.sender).transfer(amount);
}
}Checklist for Pre-0.8.0
- [ ] SafeMath used for ALL uint operations
- [ ] SignedSafeMath used for int operations
- [ ] No direct +, -, *, / on integers without SafeMath
- [ ] Type casting checked manually
---
Pre-0.5.0: Legacy Vulnerabilities
Default Visibility
CRITICAL: Functions defaulted to public before 0.5.0.
// VULNERABLE (pre-0.5.0)
pragma solidity ^0.4.24;
contract Vulnerable {
function internalLogic() { // DEFAULT PUBLIC - anyone can call!
// sensitive operation
}
}Audit Action: Check ALL function visibility declarations.
var Keyword
// VULNERABLE: var infers type
var i = 0; // Inferred as uint8, overflows at 255!
for (var i = 0; i < array.length; i++) { // Can infinite loopChecklist for Pre-0.5.0
- [ ] All functions have explicit visibility
- [ ] No
varkeyword used - [ ] Constructor uses
constructor()not function name
---
0.8.0 Breaking Changes
Arithmetic Overflow Protection
Default behavior change - arithmetic reverts on overflow.
// 0.8.0+ behavior
uint8 x = 255;
x += 1; // REVERTS with Panic(0x11)
// To bypass (use carefully!)
unchecked {
x += 1; // Wraps to 0
}Audit `unchecked` blocks carefully:
- [ ] Mathematical proof of no overflow
- [ ] Bounded loop counters only
- [ ] No user-controlled values
Type Changes
// Pre-0.8.0
address(this).balance; // OK
msg.sender.transfer(amount); // OK
// 0.8.0+
address(this).balance; // OK
msg.sender.transfer(amount); // ERROR: msg.sender is address, not address payable
payable(msg.sender).transfer(amount); // OKABI Coder v2 Default
// Pre-0.8.0: ABI coder v1 default
// 0.8.0+: ABI coder v2 default
// Affects:
// - Nested arrays
// - Structs in function signatures
// - Dynamic types in external functionsError Handling
// Pre-0.8.0: assert() uses invalid opcode (consumes all gas)
// 0.8.0+: assert() uses revert (returns remaining gas)
// Panic codes:
// 0x01: assert(false)
// 0x11: arithmetic overflow/underflow
// 0x12: division by zero
// 0x21: invalid enum conversion
// 0x22: storage array out of bounds
// 0x31: pop() on empty array
// 0x32: array index out of bounds
// 0x41: memory allocation failure
// 0x51: internal function pointer error---
0.8.4+: Custom Errors
// Old style (expensive)
require(balance >= amount, "Insufficient balance");
// New style (cheaper)
error InsufficientBalance(uint256 available, uint256 required);
function withdraw(uint256 amount) external {
if (balance < amount) {
revert InsufficientBalance(balance, amount);
}
// ...
}Audit Action: Recommend upgrade for gas savings (~50 gas per revert).
---
0.8.18+: Shanghai/Paris Features
PUSH0 Opcode (0.8.20+)
New opcode for pushing zero to stack. Enables gas savings.
Note: May not work on all chains (check L2 compatibility).
// Compiler setting for backwards compatibility
settings: {
evmVersion: "paris" // or "london" for older chains
}Block.prevrandao (0.8.18+)
// Deprecated (returns prevrandao post-merge)
block.difficulty
// New (post-merge)
block.prevrandaoNEITHER is suitable for secure randomness - use Chainlink VRF.
---
0.8.24+: Cancun Features
Transient Storage (EIP-1153)
// New opcodes: TSTORE, TLOAD
// Storage that clears after transaction
assembly {
tstore(0, 1) // Transient store
let val := tload(0) // Transient load
}Use cases: Reentrancy locks, callback context.
---
Version Migration Checklist
Migrating from Pre-0.8.0 to 0.8.0+
- [ ] Remove SafeMath imports and usage
- [ ] Convert
addresstoaddress payablewhere needed - [ ] Update constructor syntax if needed
- [ ] Review all arithmetic for intended behavior
- [ ] Add
uncheckedblocks only where proven safe - [ ] Test all arithmetic edge cases
- [ ] Verify ABI compatibility with integrations
Migrating from 0.8.x to Latest
- [ ] Enable custom errors for gas savings
- [ ] Update
block.difficultytoblock.prevrandao - [ ] Consider EVM version for deployment chain
- [ ] Review optimizer settings
- [ ] Check L2 opcode compatibility
---
Chain-Specific Considerations
| Chain | Recommended EVM | Notes |
|---|---|---|
| Ethereum Mainnet | prague (0.8.30+) | Full feature support |
| Arbitrum | cancun | Check blob support |
| Optimism | cancun | Check blob support |
| Base | cancun | Check blob support |
| Polygon | paris | Some newer features limited |
| BSC | paris | Conservative version |
---
Audit Summary by Version
Pre-0.5.0 Audit Focus: 1. Function visibility (critical) 2. Constructor naming 3. SafeMath usage 4. var keyword absence
0.5.x - 0.7.x Audit Focus: 1. SafeMath on all arithmetic 2. Proper type casting 3. ABI coder considerations
0.8.0+ Audit Focus: 1. unchecked block safety 2. Custom error usage (0.8.4+) 3. Address payable conversions 4. EVM version compatibility
Latest (0.8.20+) Audit Focus: 1. Chain compatibility 2. New opcode usage 3. Transient storage (if used) 4. Optimizer settings