Chapter 33 Exercises: dApp Development
Exercise 1: Architecture Diagramming (Conceptual)
Draw a complete architecture diagram for a decentralized NFT marketplace. Identify each layer of the dApp stack and explain the responsibilities of each component:
- What smart contracts are needed? (At minimum: an NFT contract, a marketplace contract, and potentially a royalty registry.)
- What data should be stored on-chain vs. off-chain? Justify each decision with a cost analysis.
- How would you use IPFS for NFT metadata? What happens if the pinning service goes down?
- What events would your subgraph index? Write the GraphQL schema.
- What wallet interactions does the user experience? List every transaction the user must sign for a complete buy/sell flow.
Deliverable: A written document (minimum 500 words) with a diagram and explanations for each layer. Compare your architecture to OpenSea's architecture if you can find public documentation.
Exercise 2: Frontend Error Handling (Code)
The following ethers.js code has poor error handling. Rewrite it with comprehensive error handling that covers all failure modes:
async function vote(proposalId, support) {
const signer = await provider.getSigner();
const governor = new ethers.Contract(GOVERNOR_ADDRESS, governorABI, signer);
const tx = await governor.castVote(proposalId, support);
await tx.wait();
alert("Vote cast!");
}
Your rewritten version should: - Handle the case where MetaMask is not installed. - Handle the case where the user rejects the transaction in MetaMask. - Handle the case where the transaction reverts on-chain (e.g., voting period ended). - Handle the case where the user has insufficient ETH for gas. - Handle network errors (RPC provider down or unreachable). - Display a loading state while the transaction is pending. - Provide a link to Etherscan for the pending transaction. - Recover gracefully from any error without breaking the UI.
Exercise 3: IPFS Content Verification (Code)
Write a JavaScript function that: 1. Fetches proposal metadata from an IPFS gateway given a CID. 2. Verifies that the content hash matches the CID (i.e., the gateway did not serve tampered content). 3. Returns the parsed metadata if verification succeeds, or throws an error if it fails.
Use the multihashes and crypto libraries (or ethers.js utilities) to compute the hash. Research how CIDv0 and CIDv1 encode the hash differently.
Bonus: Write a version that falls back to a second IPFS gateway if the first one is unreachable, and a third if the second fails.
Exercise 4: Testing Edge Cases (Code)
Write Hardhat tests for the following edge cases in the VotingDApp:
-
Double voting: Verify that a user who has already voted on a proposal cannot vote again. Assert that the transaction reverts with the expected error message.
-
Voting with zero power: Verify that a user who holds tokens but has not delegated (and therefore has zero voting power) can still call
castVotebut adds zero weight to the tally. -
Proposal execution before timelock expires: Verify that calling
executebefore the timelock delay has passed reverts with the expected error. -
Quorum not met: Create a scenario where votes are cast but do not meet the quorum threshold. Verify that the proposal's state transitions to
Defeatedafter the voting period ends. -
Token transfer during voting: Verify that if a user transfers their tokens to another address during an active vote, the original voter's voting power (checkpointed at the proposal's snapshot block) is preserved, and the new holder's voting power is zero for that proposal.
For each test, include a descriptive it() block, clear setup, the action being tested, and at least two assertions.
Exercise 5: Gas Optimization Analysis (Analytical)
The VotingDApp.sol contract inherits from five OpenZeppelin base contracts. Using Hardhat's gas reporter, measure the gas cost of the following operations:
| Operation | Gas Cost | USD at 30 Gwei, ETH=$3000 |
|---|---|---|
| Deploy GovernanceToken | ||
| Deploy VotingDApp | ||
| delegate() | ||
| propose() | ||
| castVote() | ||
| castVoteWithReason() (50-char reason) | ||
| queue() | ||
| execute() |
Answer the following questions:
1. Which operation is the most expensive? Why?
2. How does the gas cost of castVoteWithReason scale with the length of the reason string?
3. If the governance token has 10,000 holders who each vote on a proposal, what is the total gas cost of voting? Is this sustainable on Ethereum mainnet?
4. How would deploying on an L2 (Arbitrum, Optimism, or Base) change these economics? Research the gas cost reduction factor for L2s.
Exercise 6: Subgraph Development (Code)
Write a complete subgraph for the VotingDApp that indexes:
- All proposals (creation, voting, queueing, execution, cancellation).
- All votes (voter, support, weight, reason, timestamp).
- All delegate changes (who delegated to whom, and how much voting power transferred).
- A "GovernanceStats" entity that tracks total proposals, total votes, average participation rate, and total unique voters.
Provide:
- schema.graphql — The complete GraphQL schema with all entities and relationships.
- subgraph.yaml — The subgraph manifest specifying the contracts, events, and ABIs.
- mapping.ts — The AssemblyScript mapping functions for all event handlers.
Test your subgraph locally using graph-node and Docker before deploying.
Exercise 7: Deployment Dry Run (Practical)
Perform a complete deployment of the VotingDApp to the Sepolia testnet:
- Get Sepolia ETH from a faucet (https://sepoliafaucet.com or Alchemy's faucet).
- Configure your
.envfile with your private key, an Alchemy/Infura Sepolia RPC URL, and an Etherscan API key. - Run the deployment script.
- Verify all three contracts on Etherscan.
- Interact with the deployed contracts through Etherscan's "Write Contract" interface: - Mint tokens (if applicable) or transfer tokens to a second address. - Delegate voting power. - Create a proposal. - Vote on the proposal. - Queue and execute the proposal after the timelock delay.
- Document the entire process with screenshots and transaction hashes.
Deliverable: A report containing: - The deployed contract addresses on Sepolia. - Links to the verified contracts on Sepolia Etherscan. - Transaction hashes for each step of the governance lifecycle. - Any issues encountered and how you resolved them.
Exercise 8: Security Audit Report (Analytical)
Conduct a mini-security audit of the VotingDApp.sol and GovernanceToken.sol contracts. Your audit report should address:
- Access Control: Who can call each function? Are there any functions that should be restricted but are not?
- Reentrancy: Are there any state changes after external calls? Could any function be exploited via reentrancy?
- Flash Loan Attack Surface: Could an attacker use a flash loan to temporarily acquire governance tokens, vote, and return the tokens in the same block? How does the
ERC20Votescheckpoint mechanism defend against this? - Timelock Bypass: Is there any way to execute a proposal without going through the timelock? What if the deployer forgot to revoke their admin role?
- Governance Griefing: Could an attacker spam proposals to fill the proposal queue? Is there a proposal threshold that mitigates this?
- Centralization Risks: After deployment, does any single address retain special privileges? If so, what damage could a compromised key cause?
For each finding, assign a severity (Critical, High, Medium, Low, Informational) and provide a recommended mitigation.
Exercise 9: Multi-Chain Deployment Strategy (Design)
Design a strategy for deploying the VotingDApp on three networks simultaneously: - Ethereum mainnet (highest security, highest cost) - Arbitrum (L2, lower cost, Ethereum security inheritance) - Base (L2, lowest cost, growing ecosystem)
Address the following: 1. Should the governance token exist on one chain or all three? If all three, how do you keep voting power synchronized? 2. How would you modify the deployment script to support multiple networks? 3. What are the tradeoffs of governing on L1 vs. L2? (Consider finality time, gas costs, bridge risks.) 4. How would you handle a scenario where a proposal passes on Arbitrum but the execution target is on Ethereum mainnet? (Research cross-chain messaging protocols like LayerZero, Axelar, or Chainlink CCIP.)
Exercise 10: Progressive Project Reflection (Essay)
Write a 750-word reflection on the progressive project that you have completed across this textbook. Address:
- Which component was the most challenging to build? Why?
- Which component taught you the most about blockchain development?
- If you were starting the project over, what would you do differently?
- What would you add to make this a production-ready governance system?
- How has your understanding of "decentralization" evolved from Chapter 2 to Chapter 33? Where does decentralization break down in the system you have built, and how would you address those centralization points?
This is not a technical exercise — it is a metacognitive exercise. The goal is to consolidate your learning and identify the areas where your understanding is strongest and where it needs further development.