Static Analysis of Smart Contracts (Slither, Mythril)

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Static Analysis of Smart Contracts (Slither, Mythril)
Medium
~1 business day
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Gas Optimization of Smart Contracts

Gas is not an abstraction, it is money. At 30 gwei gas price and ETH at $3000, one transaction with 200,000 gas costs $18. Multiply by thousands of users daily — difference between optimized and unoptimized contract measures in hundreds of thousands dollars per year. On L2 gas is cheaper, but calldata still expensive and optimization remains important.

Gas optimization is not "make code prettier." It is knowledge of EVM opcodes and their costs, data storage patterns, and ability to read Foundry gas reports. Let's cover key techniques.

Storage: Main Expense Source

SSTORE and SLOAD Cost

Writing to storage (SSTORE) — one of most expensive opcodes: 20,000 gas for write to new slot, 5,000 gas for change to existing (cold), 100 gas for repeated change in same transaction (warm, EIP-2929).

Reading (SLOAD) — 2,100 gas cold, 100 gas warm. If reading same storage slot multiple times in function — cache in memory variable:

// Bad: two SLOADs
function bad() external view returns (uint256) {
    return balances[msg.sender] + balances[msg.sender] / 100;
}

// Good: one SLOAD
function good() external view returns (uint256) {
    uint256 balance = balances[msg.sender]; // one SLOAD
    return balance + balance / 100;
}

Storage Packing

EVM slot — 32 bytes. If you have multiple variables smaller than 32 bytes — Solidity will pack them into one slot if declared nearby. This reduces SLOAD/SSTORE count.

// Bad: three separate slots
uint256 a; // slot 0
uint128 b; // slot 1 (inefficient — takes whole slot)
uint128 c; // slot 2 (inefficient)

// Good: b and c packed into one slot
uint256 a; // slot 0
uint128 b; // slot 1 (first 16 bytes)
uint128 c; // slot 1 (next 16 bytes)

For struct this is especially important: field order inside struct affects slot count. Group small types together.

Mapping vs Array

Mapping cheaper for arbitrary access: O(1) and one SLOAD. Array with search — O(N) and N SLOADs. If you need "is X in list" — use mapping(address => bool), not array.

Iterating mapping on-chain impossible natively (no way to get all keys). If iteration needed — EnumerableSet from OpenZeppelin (stores both: mapping for O(1) lookup and array for iteration).

Calldata and Functions

Calldata vs Memory

Function parameters marked calldata not copied to memory — read directly from calldata. For arrays and strings this significant saving:

// memory: copies whole array — expensive
function processMemory(uint256[] memory data) external { ... }

// calldata: no copying — cheaper
function processCalldata(uint256[] calldata data) external { ... }

Difference grows with data size. For large array — savings hundreds of thousands gas.

Custom Errors vs Require Strings

Before Solidity 0.8.4 errors passed as string, encoded in calldata and stored in bytecode. Custom errors — cheaper both in deploy and revert:

// Old way: expensive
require(amount > 0, "Amount must be positive");

// Custom error: cheaper
error InvalidAmount(uint256 amount);
if (amount == 0) revert InvalidAmount(amount);

Custom error saves ~200-500 gas per revert, and reduces bytecode size (fewer string literals).

Short Functions and Inlining

Each internal function call — additional overhead (JUMP opcodes, stack management). Solidity compiler with --via-ir (Yul IR) better optimizes inlining small functions. Enabling viaIR: true in foundry.toml/hardhat.config can reduce gas 5-15% without code changes.

Optimization Patterns

Packed Structs for Batch Operations

Processing array of objects, data structure affects storage slots per item:

Structure Slots per item Gas per item
Three uint256 fields 3 slots ~60,000 gas write
uint128 + uint64 + uint64 1 slot ~20,000 gas write
Bitmap flags 1 slot / 256 items ~78 gas per flag

Bitmap for boolean flags: uint256 flags stores 256 flags in one slot. Operation flags |= (1 << bitIndex) — 100 gas instead of 20,000 for separate mapping(uint => bool).

Lazy Initialization

Don't initialize variables to default value — that's gas waste:

// Unnecessary SSTORE with zero (Solidity does this)
uint256 public counter = 0; // not needed

// Just
uint256 public counter;

Unchecked Arithmetic

Solidity 0.8+ added overflow/underflow checks to each arithmetic operation (+100-200 gas). If you're sure overflow impossible — use unchecked:

// Standard loop with checked arithmetic
for (uint256 i = 0; i < arr.length; i++) { ... }

// Optimized with unchecked increment
for (uint256 i = 0; i < arr.length; ) {
    // ... logic
    unchecked { ++i; } // prefix ++ also cheaper than postfix
}

Savings on typical 100-element loop — 5,000-10,000 gas.

Measurement Tools

Foundry gas snapshotsforge snapshot creates .gas-snapshot file with gas usage per test. forge snapshot --diff shows change after fixes. Essential for iterative optimization.

forge test --gas-report — table with average/min/max gas per function.

ETH Gas Station / Tenderly — transaction simulation with opcode breakdown. Useful to understand where exactly gas spent.

Audit and Optimization Process

  1. Baseline measurement: forge snapshot before changes
  2. Profiling: identify most expensive functions
  3. Storage analysis: check struct packing, unnecessary SLOADs
  4. Apply optimizations: by priority (storage → calldata → arithmetic)
  5. Verification: forge snapshot --diff, verify functionality not broken

Typical result: 20-40% gas reduction for unoptimized contract, 10-20% for already "reasonable" code. For DeFi with high transaction load this direct user savings.

Timeline: audit + report + optimization implementation — 2-4 weeks depending on codebase size.