Exercises: Solidity Programming
Conceptual Exercises
Exercise 13.1: Data Type Selection
For each of the following values, identify the most appropriate Solidity data type and explain why:
a) A user's Ethereum wallet address b) The number of tokens in a user's balance (with 18 decimal places) c) Whether a contract is paused d) A proposal's current state (pending, active, passed, failed, executed) e) A mapping from user addresses to their voting history f) A fixed 32-byte hash of a document
Expected depth: For each answer, specify the exact type (e.g., uint8 vs. uint256), justify the choice in terms of gas cost and semantic correctness, and note any edge cases or alternatives.
Exercise 13.2: Visibility Analysis
Consider the following contract:
contract Treasury {
uint256 public balance;
address private admin;
mapping(address => bool) internal approvedSpenders;
function deposit() public payable { ... }
function withdraw(uint256 amount) external { ... }
function _validateSpender(address spender) internal view returns (bool) { ... }
function setAdmin(address newAdmin) private { ... }
}
a) Which functions can be called by an external user sending a transaction?
b) If a contract TreasuryChild is Treasury, which functions and state variables can TreasuryChild access?
c) Can the admin state variable be read by anyone, despite being private? Explain.
d) Why might withdraw be external rather than public? What is the gas implication?
Exercise 13.3: Storage vs. Memory vs. Calldata
Explain what happens in each of the following code snippets. For each, state whether the modification affects the contract's permanent state and why.
// Snippet A
function modifyA() external {
User storage user = users[0];
user.name = "Alice";
}
// Snippet B
function modifyB() external {
User memory user = users[0];
user.name = "Bob";
}
// Snippet C
function modifyC(string calldata newName) external {
// Can we do: newName = "Charlie"?
// Why or why not?
}
Exercise 13.4: Event Design
A decentralized marketplace contract allows users to list items for sale, purchase items, and cancel listings. Design a complete set of events for this contract. For each event:
a) Name the event and define its parameters
b) Decide which parameters should be indexed and justify your choices
c) Explain how a frontend application would use each event
Exercise 13.5: The Approve/TransferFrom Pattern
Draw a sequence diagram showing all the function calls, state changes, and event emissions that occur in the following scenario:
- Alice has 1,000 VOTE tokens
- Alice approves a DEX contract to spend 500 VOTE tokens
- The DEX transfers 200 VOTE tokens from Alice to Bob
- Alice reduces the DEX's allowance to 100 tokens
For each step, list: (a) who is msg.sender, (b) which function is called, (c) which state variables change, (d) which events are emitted.
Coding Exercises
Exercise 13.6: SimpleStorage Contract
Write a complete Solidity contract called SimpleStorage that:
a) Stores a uint256 value that can be set and retrieved
b) Keeps a history of all previous values in a dynamic array
c) Emits an event every time the value changes, including the old value, new value, and the address that changed it
d) Has an owner who is the only one allowed to set the value (use a custom modifier)
e) Includes a getHistory function that returns the entire history array
Compile and test your contract using Hardhat. Write at least three test cases.
Exercise 13.7: Extend the ERC-20 Token
Starting from the VotingToken contract in this chapter, add the following features:
a) A burn function that allows any token holder to destroy their own tokens (reduce their balance and the total supply)
b) An increaseAllowance function that adds to an existing allowance (mitigating the approve race condition)
c) A decreaseAllowance function that subtracts from an existing allowance (reverting if the result would be negative)
d) A cap (maximum total supply) that is set in the constructor and enforced in the _mint function
Write tests for each new function, including edge cases (burning more than your balance, decreasing allowance below zero, minting beyond the cap).
Exercise 13.8: Voting Contract Enhancements
Extend the SimpleVoting contract with the following features:
a) A quorum parameter: proposals only pass if the total votes (for + against) exceed a minimum threshold
b) An abstain vote option (neither for nor against, but counts toward quorum)
c) A getProposalState function that returns an enum: Pending, Active, Passed, Failed, Executed
d) An event emitted when a proposal's state changes
Write comprehensive tests including: creating a proposal, voting with multiple accounts, testing quorum requirements, and testing the state transition logic.
Exercise 13.9: Multi-Signature Wallet
Build a simplified multi-signature wallet contract that:
a) Is initialized with a list of owner addresses and a required number of confirmations b) Allows any owner to submit a transaction (recipient address and ETH amount) c) Allows owners to confirm a pending transaction d) Automatically executes the transaction when the required number of confirmations is reached e) Emits events for submission, confirmation, and execution
This exercise synthesizes almost every concept from the chapter: structs, mappings, arrays, modifiers, events, require/revert, and Ether transfer.
Hints:
- Use a Transaction struct with fields for to, value, executed, and confirmationCount
- Use a nested mapping mapping(uint256 => mapping(address => bool)) to track who confirmed each transaction
- Use a modifier onlyOwner that checks if msg.sender is in the owners list
Exercise 13.10: Gas Optimization Challenge
The following contract is functionally correct but gas-inefficient. Identify at least five gas optimization opportunities and rewrite the contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Inefficient {
uint256 public count;
bool public isActive;
uint256 public lastUpdate;
bool public isPaused;
uint256 public total;
string[] public names;
mapping(string => uint256) public scores;
function addScore(string memory name, uint256 score) public {
require(isActive == true, "Not active");
require(isPaused == false, "Paused");
names.push(name);
scores[name] = score;
count = count + 1;
total = total + score;
lastUpdate = block.timestamp;
}
function getAverageScore() public view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < names.length; i++) {
sum = sum + scores[names[i]];
}
return sum / names.length;
}
function getAllNames() public view returns (string[] memory) {
return names;
}
}
For each optimization, explain: (a) what you changed, (b) why it saves gas, and (c) the approximate gas savings.
Challenge Problems
Exercise 13.11: Token Vesting Contract
Design and implement a token vesting contract that:
a) Allows an admin to create a vesting schedule for a beneficiary (start time, duration, total amount, cliff period)
b) Tokens are released linearly after the cliff period
c) The beneficiary can call release() at any time to claim all vested but unreleased tokens
d) The admin can revoke a vesting schedule (only unvested tokens are returned to admin)
e) Multiple vesting schedules can exist for the same beneficiary
This is a real-world contract pattern used by virtually every crypto project to distribute tokens to team members and investors. Think carefully about: timestamp handling, the math for linear vesting, and what happens when a schedule is revoked mid-way through.
Exercise 13.12: Critical Analysis
Read the ERC-20 standard (EIP-20) at eips.ethereum.org. Write a 500-word analysis addressing:
a) What are the known limitations of the standard? (Hint: token loss to contracts, the approve race condition, lack of callback mechanism) b) How does ERC-223 attempt to address the token-loss problem? c) How does ERC-777 address the callback problem? d) Why does the ecosystem still overwhelmingly use ERC-20 despite its known flaws? e) What does this tell us about the role of network effects in technical standards?