Smart Contracts: Immutability, Storage Slots, and the ABI
It's not 'Code is Law.' It's 'Code is Compiled Bytecode.' Deep dive into EVM storage layout, Function Selectors, and ABI encoding.
🎯 What You'll Learn
- Visualizing EVM Storage Layout (Slots)
- Decoding Function Selectors (0xa9059cbb)
- Analyzing Immutability Risks (Proxy patterns)
- Tracing the Checks-Effects-Interactions pattern
- Reading storage directly with `cast`
Introduction
A “Smart Contract” is a misleading name. It is neither smart nor a contract. It is Immutable Bytecode living at a specific address on the Global State Machine.
When you “deploy” a contract, you are burning code into the blockchain’s history forever. When you “interact” with it (call a function), you are sending a specific hexadecimal payload that triggers a jump in the program counter.
In this lesson, we will stop treating contracts like magic scripts and start treating them like EVM Bytecode.
The Physics: EVM Storage Slots
Variables in Solidity are not stored in “memory” like RAM. They are stored in 256-bit Storage Slots on the blockchain’s hard drive (State Trie).
This storage is expensive — an SSTORE operation costs 20,000 gas for a new slot. At high gas prices that can be significant. Packed appropriately, you can cut storage operations dramatically.
Visualization: The Slot Map
contract StorageExample {
uint256 public val1; // Slot 0 (32 bytes)
uint128 public val2; // Slot 1 (16 bytes) \__ Packed into Slot 1
uint128 public val3; // Slot 1 (16 bytes) /
address public owner; // Slot 2 (20 bytes)
}
```text
### Reading Slots Directly
You don't need a "getter function" to read private variables. You just need to query the slot.
```bash
# Using Foundry's cast tool (or web3.eth.getStorageAt)
cast storage 0xContractAddress 0
> 0x0000000000000000000000000000000000000000000000000000000000000abc (Value of val1)
```bash
**Lesson:** Nothing is private on the blockchain. `private` only means "other contracts can't call it." Humans can always read it.
---
## The Interface: ABI & Function Selectors
How does the EVM know which function to run?
It looks at the **first 4 bytes** of your transaction data.
### The Function Selector
$$ Selector = \text{keccak256("transfer(address,uint256)")}[0:4] $$
* `transfer(address,uint256)` -> `0xa9059cbb`
* `approve(address,uint256)` -> `0x095ea7b3`
### The Payload (ABI Encoding)
The rest of the transaction data is the arguments, padded to 32 bytes (256 bits).
**Example Transaction Data:**
`0xa9059cbb` (Function: transfer)
`000000000000000000000000abc123...` (Argument 1: Recipient Address)
`000000000000000000000000000000...01` (Argument 2: Amount = 1 wei)
---
## Security Pattern: Checks-Effects-Interactions
The DAO Hack happened because this pattern was violated.
**Reentrancy** occurs when you surrender control flow to an external contract *before* updating your own internal state.
### ❌ The Vulnerable Pattern
```solidity
function withdraw() public {
uint amount = balances[msg.sender];
// 1. Interaction (External Call)
(bool success, ) = msg.sender.call{value: amount}("");
// 2. Effect (State Update)
balances[msg.sender] = 0;
}
```text
**The Hack:** The `msg.sender` receives the ETH, and their `fallback()` function instantly calls `withdraw()` *again*. Since `balances` hasn't been set to 0 yet, they drain the vault.
### ✅ The Secure Pattern
```solidity
function withdraw() public {
// 1. Checks
uint amount = balances[msg.sender];
require(amount > 0);
// 2. Effects (Optimistic Update)
balances[msg.sender] = 0;
// 3. Interactions
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
Practice Exercises
Exercise 1: Slot Packing (Intermediate)
Scenario: You have 3 variables: uint64 a, uint256 b, uint64 c.
Task: Order them to minimize storage usage (Gas Optimization).
(Hint: Slots are 256 bits wide).
Exercise 2: Manual Decoding (Advanced)
Scenario: Tx Data: 0x70a08231000000000000000000000000...
Task: Identify the function. (Hint: It’s a standard ERC-20 read function).
Exercise 3: Proxy Storage Collision (Expert)
Scenario: You are using an Upgradable Proxy. The Logic Contract creates a variable uint x at Slot 0. The Proxy Contract also has a variable address impl at Slot 0.
Task: Explain what happens when you write to x. (This is a catastrophic bug).
Knowledge Check
- What is a Function Selector?
- Why is
privatevisibility a lie? - Why should you update
balancesbefore sending ETH? - How many bytes is a storage slot?
- What happens if you deploy code with a bug?
Answers
- The first 4 bytes of the Keccak hash of the function signature. It tells the EVM which code to run.
- State is public.
privateonly restricts internal solidity calls. Usecast storageto read anything. - To prevent Reentrancy. If you update after, an attacker can re-call the function before the balance is zeroed.
- 32 Bytes (256 bits).
- It is there forever. You cannot patch it. You must deploy a new contract and migrate all state (impossible) or users (hard).
Summary
- Storage: Is permanent and expensive. Pack variables.
- ABI: Is the language of the EVM. It’s just bytes.
- Security: Assume every external call is malicious.
- Immutability: Measure twice, cut once. Deployment is final.
Want to go deeper?
Weekly infrastructure insights for engineers who build trading systems.
Free forever. Unsubscribe anytime.
You're in. Check your inbox.
Questions about this lesson? Working on related infrastructure?
Let's discuss