Chapter 15 Exercises: Smart Contract Security
Exercise 1: Identify the Vulnerability (Conceptual)
Review the following Solidity function and identify all vulnerabilities present. For each vulnerability, explain the attack vector and propose a fix.
contract TokenSale {
mapping(address => uint256) public balances;
uint256 public tokenPrice = 1 ether;
address public owner;
constructor() {
owner = msg.sender;
}
function buyTokens(uint256 amount) external payable {
require(msg.value == amount * tokenPrice, "Wrong ETH amount");
balances[msg.sender] += amount;
}
function withdraw() external {
uint256 amount = balances[msg.sender];
(bool success, ) = msg.sender.call{value: amount * tokenPrice}("");
require(success, "Transfer failed");
balances[msg.sender] = 0;
}
function setPrice(uint256 newPrice) external {
tokenPrice = newPrice;
}
function emergencyWithdraw() external {
(bool success, ) = owner.call{value: address(this).balance}("");
require(success);
}
}
Expected vulnerabilities to identify:
1. Reentrancy in withdraw() (state updated after external call)
2. Missing access control on setPrice() (anyone can change the price)
3. Missing access control on emergencyWithdraw() (anyone can drain the contract)
4. Integer overflow risk in amount * tokenPrice multiplication (though Solidity 0.8+ would revert, this can cause unexpected reverts for large amounts)
5. No events emitted for monitoring
Exercise 2: Write a Reentrancy Exploit (Coding)
Given the VulnerableVault contract from the chapter text, write a complete attacker contract that exploits the reentrancy vulnerability. Your attacker contract should:
- Accept an initial deposit amount in its constructor.
- Have an
attack()function that deposits into the vault and then triggers the exploit. - Have a
receive()function that re-enters the vault'swithdraw(). - Include a mechanism to stop the recursion (checking the vault's balance).
- Have a
collectProfit()function for the attacker to withdraw stolen funds.
Test your understanding by tracing through the execution: how many times does withdraw() execute if the vault has 10 ETH and the attacker deposits 1 ETH?
Exercise 3: Flash Loan Attack Analysis (Analytical)
The Beanstalk exploit is described in Section 15.2.3. Answer the following questions:
- Why did the attacker need a flash loan rather than simply buying governance tokens on the open market?
- What specific property of the governance system made it vulnerable to a flash loan attack?
- If Beanstalk had implemented snapshot-based voting (balances recorded at a past block), would this attack have been possible? Why or why not?
- If Beanstalk had implemented a 48-hour timelock on proposal execution, would this attack have been possible? Why or why not?
- Design a governance system that is resistant to flash loan attacks. Specify at least three mechanisms.
Exercise 4: Oracle Manipulation Scenario (Analytical)
A DeFi lending protocol uses Uniswap V2 spot prices as its price oracle. The protocol allows users to deposit Token X as collateral and borrow USDC against it. The collateralization ratio is 150% (users can borrow up to 66.7% of their collateral value).
An attacker has identified that the Uniswap pool for Token X / USDC has relatively low liquidity ($2 million total).
- Design a flash loan attack that exploits this oracle. Describe each step of the attack transaction.
- Calculate the approximate profit if the attacker can temporarily double the price of Token X using a $5 million flash loan.
- What would happen if the protocol used a Chainlink price feed instead of Uniswap spot price?
- What would happen if the protocol used a 30-minute TWAP from Uniswap V3?
- What is the maximum amount the attacker could steal, and what limits it?
Exercise 5: MEV Sandwich Attack (Analytical)
A user submits a transaction to swap 100 ETH for USDC on Uniswap V2 with a 1% slippage tolerance. The current price is 2,000 USDC/ETH.
- Explain how a searcher constructs a sandwich attack around this transaction.
- What is the maximum profit the searcher can extract, given the 1% slippage tolerance?
- How does reducing the slippage tolerance affect the sandwich attack profitability? What is the tradeoff for the user?
- If the user submits the transaction through Flashbots Protect instead of the public mempool, can the sandwich attack still occur? Why or why not?
- If the user uses CoW Protocol (batch auction) instead of Uniswap, can the sandwich attack still occur? Why or why not?
Exercise 6: Audit Report Writing (Practical)
Review the following contract and write a professional audit finding for the most critical vulnerability. Your finding should include: - Title - Severity (Critical / High / Medium / Low / Informational) - Description - Impact - Proof of concept (describe the attack steps) - Recommendation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleLending {
IERC20 public token;
mapping(address => uint256) public deposits;
mapping(address => uint256) public borrows;
uint256 public totalDeposits;
uint256 public totalBorrows;
constructor(address _token) {
token = IERC20(_token);
}
function deposit(uint256 amount) external {
token.transferFrom(msg.sender, address(this), amount);
deposits[msg.sender] += amount;
totalDeposits += amount;
}
function borrow(uint256 amount) external {
require(deposits[msg.sender] >= amount * 2, "Insufficient collateral");
borrows[msg.sender] += amount;
totalBorrows += amount;
token.transfer(msg.sender, amount);
}
function repay(uint256 amount) external {
token.transferFrom(msg.sender, address(this), amount);
borrows[msg.sender] -= amount;
totalBorrows -= amount;
}
function withdraw(uint256 amount) external {
require(deposits[msg.sender] >= amount, "Insufficient deposits");
deposits[msg.sender] -= amount;
totalDeposits -= amount;
token.transfer(msg.sender, amount);
}
function liquidate(address borrower) external {
require(deposits[borrower] < borrows[borrower] * 2, "Not liquidatable");
uint256 seized = deposits[borrower];
deposits[borrower] = 0;
borrows[borrower] = 0;
token.transfer(msg.sender, seized);
}
}
Exercise 7: Slither Practice (Tool-Based)
Install Slither and run it on the ReentrancyVulnerable.sol contract from this chapter's code directory.
- What findings does Slither report?
- For each finding, classify it as a true positive (real vulnerability) or false positive (not actually exploitable).
- Run Slither on
ReentrancyFixed.sol. Does it still report any findings? If so, are they valid concerns? - Explore Slither's printers by running
slither --print contract-summaryon both files. What information does the contract summary provide that is useful for an audit?
Exercise 8: Design a Secure Vault (Coding)
Design and implement a secure ETH vault contract that incorporates all the security patterns discussed in this chapter. Your contract must:
- Allow users to deposit and withdraw ETH.
- Use the checks-effects-interactions pattern in all functions.
- Include a ReentrancyGuard.
- Implement role-based access control (owner and guardian roles).
- Include a pause mechanism for emergencies.
- Emit events for all state changes.
- Include a withdrawal delay (users must request a withdrawal, wait 24 hours, then claim it).
- Set a maximum single-withdrawal limit.
- Have comprehensive NatSpec documentation.
Write the contract in Solidity ^0.8.20 using OpenZeppelin libraries where appropriate.
Exercise 9: The DAO Hack Timeline (Research)
Research the DAO hack using primary sources (Ethereum Foundation blog posts, the original DAO smart contract code on Etherscan, block explorer data) and construct a detailed timeline:
- When was The DAO deployed?
- How much ETH was raised, and over what period?
- When did the first warnings about the vulnerability appear?
- When did the attack begin, and what was the first exploit transaction hash?
- What was the community response in the hours after the attack began?
- When was the soft fork proposed, and why was it abandoned?
- When was the hard fork executed, and at what block number?
- What happened to the attacker's funds?
- When did Ethereum Classic trading begin?
- What was the long-term impact on Ethereum governance?
Exercise 10: Comparative Exploit Analysis (Advanced)
Choose two major smart contract exploits from the following list and write a comparative analysis (500-800 words):
- The DAO (2016)
- Parity Wallet (2017)
- Cream Finance (2021)
- Wormhole (2022)
- Beanstalk (2022)
- Euler Finance (2023)
- Curve/Vyper (2023)
For each exploit, identify: - The vulnerability class - The root cause - The attack mechanism - The financial impact - Whether the funds were recovered - What defensive measures would have prevented it
Then compare the two exploits: what do they have in common, and what is fundamentally different about them? What does the comparison reveal about the evolution (or lack of evolution) of smart contract security?