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
}
- 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
- uint256 public number
uint256 = 32 bytes
(can't be fit in slot 0)
- Stored in
slot 1
(entire slot used)
- 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"
- 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.
data.pop() -> data.length == 2^256-1
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:
- Use Solidity ≥ 0.8.0
- Avoid
exposing publi
c write access to dynamic arrays
- Never allow
.pop() or manual index writes from untrusted users
- Use
SafeMath or OpenZeppelin's defensive wrappers
for older versions
Case Studies