Treat CBDE preparation as decision training, not definition memorization. For each topic, learn the named concept, the alternative you would reject, and the observable consequence of the choice. Work short paper scenarios weekly: state your decision, justify it with EVM mechanics, and name the risk you avoided. This turns the six CBDE domains into one connected skill you can apply under time pressure.
msg.sender vs tx.origin: fixing authentication decisions in scenarios
Use msg.sender for authorization because it names the immediate caller, whether an externally owned account or a contract. tx.origin names the account that started the transaction chain and introduces phishing exposure plus incompatibility with smart-contract wallets.
The distinction is purely about position in the call chain. msg.sender is whoever invoked your function directly; tx.origin is the externally owned account that signed the original transaction. A check like require(tx.origin == owner) seems stricter, but it rejects any legitimate call routed through a multisig wallet, a smart-contract wallet, or a forwarding contract, and it can be abused: if an owner is tricked into calling a malicious contract, that contract can invoke owner-gated functions of yours while tx.origin still shows the owner.
Worked scenario: a question describes a marketplace contract that gates withdrawals with require(tx.origin == seller). One answer option 'improves' it by adding msg.sender as an alternative; another replaces the check with require(msg.sender == seller) plus a role mapping. The plausible mistake is keeping tx.origin because it looks like proof a human acted. The better decision is msg.sender with explicit access control, because it accepts calls from contract wallets the seller may legitimately use and cannot be triggered indirectly by a phishing contract. Drill this by rewriting every authorization snippet you meet in terms of who the immediate caller is, then asking what happens when that caller is a contract.
- msg.sender: the direct caller of the current function; the safe basis for access control.
- tx.origin: the EOA that started the transaction; exposed to indirect-call phishing and hostile to contract wallets.
- Scenario habit: before choosing an authorization answer, sketch the call chain and label each hop.
Choosing memory, storage, and calldata without guessing gas behavior
Data locations decide where a value lives and whether assignment copies or references it. storage parameters point at persistent state, memory holds transient copies, and calldata is a read-only, non-copying location for external function inputs.
Reference types such as arrays, structs, and strings need an explicit location in modern Solidity. Assigning a storage struct to a local variable creates a pointer: writing through it changes contract state permanently, which is a frequent source of subtle scenario bugs. Assigning the same struct to memory produces an independent copy, so writes are discarded when the function ends. calldata is available for external functions, is read-only, and avoids copying large arrays, which is why it is the default recommendation for external inputs you only read.
Value types like uint256, bool, and address ignore these keywords in parameters because they always copy. Where scenarios get tricky is an external function receiving a large struct: choosing calldata signals you will not mutate it, while silently copying to memory wastes gas. Conversely, if a scenario requires updating an array that must persist, a memory copy is the wrong answer even though it compiles. Mini exercise: take a function that accepts uint256[] memory, sums the values, and never writes them. Rewrite the parameter as calldata and predict two things before compiling: it still compiles because you only read, and gas drops because no array copy occurs. Confirm in Remix and note the observable evidence: identical return value, lower gas estimate.
Mapping reentrancy to the checks-effects-interactions pattern
Reentrancy becomes possible whenever your contract makes an external call before it finishes updating state, because the callee's receive or fallback hook can call back in. The checks-effects-interactions pattern removes the exploitable window by reordering operations.
Name the three phases and keep them in order: validate conditions (checks), update all internal state (effects), then interact with other contracts or send value (interactions). A withdrawal function that sends ether and then zeroes the balance leaves the recorded balance intact during the external call, so a malicious recipient can re-enter the withdrawal and drain repeatedly before the first call returns. A reentrancy guard modifier is a valid belt-and-braces addition, but reordering alone removes the inconsistent intermediate state that the attack feeds on.
Worked scenario: a vault's withdraw function checks the balance, calls payable(recipient).call{value: amount}(''), then sets balances[recipient] = 0, and the question asks which change best hardens it. Option A adds a mutex only; option B moves balances[recipient] = 0 above the call and keeps call with a checked return; option C raises the gas stipend. The tempting mistake is choosing A or C, treating the guard or gas limit as the defense. The better decision is B: effects before interactions eliminates the exploitable window, and call avoids transfer's fixed gas forwarding, which can break legitimate contracts with receive logic. The reorder also covers reentrancy variants that a gas-limited transfer assumption would not, and the guard then adds cross-function protection. The same reordering logic underlies pull-payment designs, where owed balances are recorded first and claimed later.
ERC-20, ERC-721, and ERC-1155: choosing a standard by transfer semantics
Token standards differ in what a unit represents and how transfers and approvals work. ERC-20 tracks fungible balances, ERC-721 gives each token a unique identifier, and ERC-1155 batches multiple token types and semi-fungible quantities under one contract.
Match the standard to the asset model before anything else. A currency-like or points-like asset maps to ERC-20, whose transfer and transferFrom move amounts between address balances with an allowance mechanism for delegated spending. A collectible, license, or identity-bound asset maps to ERC-721, where every token has a unique id and an operator or per-token approval decides who may move it. A scenario mentioning game items where 500 identical swords and 1 unique trophy live in one contract points at ERC-1155, which supports per-id balances and array-based batch operations that save calls and gas.
When analyzing any scenario, check two details: who is authorized to move tokens, and whether quantities or identities transfer. A question that says 'users delegate a spender to move up to an allowance' points at ERC-20's approve and transferFrom; 'the owner or an approved operator transfers token id 42' points at ERC-721; 'a single transaction moves ids 3 and 9 in different amounts to two recipients' points at ERC-1155's batch functions. Answering with the wrong standard is the plausible mistake when the asset description is only skimmed. When reviewing, trace one transfer of each standard end to end in a reference implementation: what state changes, what event is emitted, and what the caller must have set up beforehand.
| Property | ERC-20 | ERC-721 | ERC-1155 |
|---|---|---|---|
| Unit model | Fungible amount per address | One unique id per token | Semi-fungible: quantity per id |
| Core transfer call | transfer / transferFrom | transferFrom / safeTransferFrom | safeTransferFrom / safeBatchTransferFrom |
| Authorization model | Allowance approved to a spender | Owner, approved address, or operator for all | Owner or approved operators per contract |
| Typical fit | Currency, points, staked value | Collectibles, deeds, licenses | Game items, mixed inventories, batched mints |
Reading storage layout and packing to predict gas-heavy code
Persistent variables occupy 32-byte slots in declaration order, and Solidity packs consecutive small types into one slot when they fit. Mappings and dynamic arrays use keccak-based slot derivation, so layout reasoning is a learnable, testable skill.
For fixed-size state variables, slot assignment is sequential: a uint128 followed by a uint64 and a bool share one 32-byte slot because 16 plus 8 plus 1 bytes fit together, while a uint256 forces a fresh slot. Mappings never store values inline; a key's value lives at keccak256(key, slot-of-mapping), and dynamic arrays store their length in the assigned slot with elements at keccak256(slot) plus an index. These rules let you predict which operations touch storage — and storage operations are the expensive ones in a scenario's gas comparison.
Worked scenario: given a struct declared as uint256 amount; bool active; uint256 price; uint64 deadline;, the tempting answer is 'widen the small types to uint256 for uniformity', which actually costs more. Hand-solve the original: amount takes slot 0; bool active occupies slot 1 alone because the following uint256 cannot share it; price takes slot 2; deadline takes slot 3 — four slots. The better decision groups the small types adjacently: amount, price, then active and deadline (1 plus 8 bytes) share a final slot — three slots. Each avoided slot reduces writes and reads on every interaction with that struct, compounding across users. Verify with a layout inspector and expect exactly those slot assignments; if your hand-drawn count disagrees, recheck the arithmetic. Note the boundary honestly: packing helps when small fields genuinely fit together, so a credible answer shows the slot arithmetic rather than asserting a win.
Proxies, Layer 2, or plain redeploy: deciding on upgrade and scaling choices
A proxy keeps a stable address and state by delegatecalling into a logic contract, which enables upgrades but adds storage-collision and initializer risks. Layer 2 solutions move execution off Layer 1 for cost and throughput. Choose based on upgrade need, cost profile, and trust assumptions.
Understand the mechanics before matching options: delegatecall executes the logic contract's code against the proxy's storage, so the storage layout of both must agree or variables collide; the initializer must be protected, typically with a lock, because implementation contracts can be initialized directly; and upgrade authority itself is a security-critical role. Plain redeployment is the simplest option, but it resets state and address, which matters for any integration that hardcodes your contract. Comparing the three options on state continuity, address continuity, and added complexity turns a fuzzy scenario into a checklist.
For scaling, distinguish what moves: Layer 2 rollups execute transactions off Layer 1 and post data back, trading confirmation characteristics for lower per-transaction cost, while state channels push even more off-chain and settle on-chain. A scenario emphasizing frequent low-value interactions with a stable token contract points toward deploying or bridging on an L2; one emphasizing preservation of an existing mainnet address and history while changing logic points toward a proxy pattern; one emphasizing simplicity and a young, still-changing contract points toward redeploying while few integrations exist. Name the accepted risk in each choice: upgrade-key compromise for proxies, withdrawal and sequencing characteristics for L2s, migration burden for redeployment. These are decision frameworks for paper scenarios and design reasoning; real deployments add protocol-specific details, so justify the choice with the stated constraints rather than treating any option as universally correct.
A scenario-first CBDE study sequence with readiness checks
Sequence study so each domain feeds the next: EVM architecture first, then Solidity data locations, then security patterns, then token standards, then tooling, deployment, and scaling. Drill one decision scenario per topic every cycle and log your reasoning, not just your chosen option.
A workable six-week cycle: week one, trace a transaction from wallet signature through the mempool and block inclusion to EVM execution, and practice the call-chain diagram habit. Week two, rewrite three functions changing only data locations and record the gas and behavior differences. Week three, take each vulnerable snippet you find in a learning resource and state the checks-effects-interactions fix plus residual risks. Week four, trace one transfer per token standard through a reference implementation. Week five, hand-draw two storage layouts and verify with an inspector. Week six, write one-paragraph justifications for proxy versus L2 versus redeploy under three different constraint sets. Adapt the pacing to your schedule; the order matters more than the calendar.
Practical exercise with a rubric: each week, write one five-line scenario question yourself covering that week's concept, plus four answer options where two are plausible. Score each scenario 0-2 on four criteria: the decision follows from named mechanics (not vibes), the rejected option's risk is named, the consequence is observable (gas, state, access), and the boundary of the claim is stated. Score your own reasoning the same way on practice items. A consistent 6-8 out of 8 across a week's scenarios is a learning milestone that your decision skills are consolidating — a self-check, not a prediction of any exam result. Readiness checks: you can explain msg.sender versus tx.origin with a call-chain sketch, predict slot counts for a five-variable contract, reorder a withdrawal function and say why, pick a token standard from an asset description alone, and argue a proxy versus redeploy decision in three sentences. Any check you cannot pass points at the exact section to revisit.
References and further reading
Use these references to explore the concepts and check the latest information from the relevant organizations.
