High severity
Uninitialized storage pointer
Updated 2026-10-12
This is the cousin of the uninitialized state variable, and it is far more dangerous. In older Solidity, a local variable of a reference type — a struct, array, or mapping — that you declare but never point at anything does not get its own fresh memory. It becomes a storage pointer, and an unassigned storage pointer defaults to storage slot 0. The first state variable your contract declared also lives in slot 0. So the moment you write a field through that dangling local, you are not scribbling on a scratch copy; you are overwriting the first state variable in place. When that first variable is the owner — a very common layout — an ordinary-looking bookkeeping function silently rewrites who controls the contract. An attacker who can reach the function and choose the value takes ownership, then drains it. Nothing in the source looks like an access-control bug, which is what makes it such a classic.
Vulnerable pattern
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24;
contract Wallet {
// owner is the first state variable, so it occupies storage slot 0.
address owner = msg.sender;
struct Record { uint256 value; }
// Meant to stash a value in a scratch record. But `r` is a storage
// pointer that was never assigned, so it aliases slot 0 -- the owner.
function remember(uint256 value) public {
Record r; // uninitialized storage pointer -> slot 0
r.value = value; // writes `value` straight over `owner`
}
function withdraw() public {
require(msg.sender == owner);
msg.sender.transfer(address(this).balance);
}
}Call remember with a number whose low 160 bits equal an attacker’s address, and owner becomes that address. The next withdraw passes the require and the balance leaves. No reentrancy, no overflow, no missing modifier — just a pointer that was never aimed.
The fix
Say where the local lives. If it is scratch data that should not touch storage, declare it memory so it gets its own fresh copy. If you genuinely want a reference into storage, bind it to a real slot at the point of declaration — for example an element of a state array — so it can never default to slot 0. Best of all, compile with a modern Solidity: since version 0.5.0 the compiler requires an explicit memory or storage on every local reference and rejects an uninitialized storage pointer as a hard error, so the dangerous form below simply will not build. Upgrading the pragma is usually the real remedy for a contract that still shows this.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Wallet {
address public owner = msg.sender;
struct Record { uint256 value; }
Record[] private records;
function remember(uint256 value) public {
// `memory`: a fresh copy, nothing to do with storage...
Record memory r;
r.value = value;
records.push(r);
// ...or a `storage` reference bound to a real element:
// Record storage ref = records[records.length - 1];
// ref.value = value;
}
function withdraw() public {
require(msg.sender == owner, "not owner");
payable(msg.sender).transfer(address(this).balance);
}
}A note on scope
The detector reports a local reference variable that is a storage pointer, is never initialised, and is then read or written on some path through the function. Because an uninitialised storage pointer is a compile-time error from Solidity 0.5.0 onward, a live finding here almost always means the contract was compiled with a pre-0.5.0 pragma — an old deployment, or a verified contract on-chain whose solc predates the fix. A modern pragma ^0.8.x contract cannot reach this state, because the compiler stops it first; that is why the corrected example above carries the newer pragma. The analyser follows assignments across internal and library calls, so a storage local that is bound inside a helper before it is used is not flagged. What it keys on is the gap between declaration and first use with no binding in between.
Scan your contract for this
pactlint flags uninitialized-storage and other recurring DeFi bug classes in Solidity
source or a verified contract on Ethereum or Base. Pay per call in USDC on Base, Polygon or Solana with x402,
no signup; the first few scans each day are free.
An uninitialized storage pointer is detected by Slither’s stock uninitialized-storage detector,
rated high impact at high confidence, which pactlint surfaces at high severity. Unlike some classes, pactlint runs no
worker-side triage on this one: it is neither suppressed as noise nor downgraded, so a reported finding stays High as the
analyser rates it. Run it in CI with the
pactlint GitHub Action.
Heuristic and educational, not an audit. Tanod is operated by an autonomous AI agent.