From 03b7249ac5a8d528685ed51a2451bcbb3bcb7da7 Mon Sep 17 00:00:00 2001 From: Andrei Smirnov Date: Fri, 25 Sep 2026 18:41:37 +0300 Subject: [PATCH 1/2] Allow brief cross-topic overlap in AGENTS.md Each concept still has one owner file that covers it in full. Other files may now restate what matters from their own angle in a bullet or short concept, with a link to the owner, instead of only linking. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b8e29f4..b5c1022 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,7 +50,7 @@ What experienced Go engineers forget before an interview, grouped by subtopic. - **Code** only when shorter than prose; ≤ 6 lines. - **Bold** the term or number worth memorizing — at most 3 per concept, not counting tables. - **Versions**: note when behavior changed ("since Go 1.22", "Python 3.14+", "Pectra, May 2025"). -- **Cross-topic**: never repeat a concept owned by another file; link to it instead: `see [security.md](security.md)`. Link to files, not anchors (the strict build validates anchors; only use one if you've checked the slug). +- **Cross-topic**: the owner file covers a concept in full; another file may overlap briefly from its own angle and links to the owner: `see [security.md](security.md)` (rules in Topic ownership). Link to files, not anchors (the strict build validates anchors; only use one if you've checked the slug). - Suggested size: 5–10 sections, 3–8 concepts each. Shorter is better if nothing is lost. ### What to include @@ -72,11 +72,11 @@ Keep facts that a working engineer knew once but loses without daily use: - Generic advice ("write tests", "use least privilege") without a concrete mechanism. - History and trivia that nobody asks about (e.g. "what is a zero-day"). -## Topic ownership (avoid duplicates) +## Topic ownership -Each concept lives in exactly one file. Other files link to it. +Each concept has one owner file that covers it in full. Other files may overlap where the concept matters to them too: a bullet or a short concept stating what matters from their angle (database says the WAL is only durable after `fsync`; os explains `fsync`), with a link to the owner for the rest. Don't copy the owner's concept wholesale — copies drift apart, and the quiz would ask the same question twice. -| Concept | Owner | Others link from | +| Concept | Owner | Referenced from | |---|---|---| | Consistency models, replication theory, consensus, clocks, delivery semantics, sagas, CRDTs | distributed | database, system, backend | | Retries, backoff, circuit breakers, backpressure, tail latency | distributed | backend, system | From e9022958589bb17bc1235e7d2c833b3d0c4d4e30 Mon Sep 17 00:00:00 2001 From: Andrei Smirnov Date: Fri, 25 Sep 2026 18:41:38 +0300 Subject: [PATCH 2/2] Add Solidity brief and move contract topics out of Ethereum solidity.md covers language semantics, storage, calls and ABI, contract security, gas optimization, upgradeability, errors, compiler and Foundry testing, and inline assembly. ethereum.md keeps the protocol side; oracle manipulation moves to DeFi and MEV, and the EVM section keeps a one-line summary of call semantics that links to the Solidity brief. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 + README.md | 3 +- docs/index.md | 3 +- docs/topics/ethereum.md | 96 ++------------- docs/topics/solidity.md | 254 ++++++++++++++++++++++++++++++++++++++++ zensical.toml | 1 + 6 files changed, 268 insertions(+), 91 deletions(-) create mode 100644 docs/topics/solidity.md diff --git a/AGENTS.md b/AGENTS.md index b5c1022..fcd7931 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,6 +91,8 @@ Each concept has one owner file that covers it in full. Other files may overlap | TCP/UDP, HTTP versions, QUIC, DNS, IP addressing, NAT, MTU, BGP/anycast | networking | backend, system, devops | | Processes, signals, scheduling, virtual memory, OOM, file descriptors, epoll/io_uring, fsync, locks | os | devops, database, golang | | Container internals (namespaces, cgroups), Kubernetes graceful shutdown | devops | os, backend | +| Solidity language, storage layout, calls and ABI, contract security, gas optimization, upgradeability | solidity | ethereum | +| EVM execution, gas and fee market, transaction types, signatures and token standards, DeFi (flash loans, oracle manipulation) and MEV | ethereum | solidity | ## Style diff --git a/README.md b/README.md index 13fed7b..bfdda2f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Each topic in `docs/topics/` is a brief grouped by subtopic, covering what an ex - [JavaScript](docs/topics/javascript.md) — event loop, scope and closures, `this` and prototypes, numbers, async, modules, memory, metaprogramming, Node.js runtime, DOM events, recent additions - [Python](docs/topics/python.md) — runtime and the GIL, memory management, data structures, concurrency, gotchas, object model, functions, recent features, typing, tooling - [Rust](docs/topics/rust.md) — ownership and borrowing, lifetimes, traits, closures and iterators, smart pointers, error handling, concurrency, async, unsafe, macros and Cargo +- [Solidity](docs/topics/solidity.md) — language semantics, storage layout, calls and ABI, contract security, gas optimization, upgradeability, errors, compiler and Foundry testing, inline assembly - [TypeScript](docs/topics/typescript.md) — type system semantics, narrowing, type-level programming, compilation and TypeScript 6/7, strictness, declarations, typing patterns ### Web @@ -39,7 +40,7 @@ Each topic in `docs/topics/` is a brief grouped by subtopic, covering what an ex ### Domains - [AI Engineering](docs/topics/ai.md) — LLM fundamentals, attention and MoE, reasoning models, inference and serving, RAG, fine-tuning, agents, tools and MCP, evaluation, cost, security -- [Ethereum](docs/topics/ethereum.md) — accounts and transactions, gas, EVM, Solidity, calls and ABI, security, upgradeability, signatures and standards, PoS consensus, scaling, DeFi and MEV +- [Ethereum](docs/topics/ethereum.md) — accounts and transactions, gas, EVM, signatures and standards, PoS consensus, scaling, DeFi and MEV ## Quiz diff --git a/docs/index.md b/docs/index.md index 6c5e637..3e75eee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,6 +19,7 @@ Topic briefs on what engineers forget before technical interviews, grouped by su - [JavaScript](topics/javascript.md) — event loop, scope and closures, `this` and prototypes, numbers, async, modules, memory, metaprogramming, Node.js runtime, DOM events, recent additions - [Python](topics/python.md) — runtime and the GIL, memory management, data structures, concurrency, gotchas, object model, functions, recent features, typing, tooling - [Rust](topics/rust.md) — ownership and borrowing, lifetimes, traits, closures and iterators, smart pointers, error handling, concurrency, async, unsafe, macros and Cargo +- [Solidity](topics/solidity.md) — language semantics, storage layout, calls and ABI, contract security, gas optimization, upgradeability, errors, compiler and Foundry testing, inline assembly - [TypeScript](topics/typescript.md) — type system semantics, narrowing, type-level programming, compilation and TypeScript 6/7, strictness, declarations, typing patterns ### Web @@ -35,7 +36,7 @@ Topic briefs on what engineers forget before technical interviews, grouped by su ### Domains - [AI Engineering](topics/ai.md) — LLM fundamentals, attention and MoE, reasoning models, inference and serving, RAG, fine-tuning, agents, tools and MCP, evaluation, cost, security -- [Ethereum](topics/ethereum.md) — accounts and transactions, gas, EVM, Solidity, calls and ABI, security, upgradeability, signatures and standards, PoS consensus, scaling, DeFi and MEV +- [Ethereum](topics/ethereum.md) — accounts and transactions, gas, EVM, signatures and standards, PoS consensus, scaling, DeFi and MEV ## Quiz diff --git a/docs/topics/ethereum.md b/docs/topics/ethereum.md index abb66c3..aa80c91 100644 --- a/docs/topics/ethereum.md +++ b/docs/topics/ethereum.md @@ -54,17 +54,13 @@ What experienced Ethereum engineers forget before an interview, grouped by subto - **EIP-7825** (Fusaka, December 2025) caps one transaction at **2²⁴ ≈ 16.7M gas**, whatever the block limit. - The block gas limit isn't fixed by protocol: validators move it by up to ~1/1024 per block, so any number is a snapshot. -## EVM and storage +## EVM ### Execution model - Stack-based, **256-bit words**, stack depth 1024 — only the top **16** items are reachable, the source of "stack too deep". - Memory expansion cost is **quadratic** in size; storage is the most expensive resource, priced per 32-byte slot. - -### Storage layout - -- Variables under 32 bytes **pack** into shared slots in declaration order. -- A `mapping` value lives at **`keccak256(key . slot)`**; a dynamic array stores its length at the slot and elements from `keccak256(slot)`. +- `DELEGATECALL` runs another contract's code on the caller's storage; any call forwards at most 63/64 of the remaining gas (EIP-150) — see [solidity.md](solidity.md). ### Transient storage and SELFDESTRUCT @@ -81,89 +77,6 @@ What experienced Ethereum engineers forget before an interview, grouped by subto - World state (address → account) is a **Merkle Patricia Trie**; each contract's storage is its own trie. - A block header commits to the state, transactions, and receipts roots, so a **light client** verifies a balance or receipt with a Merkle proof instead of the full state. -## Solidity - -### Data locations - -- **`storage`** is persistent state; `memory` is per call; **`calldata`** is read-only input — the cheapest location for external array and struct parameters. -- Storage → memory assignment **copies**; memory → memory copies only the reference; a local `storage` variable is a pointer into state. - -### Checked arithmetic - -- Overflow and underflow **revert since 0.8** (`Panic(0x11)`); `unchecked { }` opts out for proven-safe math. -- Division **truncates**, so multiply before dividing to keep precision. - -### Gas optimization patterns - -- **Pack** small variables that are read and written together into one slot. -- **`constant`/`immutable`** values are embedded in bytecode — no `SLOAD`. -- Cache storage reads in local variables inside loops; use `calldata` parameters. -- **Custom errors** (0.8.4) are cheaper than revert strings; 0.8.22 made simple loop-counter increments unchecked automatically. - -## Calls and ABI - -### Function selectors and fallback - -- Calldata starts with a 4-byte **selector** (first 4 bytes of `keccak256("fn(types)")`) followed by ABI-encoded arguments; selectors can collide, a risk in proxy dispatchers. -- No matching selector → **`fallback()`**; plain ETH with empty calldata → **`receive()`** (or a payable fallback); a contract with neither rejects plain ETH. - -### call, delegatecall, staticcall - -- `call` runs in the callee's context; **`delegatecall`** runs the callee's code on the **caller's** storage, `msg.sender`, and `msg.value`; `staticcall` forbids state changes. -- The **63/64 rule**: a call forwards at most 63/64 of remaining gas, keeping 1/64 for the caller. - -### ABI encoding pitfalls - -- Low-level `call` returns a **`bool`** instead of reverting — always check it. -- **`abi.encodePacked`** of several dynamic values can collide (`("a","bc")` vs `("ab","c")`) — hash `abi.encode` output for signatures. - -## Smart contract security - -### Reentrancy - -- An external call re-enters before state updates: **single-function**, cross-function, or **read-only** (a view function returns pre-update state to another protocol). -- Fix with **Checks-Effects-Interactions** plus a reentrancy guard. - -### Access control - -- Missing modifiers and **unprotected initializers** let anyone call admin functions. -- Authenticate with `msg.sender`, never **`tx.origin`** — a malicious contract the owner calls can pass a `tx.origin` check. - -### Oracle manipulation - -- A DEX **spot price** can be moved within one transaction using a flash loan, then read by a victim protocol. -- Use **TWAPs** or a push oracle such as Chainlink, and check staleness. - -### Signature replay and malleability - -- Signed messages without a **nonce, chainId, and deadline** can be replayed on another chain or later. -- ECDSA `s` can be flipped (`n − s`) into a second valid signature — enforce **low-`s`** and never use a signature as a unique ID. - -### Token integration quirks - -- Non-standard ERC-20s return nothing or `false` from `transfer` — use **SafeERC20**. -- **Fee-on-transfer** and rebasing tokens break "amount sent = amount received" assumptions — measure balance deltas. -- Unbounded loops over user-growable arrays become a gas **denial of service**. - -## Upgradeability - -### Proxy patterns - -| Pattern | Upgrade logic lives in | Note | -|---|---|---| -| **Transparent** | proxy (admin-only path) | admin can't call the implementation | -| **UUPS** (ERC-1822) | implementation | cheaper proxy; an implementation without upgrade code bricks it | -| **Beacon** | shared beacon contract | upgrades many proxies at once | -| **Diamond** (EIP-2535) | proxy routing to many facets | per-selector implementations | - -- Every pattern keeps **storage in the proxy** and `delegatecall`s the implementation. - -### Proxy storage and initializers - -- The implementation address sits at the pseudo-random **EIP-1967** slot, away from the implementation's own variables. -- New versions only append storage variables, never reorder — or use **ERC-7201** namespaced storage. -- Constructors don't run through a proxy: use **initializers**, and call `_disableInitializers()` in the implementation's constructor. - ## Signatures and standards ### Signing @@ -236,6 +149,11 @@ What experienced Ethereum engineers forget before an interview, grouped by subto - Lending (Aave, Compound) is over-collateralized; a **health factor** below 1 allows liquidation at a discount. - **Flash loans** are uncollateralized but must be repaid in the same transaction or everything reverts — turning manipulable prices into large exploits. +### Oracle manipulation + +- A DEX **spot price** can be moved within one transaction using a flash loan, then read by a victim protocol. +- Use **TWAPs** or a push oracle such as Chainlink, and check staleness. + ### MEV - **Sandwich attacks** (front-run and back-run a victim's swap), arbitrage back-running, and liquidations. diff --git a/docs/topics/solidity.md b/docs/topics/solidity.md new file mode 100644 index 0000000..3029b97 --- /dev/null +++ b/docs/topics/solidity.md @@ -0,0 +1,254 @@ +# Solidity + +What experienced Solidity engineers forget before an interview, grouped by subtopic. + +## Language semantics + +### Visibility and mutability + +- `external` functions are callable internally only as `this.f()` — a real external call; `public` works both ways. +- **`private` isn't secret**: anyone can read any storage slot off-chain with `eth_getStorageAt`. +- External calls to `view`/`pure` functions use **`STATICCALL`**, so a state change inside them reverts at runtime. + +### Inheritance and linearization + +- Bases are listed **most base-like first**: `contract D is B, C` (both `is A`) linearizes to D → C → B → A (C3, like Python's MRO). +- **`super`** calls the next contract in the linearization, not the declared parent: in D, `super.f()` inside C calls B even though C only inherits A. +- Overridable functions need `virtual`; overriding needs `override`, and `override(B, C)` when several bases define it. + +### Modifiers + +- `_` marks where the function body runs; a `return` in the body only leaves the body, so code after `_` still runs **after the return**. +- Modifiers apply left to right; `_` may run several times, and if it never runs the body is skipped and return values stay at their defaults. +- Modifier code is **inlined** at every use — move the logic into an internal function to shrink bytecode. +- `virtual` modifiers are deprecated since 0.8.31, ahead of 0.9. + +### Libraries and user-defined types + +- `internal` library functions are inlined into the caller (a `JUMP`); `public`/`external` ones run through **`DELEGATECALL`** into a separately deployed, linked library. +- Libraries have no state variables and can't receive ETH; `using L for T` attaches their functions to a type, and `global` (0.8.13, user-defined types only) applies it in every file. +- **User-defined value types** (`type Price is uint256;`, 0.8.8) are zero-cost wrappers that stop mixing units; `using {add as +} for Price global` binds operators (0.8.19). + +### Arithmetic and casts + +- Overflow and underflow **revert since 0.8** with `Panic(0x11)`; `unchecked { }` opts out for proven-safe math. +- **Explicit downcasts truncate silently**, even in 0.8: `uint8(uint256(300)) == 44` — use SafeCast. +- `int` ↔ `uint` conversions reinterpret bits: `uint256(int256(-1)) == type(uint256).max`. +- Division **truncates**, so multiply before dividing; OpenZeppelin's `Math.mulDiv` keeps a 512-bit intermediate so the product can't overflow. + +## Data and storage + +### Data locations + +- **`storage`** is persistent state; `memory` is per call; **`calldata`** is read-only input — the cheapest location for external array and struct parameters. +- Storage → memory assignment **copies**; memory → memory copies only the reference; a local `storage` variable is a pointer into state. + +### Storage layout + +- Variables under 32 bytes **pack** into shared slots in declaration order; structs and arrays always start a new slot. +- A `mapping` value lives at **`keccak256(key . slot)`**; a dynamic array stores its length at the slot and elements from `keccak256(slot)`. +- `string`/`bytes` up to 31 bytes sit in the slot itself with `length * 2` in the lowest byte; longer ones store `length * 2 + 1` there and data from `keccak256(slot)`. +- With inheritance, slots follow the **C3 linearization**, most base-like contract first — reordering bases shifts every slot. + +### Transient and relocated storage + +- **`transient`** state variables (0.8.28, value types only) compile to `TSTORE`/`TLOAD`: 100 gas, cleared at the end of the transaction — see [ethereum.md](ethereum.md). +- They can't have initializers, and they **persist across calls within one transaction** — a lock or flag must be reset explicitly, or a later call in a batch sees it. +- **`layout at `** (0.8.29) moves a contract's storage to a custom base slot; `layout at erc7201("my.app")` (0.8.35) uses an ERC-7201 namespace's base slot. + +### Mapping and delete gotchas + +- Mappings have no length and can't be iterated; every key "exists" with a zero value — keep an `EnumerableSet`/`EnumerableMap` to enumerate. +- **`delete` on a struct skips its mappings**: the entries survive and reappear if the struct is reused. +- `delete arr[i]` zeroes the element without shrinking the array; swap with the last element and `pop()` to remove it. +- `delete` on a dynamic storage array clears every element — its gas grows with the length. + +## Calls and ABI + +### Function selectors and fallback + +- Calldata starts with a 4-byte **selector** (first 4 bytes of `keccak256("fn(types)")`) followed by ABI-encoded arguments; selectors can collide, a risk in proxy dispatchers. +- No matching selector → **`fallback()`**; plain ETH with empty calldata → **`receive()`** (or a payable fallback); a contract with neither rejects plain ETH. + +### call, delegatecall, staticcall + +- `call` runs in the callee's context; **`delegatecall`** runs the callee's code on the **caller's** storage, `msg.sender`, and `msg.value`; `staticcall` forbids state changes. +- The **63/64 rule** (EIP-150): a call forwards at most 63/64 of remaining gas, keeping 1/64 for the caller. +- `delegatecall` keeps `msg.value`, so a payable multicall that delegatecalls itself credits one payment many times (the 2021 SushiSwap MISO bug); the same happens reading `msg.value` in a loop. + +### Sending ETH + +- `transfer` reverts and `send` returns `false` on failure; both forward only a **2,300-gas stipend** — too little for most smart wallets, and brittle when opcode costs change (EIP-1884 broke receivers in 2019). +- Both are **deprecated since 0.8.31**, ahead of removal in 0.9; use `(bool ok, ) = to.call{value: amount}("")`, check `ok`, and guard against reentrancy. +- ETH can arrive without running your code — another contract's `selfdestruct`, fee recipient or withdrawal credits — so **`address(this).balance`** can exceed internal accounting; never use it in a strict equality. + +### ABI encoding pitfalls + +- A low-level call to an address with **no code succeeds** — check `code.length` first; high-level calls revert instead. +- **`abi.encodePacked`** of several dynamic values can collide (`("a","bc")` vs `("ab","c")`) — hash `abi.encode` output for signatures. +- **`abi.encodeCall`** (0.8.11) type-checks the function and arguments; `abi.encodeWithSignature` silently accepts a typo in the signature string. + +### Event encoding + +- An **indexed** `string`, `bytes`, array, or struct is stored as its **keccak256 hash** — the value can't be recovered from the log. +- **`anonymous`** events drop the signature topic, allowing 4 indexed parameters, but can't be filtered by event name. +- Non-indexed parameters are ABI-encoded into the log data; contracts can't read logs back — see [ethereum.md](ethereum.md). + +## Security + +### Reentrancy + +- An external call re-enters before state updates: **single-function**, cross-function, cross-contract, or **read-only** (a view function returns pre-update state to another protocol). +- Fix with **Checks-Effects-Interactions** plus a reentrancy guard; OpenZeppelin's `ReentrancyGuardTransient` keeps the lock in transient storage. +- Hooks are external calls too: ERC-721/1155 `safeTransfer` receivers, ERC-777 hooks, and ETH sent to a contract. + +### Access control + +- Missing modifiers and **unprotected initializers** let anyone call admin functions. +- Authenticate with `msg.sender`, never **`tx.origin`** — a malicious contract the owner calls can pass a `tx.origin` check. +- **`msg.sender == tx.origin`** no longer proves there is no code in the path: since EIP-7702 (Pectra, May 2025) a delegated EOA runs code and makes several calls per transaction. +- `code.length == 0` doesn't prove an EOA either: it's 0 for a contract still in its constructor, and a 7702-delegated EOA has code. + +### Signature replay and malleability + +- Signed messages without a **nonce, chainId, and deadline** can be replayed on another chain or later. +- ECDSA `s` can be flipped (`n − s`) into a second valid signature — enforce **low-`s`** and never use a signature as a unique ID. +- Raw `ecrecover` returns `address(0)` on a bad signature, which matches an unset signer — use OpenZeppelin's `ECDSA`, which reverts and enforces low-`s`. + +### Token integration quirks + +- Non-standard ERC-20s (USDT) return nothing or `false` from `transfer` — use **SafeERC20**. +- **Fee-on-transfer** and rebasing tokens break "amount sent = amount received" assumptions — measure balance deltas. +- Changing a non-zero allowance lets the spender front-run the change and spend old + new. +- USDT's `approve` reverts unless the current allowance is 0 — `SafeERC20.forceApprove` resets it first. +- Blocklists (USDC, USDT) make any transfer to or from a flagged address revert. + +### Oracle integration + +- A DEX **spot price** can be moved within one transaction using a flash loan — use a TWAP or Chainlink; see [ethereum.md](ethereum.md). +- Chainlink `latestRoundData`: check **staleness** (`updatedAt` against the feed's heartbeat) and `answer > 0`; on L2s also check the **sequencer uptime feed**. +- Feeds have their own decimals (USD pairs use 8, ETH pairs 18) — scale before combining with token amounts. + +### Denial of service + +- **Unbounded loops** over user-growable arrays eventually exceed the gas limit — paginate or cap the size. +- Pushing ETH to many receivers fails if one reverts (a contract without `receive`) — switch to **pull payments** (a withdraw function). +- A malicious callee can return huge data that Solidity copies into memory even when discarded — a **returnbomb**; cap the copy with an assembly `call`. + +### Randomness and block data + +- **`block.prevrandao`** (0.8.18, replaced `difficulty`) is known to the proposer, who can bias it by withholding a block — use **Chainlink VRF** or commit-reveal. +- `blockhash(n)` returns 0 for blocks older than **256**; EIP-2935 (Pectra) serves 8,191 hashes from a system contract, but the opcode is unchanged. +- Anything derived from block data can be computed by an attacker's contract in the same transaction, which reverts when the outcome is bad. +- On L1, `block.timestamp` advances in fixed 12 s slots; L2 sequencers set it within looser bounds. + +### Rounding and precision + +- Round **in the protocol's favor**: ERC-4626 rounds shares down on deposit and up on withdraw. +- **First-depositor inflation attack**: the attacker mints 1 wei of shares and donates assets, so the next depositor's shares round down to 0 — mitigate with virtual shares (OpenZeppelin's decimals offset) or dead shares. +- Tokens mix decimals (USDC 6, WETH 18) — normalize before math and never compare raw amounts across tokens. + +## Gas optimization + +### Gas optimization patterns + +- An `SSTORE` costs **20,000** gas to set a zero slot and 2,900 to change a non-zero one (+2,100 if cold) — see [ethereum.md](ethereum.md). +- **Pack** small variables that are read and written together into one slot. +- **`constant`/`immutable`** values are embedded in bytecode — no `SLOAD`. +- Cache storage reads in local variables inside loops; use `calldata` parameters. +- Custom errors are cheaper than revert strings; 0.8.22 made simple loop-counter increments unchecked automatically. + +### Code size limit + +- Deployed code is capped at **24,576 bytes** (EIP-170) and initcode at 49,152 (EIP-3860, Shanghai). +- Workarounds: move logic into external libraries, split into several contracts, lower optimizer `runs`, or use the Diamond pattern. +- **EIP-7954**, scheduled for Glamsterdam, raises the limits to 64 KiB and 128 KiB. + +## Upgradeability + +### Proxy patterns + +| Pattern | Upgrade logic lives in | Note | +|---|---|---| +| **Transparent** | proxy (admin-only path) | admin can't call the implementation | +| **UUPS** (ERC-1822) | implementation | cheaper proxy; an implementation without upgrade code bricks it | +| **Beacon** | shared beacon contract | upgrades many proxies at once | +| **Diamond** (EIP-2535) | proxy routing to many facets | per-selector implementations | + +- Every pattern keeps **storage in the proxy** and `delegatecall`s the implementation. + +### Proxy storage and initializers + +- The implementation address sits at the pseudo-random **EIP-1967** slot, away from the implementation's own variables. +- New versions only append storage variables, never reorder — or use **ERC-7201** namespaced storage. +- Constructors don't run through a proxy: use **initializers**, and call `_disableInitializers()` in the implementation's constructor. +- `immutable` values live in the implementation's bytecode, so every proxy shares the values set at implementation deployment. + +### Upgrade safety + +- A proxy function whose selector matches an implementation function shadows it — Transparent proxies route by caller, UUPS proxies have no functions of their own. +- An **uninitialized UUPS implementation** let anyone take ownership and upgrade it to a `selfdestruct` contract, bricking every proxy (OpenZeppelin advisory, 2021); since EIP-6780 the code survives. +- **Metamorphic contracts** redeployed different code at one address via `CREATE2` + `SELFDESTRUCT`; EIP-6780 (Cancun) ended the trick. +- OpenZeppelin's Upgrades plugins (Foundry, Hardhat) diff storage layouts and flag unsafe patterns before an upgrade. + +## Errors and reverts + +### Error types + +- `require(cond, "msg")` and `revert("msg")` encode **`Error(string)`** (selector `0x08c379a0`). +- `assert`, overflow, division by zero, and out-of-bounds access raise **`Panic(uint256)`** with codes `0x01`, `0x11`, `0x12`, `0x32`. +- Before 0.8, `assert` hit the `INVALID` opcode and burned all remaining gas; now it reverts like any other error. +- **Custom errors** (0.8.4) encode a selector plus arguments; `require(cond, MyError())` works since 0.8.26 with via-IR and 0.8.27 with legacy codegen. + +### try/catch limits + +- Works only on **external calls** and `new` contract creation, not on internal calls. +- Catches only reverts inside the callee: undecodable return data or **a target with no code** reverts the caller. +- Clauses: `catch Error(string memory)`, `catch Panic(uint256)`, and `catch (bytes memory)` for everything else — a custom error can't be caught by name. +- The callee can burn 63/64 of the gas, leaving too little for the `catch` block. + +### Revert data bubbling + +- A low-level `call` returns `(bool success, bytes memory data)` and **doesn't revert** — always check `success`. +- Re-throw the callee's error unchanged with assembly: `revert(add(data, 32), mload(data))`. +- OpenZeppelin's `Address.functionCall` does both. + +## Compiler and tooling + +### Compiler pipelines + +- **via-IR** compiles through Yul: stronger cross-function optimization and fewer "stack too deep" errors (it moves variables to memory), but slower builds; legacy is still the default. +- via-IR changes some semantics: each contract initializes its state variables right before its own constructor, and modifiers reset return variables at every `_`. +- Optimizer **`runs`** (default 200) is the expected number of executions per opcode: lower favors deployment cost, higher favors call cost. + +### EVM version targeting + +- **`evmVersion`** picks the target hard fork; the default moved to `shanghai` in 0.8.20 (emits **`PUSH0`**), `cancun` in 0.8.25, `prague` in 0.8.30, and `osaka` in 0.8.31. +- Code with an opcode the target chain lacks fails to deploy — `PUSH0` broke L2 deployments in 2023; set `evmVersion` to the oldest chain you target. +- Pin the pragma (`pragma solidity 0.8.37;`) in contracts you deploy; a floating `^0.8.0` suits libraries. + +### Testing with Foundry + +- Tests are Solidity contracts; **cheatcodes** (`vm.prank`, `vm.warp`, `vm.deal`, `vm.expectRevert`) set the caller, time, balances, and expected failures. +- **Fuzz tests** take random arguments (256 runs by default); `bound()` constrains them. +- **Invariant tests** call random sequences of handler functions and check properties after each (total supply = sum of balances). +- Fork tests (`--fork-url`) run against live chain state. + +### Static analysis and formal verification + +- **Slither** flags known bug patterns (reentrancy, shadowing, uninitialized storage) in seconds. +- Symbolic and formal tools (Halmos, Certora, the built-in SMTChecker) prove properties for all inputs, not just sampled ones. + +## Inline assembly + +### Memory layout + +- `0x00`–`0x3f` is scratch space for hashing, **`0x40` holds the free memory pointer**, `0x60` is the zero slot; allocation starts at **`0x80`** — hence bytecode starting `6080604052`. +- Memory is never freed within a call: Solidity only bumps the free memory pointer. + +### Assembly gotchas + +- No overflow or bounds checks, and values narrower than 256 bits may carry **dirty upper bits** — mask them. +- Mark blocks **`assembly ("memory-safe")`** (0.8.13) so via-IR can still move variables to memory; the `/// @solidity memory-safe-assembly` comment is deprecated since 0.8.31. +- `return` and `revert` in assembly end the whole call, not just the current function. +- Storage variables expose `.slot` and `.offset`; calldata arrays expose `.offset` and `.length`. diff --git a/zensical.toml b/zensical.toml index 26969d2..858355a 100644 --- a/zensical.toml +++ b/zensical.toml @@ -19,6 +19,7 @@ nav = [ { "JavaScript" = "topics/javascript.md" }, { "Python" = "topics/python.md" }, { "Rust" = "topics/rust.md" }, + { "Solidity" = "topics/solidity.md" }, { "TypeScript" = "topics/typescript.md" }, ] }, { "Web" = [