The EVM, storage layout, function dispatch, reentrancy, proxy patterns, and everything in between — how smart contracts actually work
A smart contract is a program stored on a blockchain that executes automatically when predetermined conditions are met. It has no owner who can override it, no server it runs on that can be taken offline, and no administrator who can selectively apply its rules. Once deployed, it runs exactly as written — for better or worse.
The term was coined by computer scientist Nick Szabo in 1994, who described a smart contract as "a set of promises, specified in digital form, including protocols within which the parties perform on these promises." Szabo's insight was that traditional contracts require trust in institutions — courts, lawyers, banks — to enforce them. A digital contract with self-executing logic could eliminate that dependency.
Ethereum, launched in 2015, was the first general-purpose smart contract platform. Where Bitcoin's scripting language is deliberately limited (it can express conditions on who can spend funds, but not arbitrary programs), Ethereum introduced theEthereum Virtual Machine (EVM) — a sandboxed, deterministic computing environment that runs the same way on every node in the network. Any computation that can be expressed in Solidity or Vyper can run on the EVM, and every Ethereum node will arrive at exactly the same result.
Today, smart contracts underpin the entire DeFi ecosystem: lending protocols hold billions in collateral and calculate interest rates algorithmically; automated market makers set prices for trillions in annual trading volume; stablecoin systems mint and burn currency based on collateral ratios; governance systems execute protocol changes when token holders vote. Every one of these systems is a collection of smart contracts running autonomously on a public blockchain.
Despite the name, smart contracts have no intelligence — they execute deterministic logic, not judgement. And while they are sometimes used for legal agreements, most are simply programs that manage digital assets. A more accurate term is "self-executing code with on-chain state." The name has stuck because it captures what makes them novel: the rules are enforced by the program, not by the parties or a third party.
The Ethereum Virtual Machine is a stack-based, sandboxed virtual machine that executes smart contract bytecode. Understanding the EVM is essential to understanding what smart contracts actually are — the Solidity code you write is not what runs on-chain. It is compiled to EVM bytecode, and the bytecode is what the network executes.
The EVM is a stack machine: all operations push to and pop from a 256-bit-wide stack (maximum depth of 1,024 items). Arithmetic, comparisons, and most operations work on the top of the stack. This is different from register-based architectures (like x86 or ARM) — there are no named registers, only the stack and memory regions.
The EVM has four distinct data regions that smart contract code can read from and write to:
| Region | Persistence | Cost | Access |
|---|---|---|---|
| Stack | Current call only | Very cheap | Push/pop — 256-bit words, 1,024 max depth |
| Memory | Current call only | Cheap (quadratic beyond 724 bytes) | Byte-addressable — MLOAD/MSTORE opcodes |
| Storage | Permanent (on-chain) | Expensive — 20,000 gas (cold write) | 32-byte slot addressed by 256-bit key — SLOAD/SSTORE |
| Calldata | Current call, read-only | Cheapest — 4 gas/zero byte, 16 gas/non-zero | Input data from the transaction or external call |
Gas is the unit of EVM computation. Every opcode has a fixed gas cost; complex operations (storage writes, external calls, hash computations) cost more than simple arithmetic. Gas serves two purposes: it compensates validators for computation, and it prevents infinite loops — a contract that loops forever simply runs out of gas and reverts, rather than halting the entire network.
When the EVM executes a transaction, it maintains a program counterpointing to the current instruction in the bytecode, a gas countertracking remaining gas, and the stack and memory described above. Each opcode advances the program counter and deducts gas. If gas hits zero before execution completes, the transaction reverts — all state changes are rolled back, but the gas is not refunded (the validator did the work).
Because the EVM became dominant early, most competing L1s and all major L2s implement EVM compatibility — their execution environments behave identically to the Ethereum EVM. This means the same Solidity contract can be deployed on Ethereum, Arbitrum, Optimism, Polygon, Avalanche, BNB Chain, and dozens of others with little or no modification. The EVM's ubiquity has made it the de facto standard for smart contract execution, much as x86 became the dominant CPU architecture for personal computers.
A Solidity smart contract is organised into state variables, events, modifiers, functions, and constructor logic. Each of these serves a distinct role in defining what a contract stores, what it can do, and how it communicates with the outside world.
State variables are the contract's persistent data — the information stored on-chain and visible to anyone. They live in the contract's storage slots, persist between transactions, and are the most expensive data to read and write. Common types include uint256 (unsigned integer),address (20-byte Ethereum address),bool, bytes32,mapping(keyType => valueType) (hash map), and address[] (dynamic array). State variables are laid out in sequential storage slots (32 bytes each), with the compiler packing multiple smaller variables into a single slot where possible.
Events are structured log entries emitted during execution and stored in the transaction receipt — not in contract storage. They are far cheaper to emit than writing to storage (375 gas + 8 gas/byte for data), and are indexed by the Ethereum node for efficient lookup. Events are how dapps know what happened: a token transfer emits a Transfer(address indexed from, address indexed to, uint256 value)event; a governance vote emits a VoteCast event. Indexed parameters (up to 3) are stored as topics and can be filtered efficiently; non-indexed parameters go into the data field.
Modifiers are reusable conditions that wrap function bodies. The most common is onlyOwner — a modifier that checksmsg.sender == owner and reverts if not. Modifiers keep access control logic DRY and clearly separated from business logic. The_ placeholder marks where the function body is inserted. Modifiers run before (and optionally after) the function body, making them suitable for reentrancy guards and access checks.
The constructor runs exactly once — when the contract is deployed. It initialises state variables, sets ownership, and performs any one-time setup. Constructor arguments are encoded in the deployment transaction's calldata. After the constructor runs, the contract's bytecode is written to the blockchain at its address, and subsequent calls execute the runtime bytecode (not the constructor).
| Component | Stored Where | Gas Cost | Purpose |
|---|---|---|---|
| State variables | Contract storage (on-chain, permanent) | 20,000 gas (cold write) | Persistent contract data |
| Events | Transaction receipt (prunable logs) | 375 + 8/byte | Off-chain notification & indexing |
| Modifiers | Bytecode only | Inline — no extra overhead | Access control & pre/post conditions |
| Constructor | Bytecode (one-time execution) | Paid at deploy time | Initialisation logic |
| Functions | Bytecode | Depends on operations | External and internal behaviour |
| Immutables | Bytecode (inlined at deploy) | Same as constant | Set once at deploy, cheap to read |
| Constants | Bytecode (compile-time literal) | ~3 gas (PUSH opcode) | Fixed values — no storage slot used |
Functions are the interface through which users, other contracts, and off-chain code interact with a smart contract. Solidity's function system has two orthogonal dimensions: visibility (who can call it) and state mutability(what it can do to storage). Getting these right is the first line of contract security.
Visibility controls which callers can invoke a function:
public functions can be called by anyone — external accounts, other contracts, or the contract itself. They generate an ABI entry and can be called via transactions or internal calls.
external functions can only be called from outside the contract (not by the contract itself using internal call syntax). They are slightly cheaper than public for functions with large calldata arguments because the EVM can read arguments directly from calldata rather than copying them to memory.
internal functions can only be called from within the contract or contracts that inherit from it. They do not appear in the ABI and cannot be called by external accounts.
private functions can only be called from within the contract itself — not by inheriting contracts. Note that "private" means inaccessible to Solidity callers, not hidden from the blockchain — private state variables are still readable by anyone inspecting the raw storage.
State mutability controls what a function can do to the blockchain state:
Functions with no mutability keyword can read and write storage. They must be called via a transaction (which costs gas) and can change contract state.
view functions promise not to modify state. They can read storage and return values. When called off-chain (by a node, not a transaction), they execute locally and cost no gas — this is how most dapp data queries work.
pure functions cannot read or modify state. They are deterministic computations on their inputs alone — mathematical functions, encoding helpers, hash utilities. They are the cheapest to call and the easiest to reason about.
Function dispatch works via the function selector: the first 4 bytes of the Keccak-256 hash of the function signature (e.g. transfer(address,uint256) → 0xa9059cbb). When a transaction arrives, the EVM checks the first 4 bytes of calldata against the contract's dispatch table. If no match is found, the fallbackor receive function is called (if defined), or the call reverts. Four-byte selectors can collide — two different function signatures can produce the same selector — which is a known attack vector in proxy contracts.
Marking a state variable private prevents other Solidity contracts from reading it through the ABI — but the data is still written to the blockchain and readable by anyone who queries the storage slot directly usingeth_getStorageAt. Several protocols have made the mistake of storing sensitive values (API keys, secret parameters) in "private" storage, only to have them extracted. True private data must be stored off-chain or encrypted before storage.
Smart contract storage is organised as a key-value map of 2²⁵⁶ slots, each 32 bytes wide. Understanding storage layout is critical for writing gas-efficient contracts, building upgradeable proxy systems, and auditing contracts for storage collision vulnerabilities.
State variables are assigned sequential storage slots starting from slot 0, in the order they are declared. A uint256 takes a full 32-byte slot. Smaller types — uint128, bool,address (20 bytes) — can be packed into a single slot if they fit consecutively in the declaration order. This is called storage packingand is a key gas optimisation: a single SLOAD reads 32 bytes, so accessing two packed variables in the same slot costs one SLOADrather than two.
Mappings do not occupy their declared slot in the normal sense. The slot number is used as a seed: the value at key k is stored at slot keccak256(k || p), where p is the mapping's declared slot position. This spreads mapping entries across the storage space, preventing collisions with other variables.
Dynamic arrays store their length at the declared slot. The array elements start at slot keccak256(p) and continue sequentially. This means element i is at slotkeccak256(p) + i (for 32-byte elements).
| Type | Storage Layout | Gas to Read | Gas to Write (cold) |
|---|---|---|---|
| uint256 | 1 full slot | 2,100 (cold SLOAD) | 20,000 (SSTORE zero→nonzero) |
| address | 20 bytes, packable with adjacent vars | 2,100 | 20,000 |
| bool | 1 byte, packable | 2,100 | 20,000 |
| bytes32 | 1 full slot | 2,100 | 20,000 |
| mapping(k=>v) | Values at keccak256(k || slot) | 2,100 per key | 20,000 per key (first write) |
| address[] | Length at slot; elements at keccak256(slot)+i | 2,100 per element | 20,000 per element |
| string/bytes | ≤31 bytes inline; longer in separate slot | 2,100 | 20,000+ |
Storage layout becomes critical in upgradeable proxy contracts. When a proxy delegates calls to an implementation contract, both share the same storage space. If the implementation's storage layout doesn't match the proxy's, variables will read from and write to the wrong slots — a class of bug that has caused significant losses. The OpenZeppelin Upgrades plugin enforces storage layout compatibility checks. The ERC-7201 namespaced storage pattern avoids this entirely by using a fixed, pseudo-random storage slot derived from a string hash as the base for all storage, rather than sequential slots.
Since EIP-2929 (Berlin hard fork, April 2021), storage accesses are priced differently depending on whether the slot has been accessed in the current transaction. The first access to a storage slot costs 2,100 gas (cold); subsequent accesses in the same transaction cost 100 gas (warm). This was introduced to prevent DoS attacks via cheap cold reads. In practice, it means that contracts with loops over many storage slots pay 2,100 gas on the first iteration and 100 on subsequent ones — a significant optimisation opportunity by caching frequently-read storage values in memory at the start of a function.
The Application Binary Interface (ABI) is the specification for how to call a smart contract's functions and interpret its return values and events. It is the bridge between off-chain code (JavaScript, Python, a wallet UI) and on-chain bytecode. Without the ABI, a contract's bytecode is opaque — you can send it data, but you have no way of knowing how to encode the inputs or decode the outputs.
The ABI is a JSON document (usually generated by the Solidity compiler alongside the bytecode) that lists every public and external function with its name, parameter types, return types, and whether it is a view, payable, or event. Libraries likeethers.js, viem, andweb3.py use the ABI to encode function calls into hex calldata and decode the raw bytes returned by the EVM into typed values.
ABI encoding follows a strict specification: values are padded to 32-byte words, dynamic types (strings, bytes, arrays) are encoded with an offset pointer followed by the data. The function selector (first 4 bytes) identifies which function to call; the remaining bytes are the ABI-encoded arguments. This encoding is deterministic — the same call always produces the same bytes — which is important for signature verification schemes like EIP-712.
EIP-712 typed structured data extends ABI encoding to support human-readable signatures in wallets. Rather than signing an opaque hex string, the user sees "Transfer 100 USDC to 0x1234…" in their wallet. The typed data is ABI-encoded with a domain separator (preventing cross-contract replay) and the resulting hash is signed with ECDSA. This pattern is used for gasless approvals (ERC-2612 permit), order books (Seaport, 0x), and any system where users sign off-chain messages with on-chain effects.
| ABI Type | Solidity Type | Encoding | Notes |
|---|---|---|---|
| uint256 | uint256 | 32 bytes, big-endian | Also covers uint8–uint248 (zero-padded to 32) |
| address | address | 32 bytes, left-padded with zeros | Only 20 bytes are significant |
| bool | bool | 32 bytes — 0 or 1 | 1 byte of data in 32-byte word |
| bytes32 | bytes32 | 32 bytes, right-padded | Fixed-size; dynamic bytes uses offset encoding |
| string | string | Offset + length + data | Dynamic type — uses pointer to data location |
| tuple | struct | Concatenated encoded fields | Structs become tuples in the ABI |
| uint256[] | uint256[] | Offset + length + elements | Dynamic array — uses pointer encoding |
Solidity provides built-in global variables that give contracts access to information about the current transaction, block, and caller. These are the primary inputs a contract has about the world — understanding them is essential for writing correct access control, payment logic, and time-dependent behaviour.
| Variable | Type | Meaning | Common Use |
|---|---|---|---|
| msg.sender | address | Address of the immediate caller (EOA or contract) | Access control — onlyOwner checks, ERC-20 approvals |
| msg.value | uint256 | ETH sent with the call (in wei) | Payable functions — checking payment amount |
| msg.data | bytes | Full calldata of the transaction | Low-level dispatch, proxy contracts |
| msg.sig | bytes4 | First 4 bytes of calldata (function selector) | Dispatcher logic |
| block.timestamp | uint256 | Current block timestamp (seconds since epoch) | Time locks, vesting schedules — manipulable by ±12s |
| block.number | uint256 | Current block height | Relative time — more manipulation-resistant than timestamp |
| block.chainid | uint256 | EIP-155 chain ID | Preventing replay attacks across chains |
| tx.origin | address | Original EOA that initiated the transaction | Avoid for auth — vulnerable to phishing via malicious contracts |
| block.basefee | uint256 | EIP-1559 base fee in wei | Gas price strategies in MEV bots |
The distinction between msg.sender and tx.originis critical for security. msg.sender is the direct caller — if contract A calls contract B, B sees A as msg.sender.tx.origin is always the original human account (EOA) that signed the transaction. Using tx.origin for authentication enables a class of phishing attack: a malicious contract can trick a user into calling it, then forward the call to the target contract — which sees the original user astx.origin and grants access. The rule is simple: never usetx.origin for access control.
Validators control the timestamp of the blocks they produce, within a window. Ethereum consensus rules require that a block's timestamp is greater than the parent's timestamp and not more than 15 seconds in the future relative to wall time. This means a validator can shift block.timestampby roughly 12 seconds. For most uses (vesting schedules with day-level granularity, time locks measured in hours), this is acceptable. For high-value, second-level precision applications — lotteries, narrow auction windows, price snapshots — timestamp manipulation is a real attack vector. Prefer block numbers for relative timing within a single chain.
When a smart contract needs to send ETH or call another contract, it has several mechanisms available — each with different gas limits, error handling, and security properties. The interaction between contracts is also where the most dangerous class of smart contract bug — reentrancy — arises.
transfer() sends ETH and forwards exactly 2,300 gas to the recipient — just enough to emit an event but not enough to make further storage writes or external calls. If the call fails, it reverts automatically. This fixed gas stipend was originally considered a reentrancy mitigation; after EIP-1884 (Istanbul, 2019) increased the gas cost of some opcodes, 2,300 gas was no longer sufficient for some legitimate recipient contracts, making transfer()a potential source of stuck funds. It is now considered outdated.
send() is similar to transfer()but returns a boolean instead of reverting on failure. It also forwards 2,300 gas. Also considered outdated for the same reasons.
call() is the low-level method recommended by the Solidity team for ETH transfers. It forwards all available gas by default (or a specified amount), returns a boolean success flag and any return data, and does not revert on failure — the caller must check the return value. Example: (bool success, ) = recipient.call{value: amount}("");
Reentrancy is the most famous smart contract vulnerability. It occurs when a contract calls an external address before updating its own state, allowing the callee to call back into the caller and exploit the stale state. The DAO hack in 2016 drained $60 million from the first major DeFi protocol using exactly this attack. The attack flow: attacker calls withdraw(); contract checks balance and sends ETH via call(); attacker'sreceive() function immediately calls withdraw()again before the balance is updated; the contract sends again because the balance is still non-zero from the outer call's perspective.
Validate all preconditions first — who is calling, are parameters valid, are there sufficient funds. Revert immediately if any check fails.
Update all internal state — reduce balances, mark items as processed, set flags. Do this before any external interaction.
Only after state is updated, make external calls — send ETH, call other contracts. External calls are the last step, not the first.
The Checks-Effects-Interactions pattern is the primary reentrancy defence: update all state before making any external calls. A secondary defence is thereentrancy guard modifier — a boolean flag that is set at the start of a function and cleared at the end, causing any reentrant call to revert immediately. OpenZeppelin's ReentrancyGuard contract implements this. Modern contracts typically use both: CEI as the structural pattern, reentrancy guard as a belt-and-suspenders safety net for complex multi-call flows.
Classic reentrancy attacks the same function recursively. But reentrancy can also occur across functions — an attacker calls withdraw(), which sends ETH, and in the callback calls transfer() which reads the not-yet-updated balance. Cross-contract reentrancy is harder to detect: contract A reads a price from contract B; an attacker manipulates B's state during a call from A. A shared reentrancy guard (locking across all functions) defends against cross-function reentrancy; protocol-level analysis is required for cross-contract cases.
Solidity supports multiple inheritance, allowing contracts to inherit state variables, functions, and modifiers from parent contracts. Interfaces define the external API of a contract without implementation. Libraries provide reusable logic without their own storage. Understanding how these three mechanisms work is essential for reading and writing production contracts.
Inheritance in Solidity follows C3 linearisation — the order of parent contracts in the is declaration determines the method resolution order (MRO). When a child contract overrides a function,super calls the next contract in the MRO, not necessarily the direct parent. Most production contracts inherit from OpenZeppelin base contracts:ERC20, Ownable,Pausable, AccessControl,ReentrancyGuard. These are well-audited, gas-optimised implementations of common patterns.
Interfaces define a contract's external API — the set of functions external callers can invoke — without any implementation. They contain only function signatures, events, and custom errors; no state variables, no constructors, no function bodies. Interfaces are how contracts call each other in a type-safe way: rather than using low-level call() with raw bytes, a contract casts an address to an interface type and calls its functions with full type-checking. The ERC-20 interface (IERC20) is the most widely-used example — it defines transfer, approve,balanceOf, and the rest of the token standard.
Libraries contain reusable code that can be attached to types (using using SafeMath for uint256 was the canonical example before Solidity 0.8 added built-in overflow checking) or called directly. Libraries can be deployed and linked externally (the contract stores the library's address and calls it via DELEGATECALL) or inlined at compile time (internal library functions are copied into the contract bytecode). External libraries save bytecode size; internal libraries avoid the gas overhead of an external call.
| Mechanism | Has Storage | Deployed Separately | Primary Use |
|---|---|---|---|
| Contract | Yes | Yes | Main unit of on-chain logic and state |
| Abstract contract | Yes | No (cannot deploy directly) | Base class with partial implementation |
| Interface | No | No | Type-safe cross-contract calls; token standards |
| Library (internal) | No | No (inlined into caller) | Reusable logic — math, data structures |
| Library (external) | No | Yes | Shared logic across many contracts — saves deploy gas |
Smart contracts are immutable once deployed — you cannot edit the bytecode at an existing address. Yet most production protocols need to fix bugs and add features. The solution is the proxy pattern: a thin contract that delegates all calls to a separate implementation contract, which can be replaced. The proxy's address stays constant; the logic changes.
The core primitive is DELEGATECALL: an EVM opcode that executes another contract's code in the calling contract's storage context. When a proxy delegates to an implementation, the implementation's code runs but reads and writes the proxy's storage slots. This is what makes upgrades possible: swap the implementation address in the proxy's storage, and the same proxy address now executes different logic over the same state.
| Pattern | Upgrade Control | Overhead | Risk | Example |
|---|---|---|---|---|
| Transparent Proxy (EIP-1967) | Admin address | Extra SLOAD per call for admin check | Function selector clash (admin vs user) | OpenZeppelin TransparentUpgradeableProxy |
| UUPS (EIP-1822) | Implementation itself | Minimal — no admin slot check on calls | Upgrade function must be in implementation; risk of bricking | OpenZeppelin UUPSUpgradeable |
| Beacon Proxy | Beacon contract | Extra call to beacon per transaction | All proxies upgrade simultaneously | OpenZeppelin BeaconProxy |
| Diamond (EIP-2535) | Owner via facets | Mapping lookup per call | Complex storage layout management | Custom implementations |
| Immutable (no proxy) | None | None | Cannot fix bugs or upgrade | Uniswap v1, early DeFi |
The critical risk in all proxy patterns is storage collision: if the proxy and implementation define state variables at the same storage slots with different types, they will corrupt each other. EIP-1967 mitigates this by storing proxy-specific variables (implementation address, admin address) in pseudo-random slots derived from well-known strings (e.g. keccak256("eip1967.proxy.implementation") - 1) rather than sequential slots, making collision with normal state variables nearly impossible.
An upgradeable contract is, by definition, controlled by whoever holds the upgrade key — a multisig, a DAO, or in the worst case, a single EOA. An upgrade can change any logic, including transferring all funds to an attacker. The Ronin Bridge hack ($625M, 2022) and the Wormhole exploit ($320M, 2022) were both enabled partly by centralised upgrade authority. Before using an upgradeable protocol, check: who controls the upgrade key? Is it a timelock? Is it a multisig with enough signers to be credibly decentralised? Or is it a single address? The upgrade key is the most powerful role in any proxy system.
Smart contract execution either succeeds — all state changes are committed — or it reverts — all state changes for the current call are rolled back. There is no partial success. Understanding how revert conditions work, and how to communicate error information efficiently, is essential for both security and gas efficiency.
require(condition, "error message") is the traditional guard: it evaluates a condition and reverts with an error message string if false. The string is ABI-encoded as a Error(string) type and returned as revert data. The cost of a string revert includes the gas to encode and return the string — which is non-trivial for long messages.
revert() unconditionally reverts with no data.revert CustomError(args) reverts with a custom error — defined using Solidity's error keyword — which is significantly cheaper than string reverts. A custom error with no arguments costs only 4 bytes of revert data (the error selector); a string revert costs the full ABI-encoded string. Custom errors also support typed parameters, making them more informative than strings:error InsufficientBalance(uint256 required, uint256 available)tells the caller exactly what went wrong without the overhead of string encoding.
assert(condition) is different from require: it consumes all remaining gas and reverts with a Panic(uint256) error. It is meant for invariant checks — conditions that should never be false in correct code. If an assert fails, it indicates a bug, not a user error. In practice, assert is rarely used in production Solidity; require and custom errors handle all expected failure modes, and formal verification or fuzzing handles invariant checking.
Events are the primary mechanism by which smart contracts communicate with the outside world. They are the cheapest way to record information durably on-chain, and they are how dapps, analytics platforms, and block explorers learn what contracts did.
When a contract emits an event, it writes a log entry to the transaction receipt. Logs consist of up to four topics (indexed parameters) and adata field (non-indexed parameters). Topic 0 is always the event's Keccak-256 signature (e.g. Transfer(address,address,uint256)). Topics 1–3 are the indexed parameters — each padded to 32 bytes, stored as individual 32-byte values for efficient filtering. The data field is ABI-encoded and contains the non-indexed parameters.
Ethereum nodes expose eth_getLogs — an RPC method that filters log entries by contract address, block range, and topics. This is how dapps query historical events: "give me all Transfer events from this ERC-20 contract where the to address is 0x1234." The filtering is efficient because indexed topics are stored in a bloom filter in the block header, allowing quick pruning of irrelevant blocks.
Subgraph indexers (The Graph, Ponder, Envio) listen to events in real-time and build queryable databases. Rather than querying the chain directly for every page load, dapps query the subgraph — which has already processed all events and stored them in a relational database. This is the practical architecture for any dapp that needs to display historical data or aggregate on-chain activity.
Every part of a smart contract's anatomy — the storage layout, the function visibility rules, the reentrancy guards, the event logs, the proxy patterns — exists to solve one problem: how do you build systems where rules are enforced by code rather than counterparties? The immutability of deployed bytecode makes contracts trustworthy but brittle; the proxy pattern trades some trustlessness for maintainability; the ABI makes opaque bytecode accessible to humans and tools. Smart contracts are not magic — they are programs that run in a specific, constrained environment designed to make their execution verifiable and their state tamper-evident. Understanding their anatomy is the first step to evaluating whether any given protocol is actually doing what it claims.