How I Saved 20,000 Gas Per Transaction by Reordering One Line in Solidity

Published: (March 1, 2026 at 01:58 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Smart‑Wallet Storage Optimization – A Quick Code Review

While building Fishnet – an AI‑agent transaction‑security proxy – I ran a self‑imposed code review and uncovered a subtle optimization that every Solidity developer should know about: re‑ordering state variables can save ~20 k gas per transaction.


Original State Layout

address public owner;          // 20 bytes → Slot 0
address public fishnetSigner; // 20 bytes → Slot 1
mapping(uint256 => bool) public usedNonces; // Slot 2
bool public paused;           // 1 byte  → Slot 3  ← wasting 31 bytes
  • bool paused occupies its own 32‑byte slot, wasting 31 bytes and incurring an extra SLOAD/SSTORE on every pause check.

The EVM works with 32‑byte words. The Solidity compiler lays out storage top‑to‑bottom in declaration order:

SlotLayout
0[owner ─────── 20 B][── 12 B empty]
1[fishnetSigner ─────── 20 B][── 12 B empty]
2[usedNonces mapping hash ─────────────────────── 32 B]
3[paused ─ 1 B][─────── 31 B empty]

The compiler does not reorder variables for you. If a variable cannot fit in the remaining space of the current slot, it starts a new one.


Optimized Layout

Move paused right after owner:

address public owner;          // 20 bytes ─┐
bool   public paused;          // 1 byte   ──┘ → Slot 0 (21/32 B)
address public fishnetSigner;  // 20 bytes → Slot 1
mapping(uint256 => bool) public usedNonces; // Slot 2

Resulting storage:

SlotLayout
0[owner ─────── 20 B][paused ─ 1 B][── 11 B empty]
1[fishnetSigner ─────── 20 B][── 12 B empty]
2[usedNonces mapping hash ─────────────────────── 32 B]

Benefit: 4 slots → 3 slots. One fewer storage slot is touched at runtime.


Gas Savings in Practice

OperationBefore (separate slots)After (packed)Approx. Savings
Cold SLOAD (first read in tx)2,100 gas × 2 slots2,100 gas × 1 slot2,100 gas
Cold SSTORE (pause/unpause)~20,000 gas0 (slot already warm from owner)~20,000 gas
whenNotPaused modifier per callReads its own slot + owner’s slot (often warm)Same reads, but now both are in the same slotUp to 2,000 gas saved

The biggest win is eliminating the cold SSTORE. Writing to a storage slot that hasn’t been accessed in the current transaction costs ~20 k gas. If owner has already been read (which is typical), the slot containing paused is now warm, and a warm SSTORE costs only ~2.9 k gas.


Verifying Layout with Foundry

forge inspect YourContract storage-layout

The command prints each variable’s slot, offset, and byte size.

Example output:

NameTypeSlotOffsetBytes
owneraddress0020
pausedbool0201
fishnetSigneraddress1020
usedNoncesmapping(uint256 => bool)2032
  • When Offset > 0, packing is already happening.
  • When a small type has Offset = 0 and its own slot, that’s a packing opportunity.

Other Findings from the Review

IssueBeforeAfter
Permit value mismatchNo check that permit.value == msg.value.require(permit.value == msg.value, InsufficientValue());
Stale DOMAIN_SEPARATORCached at deployment, never recomputed on a fork.Re‑compute when block.chainid != _CACHED_CHAIN_ID.
Unnecessary hashkeccak256 computed before validating signature length.Validate length first, then hash.
String revert messagesrequire(..., "Not authorized") (stores string in bytecode).Use custom errors: error Unauthorized(); if (msg.sender != owner) revert Unauthorized();
Dead codeLeftover console.log imports & unused test helpers.Removed – reduces deployment bytecode size.

Takeaway

A code review isn’t just about finding bugs; it’s about understanding the machine your code runs on.

  • The EVM’s 32‑byte word size means every storage slot costs real money.
  • Knowing how the compiler packs storage can be the difference between a contract that costs users $2 vs $5 per transaction.

Action items:

  1. Run forge inspect YourContract storage-layout.
  2. Look for variables that could be packed together (combined size ≤ 32 bytes) but are in separate slots.
  3. Re‑order or group small types (bool, uint8, uint16, address) accordingly.

You might be surprised what you find.

This optimization came out of building Fishnet – an open‑source security proxy for AI agents.

## Rarity Proxy for AI Agent Transactions on Ethereum

Rarity proxy for AI agent transactions on Ethereum. If you're working on **AI × Web3** infrastructure, check it out.
0 views
Back to Blog

Related posts

Read more »

The Last Dance with the past🕺

Introduction Hello dev.to community! A week ago I posted my first article introducing myself and explaining that I left web development to focus on cryptograph...