Quiz: Solidity Programming
Multiple Choice
Q1. What does pragma solidity ^0.8.20; mean?
A) The contract can only be compiled with version 0.8.20 exactly B) The contract can be compiled with version 0.8.20 or higher, but below 0.9.0 C) The contract can be compiled with any version 0.8.x or higher D) The contract requires the Solidity optimizer to be enabled at level 20
Q2. Which data location is the most expensive for storing data in Solidity?
A) Memory B) Calldata C) Storage D) Stack
Q3. What is the primary difference between external and public visibility?
A) external functions can only be called by the contract owner
B) external functions cannot be called internally without using this.functionName(), while public functions can be called both internally and externally
C) public functions cost more gas than external functions in all cases
D) There is no practical difference; they are interchangeable
Q4. In Solidity 0.8.x, what happens when you add 1 to type(uint256).max?
A) The value wraps around to 0 (silent overflow)
B) The transaction reverts with an overflow error
C) The compiler refuses to compile the code
D) The value is clamped at type(uint256).max
Q5. Which of the following is true about Solidity mappings?
A) You can iterate over all keys in a mapping using a for loop
B) Mappings track their size via a .length property
C) Every possible key exists in a mapping and maps to the default value of the value type
D) Mappings can only use address as the key type
Q6. What is the purpose of the indexed keyword in event parameters?
A) It makes the event data cheaper to store B) It allows the parameter to be efficiently filtered when querying logs C) It ensures the event is emitted in chronological order D) It restricts who can read the event data
Q7. In the ERC-20 standard, what is the purpose of the approve and transferFrom pattern?
A) To allow batch transfers of tokens to multiple recipients B) To enable a third party (such as a DEX) to transfer tokens on behalf of the token holder C) To create a time-lock on token transfers D) To reduce the gas cost of token transfers
Q8. What does a view function guarantee?
A) The function does not read or modify state B) The function reads state but does not modify it C) The function can only be called by the contract owner D) The function returns a value visible on Etherscan
Q9. Consider the following code:
User storage user = users[0];
user.name = "Alice";
What does this code do?
A) Creates a copy of users[0] in memory and modifies the copy
B) Creates a reference to users[0] in storage and modifies the original data
C) Creates a temporary variable that is discarded after the function ends
D) Causes a compilation error because you cannot assign storage to a local variable
Q10. Which error-handling mechanism should you use for validating user input in Solidity?
A) assert — because it consumes all remaining gas on failure
B) require — because it reverts with a message and refunds remaining gas
C) try/catch — because it gracefully handles all error types
D) revert is the only option for input validation
Q11. Why should you NOT use transfer() or send() to send Ether in modern Solidity?
A) They are deprecated and removed from the language B) They forward only 2,300 gas, which may not be enough for the recipient contract C) They do not return a success/failure boolean D) They bypass the contract's receive function
Q12. What is the gas cost difference between writing to a previously-zero storage slot versus a non-zero slot?
A) Zero-to-nonzero costs approximately 20,000 gas; nonzero-to-nonzero costs approximately 5,000 gas B) Both cost exactly 5,000 gas C) Zero-to-nonzero costs 5,000 gas; nonzero-to-nonzero costs 20,000 gas D) Storage writes always cost exactly 20,000 gas regardless of the previous value
Q13. In the SimpleVoting contract, why is the voting weight determined by the voter's token balance at the time of voting rather than at proposal creation?
A) It is a gas optimization that avoids storing historical balances B) It is a deliberate simplification; a production contract would use snapshots to prevent vote manipulation C) The ERC-20 standard does not support balance snapshots D) Checking balance at proposal creation would require more than one transaction
Q14. What does the _; (underscore) represent in a Solidity modifier?
A) A wildcard that matches any function parameter B) The point where the modified function's body is inserted and executed C) A placeholder for the return value D) A syntax marker that signals the end of the modifier
Q15. Which of the following is NOT part of the ERC-20 standard?
A) transfer(address to, uint256 amount)
B) approve(address spender, uint256 amount)
C) mint(address to, uint256 amount)
D) transferFrom(address from, address to, uint256 amount)
Short Answer
Q16. Explain the difference between require and assert in Solidity. When should each be used? Provide one example scenario for each.
Q17. A developer writes the following state variable declarations:
uint128 a;
uint256 b;
uint128 c;
This uses 3 storage slots. Rewrite the declarations to use only 2 storage slots and explain why the reordering helps.
Q18. Why are Solidity mappings not iterable? Explain the underlying storage mechanism that makes iteration impossible.
Q19. Describe the ERC-20 approve race condition. A user has approved a spender for 100 tokens and now wants to change the approval to 50. Explain how the spender could exploit this and describe two mitigation strategies.
Q20. A junior developer marks a state variable as private and believes the data is hidden from the public. Explain why this belief is incorrect and what private actually controls.
Answer Key
Q1: B. The caret (^) specifies a range from the specified version up to (but not including) the next minor version.
Q2: C. Storage writes persist on the blockchain and cost 5,000-20,000 gas per slot, compared to 3 gas for memory operations.
Q3: B. External functions can only be called from outside the contract (or via this.functionName()), while public functions generate both an external and internal calling convention. External functions with calldata parameters are slightly cheaper.
Q4: B. Since Solidity 0.8.0, arithmetic operations revert on overflow and underflow by default. The unchecked block can restore wrapping behavior if needed.
Q5: C. Mappings do not track keys or support iteration. Every key maps to the default value (0, false, address(0), etc.) until explicitly set.
Q6: B. Up to three indexed parameters become log "topics" that can be efficiently filtered by clients and block explorers without scanning every log entry.
Q7: B. The approve/transferFrom pattern allows a third party (like a DEX or lending protocol) to move tokens on behalf of the holder, enabling composability across DeFi.
Q8: B. A view function can read state variables but cannot modify them. Calls to view functions from outside the blockchain (not in a transaction) are free.
Q9: B. User storage user creates a reference (pointer) to the storage location. Modifications through this reference directly change the contract's permanent state.
Q10: B. require is the standard mechanism for input validation. It reverts with a human-readable message and refunds unused gas to the caller.
Q11: B. transfer() and send() forward only 2,300 gas to the recipient. After the Istanbul hard fork increased certain opcode costs, this may not be enough for recipient contracts. The recommended pattern is call{value: amount}("").
Q12: A. Writing to a previously-zero slot (SSTORE from zero to nonzero) costs approximately 20,000 gas. Writing to a nonzero slot costs approximately 5,000 gas. This difference drives many gas optimization strategies.
Q13: B. Checking balance at vote time is a simplification. A sophisticated attacker could buy tokens, vote, then sell. Production governance contracts use snapshot mechanisms (like ERC-20 Votes) to record balances at proposal creation time.
Q14: B. The _; marks where the modified function's body is inserted. Code before _; runs as a precondition; code after _; runs as a postcondition.
Q15: C. mint is not part of the ERC-20 standard. The standard defines only totalSupply, balanceOf, transfer, allowance, approve, and transferFrom. Minting is an implementation choice.
Q16: require is for validating external inputs and preconditions (e.g., "Does the user have enough balance?"). It reverts with a message and refunds gas. assert is for checking internal invariants that should never be false (e.g., "After a transfer, total supply should not change"). An assert failure indicates a bug in the contract logic, not invalid user input.
Q17: Reorder to: uint128 a; uint128 c; uint256 b;. The compiler packs a and c into slot 0 (16 + 16 = 32 bytes), and b occupies slot 1. The original order cannot pack a and c together because b (32 bytes) forces a new slot boundary.
Q18: Mapping values are stored at keccak256(key . slot) where slot is the mapping's position. There is no linked list, array, or other structure connecting the keys. The 2^256 address space is too large to enumerate, and the contract has no record of which keys have been set.
Q19: Alice approves the spender for 100 tokens. She submits a transaction to change the approval to 50. The spender sees Alice's pending transaction in the mempool and front-runs it by calling transferFrom for the original 100 tokens. After Alice's approval change is mined, the spender calls transferFrom again for 50 tokens, spending 150 total. Mitigations: (1) Approve to 0 first, then approve to the new amount (two transactions). (2) Use increaseAllowance/decreaseAllowance functions that adjust relative to the current value.
Q20: private only restricts which contracts can call the function or read the variable at the Solidity level. It does not hide data on the blockchain. All storage slot values are publicly readable by anyone who queries the Ethereum node's storage directly (e.g., via eth_getStorageAt). Private data on a public blockchain is an oxymoron.