Contract 1: Bank Withdrawal System

1. Primary Vulnerability
CRITICAL - Reentrancy Attack

Correct Answer: The contract is vulnerable to reentrancy because it sends ETH to the caller before updating the balance. The vulnerable pattern is: check → external call → state update (should be: check → state update → external call).

// VULNERABLE CODE: function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); (bool success, ) = msg.sender.call{value: amount}(""); // ❌ External call FIRST balances[msg.sender] -= amount; // ❌ State update AFTER }
Grading (6 points):
  • Full credit: Identifies reentrancy, explains external call before state update
  • Partial credit (4 pts): Identifies reentrancy but vague explanation
  • Partial credit (2 pts): Notes "security issue" without specificity
  • No credit: Incorrect vulnerability or no answer
2. Exploitation Steps
Attack Scenario:
  1. Setup: Attacker deploys malicious contract with fallback/receive function that calls withdraw()
  2. Initial Deposit: Attacker deposits 1 ETH into Bank contract
  3. First Withdrawal: Attacker calls withdraw(1 ETH)
  4. Balance Check Passes: Contract verifies attacker has 1 ETH (✓ passes)
  5. ETH Sent: Contract sends 1 ETH to attacker, triggering malicious fallback
  6. Reentrant Call: Fallback function calls withdraw(1 ETH) again
  7. Balance Still 1 ETH: Balance hasn't been updated yet, check passes again
  8. Loop Continues: Attacker withdraws repeatedly until Bank is drained
  9. Final State Update: Original balance update executes (too late)

Example Code:

contract Attacker { Bank public bank; uint256 public attackCount; receive() external payable { if (address(bank).balance >= 1 ether && attackCount < 10) { attackCount++; bank.withdraw(1 ether); // Reenter! } } function attack() external payable { bank.deposit{value: 1 ether}(); bank.withdraw(1 ether); // Start the attack } }
Grading (8 points):
  • Full credit: Step-by-step attack with fallback function explanation
  • 7-6 pts: Clear steps but missing technical details (e.g., fallback mechanism)
  • 5-4 pts: General idea but vague ("attacker steals funds repeatedly")
  • 3-0 pts: Incorrect attack vector or minimal effort
3. Impact Severity
✓ CRITICAL

Justification: Allows complete drainage of all contract funds with minimal setup. Historical precedent: The DAO hack (2016) exploited identical vulnerability for $60M loss.

Grading (2 points): CRITICAL (2 pts), HIGH (1 pt), others (0 pts)
4. Proposed Fix
Solution 1: Checks-Effects-Interactions Pattern
// FIXED CODE: function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; // ✅ Update state FIRST (bool success, ) = msg.sender.call{value: amount}(""); require(success); }
Solution 2: Reentrancy Guard
bool private locked; modifier noReentrant() { require(!locked, "No reentrancy"); locked = true; _; locked = false; } function withdraw(uint256 amount) public noReentrant { require(balances[msg.sender] >= amount); (bool success, ) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] -= amount; }
Solution 3: Pull Payment Pattern
mapping(address => uint256) public pendingWithdrawals; function initiateWithdrawal(uint256 amount) public { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; pendingWithdrawals[msg.sender] += amount; } function completeWithdrawal() public { uint256 amount = pendingWithdrawals[msg.sender]; pendingWithdrawals[msg.sender] = 0; (bool success, ) = msg.sender.call{value: amount}(""); require(success); }
Grading (6 points):
  • Full credit: Correct fix with explanation (any valid solution)
  • 4 pts: Partially correct (e.g., mentions state update but no code)
  • 2 pts: Wrong approach but shows understanding
5. Bug Bounty Valuation
Sample Calculation:
Estimated TVL at Risk: $50,000,000
Severity Factor (CRITICAL): 1.0
Base Bounty (TVL × Factor × 10%): $5,000,000
Typical Actual Payout: $500,000 - $2,000,000

Note: Most protocols cap bounties at $500K-$1M even for critical issues. Immunefi reports median critical bounty is $500K.

Grading (3 points):
  • Full credit: Reasonable calculation using formula ($1M-$5M range)
  • 2 pts: Uses formula but incorrect math
  • 1 pt: Random guess without calculation
Common Student Mistakes:
  • Confusing reentrancy with overflow/underflow
  • Not explaining the fallback function mechanism
  • Suggesting "use transfer() instead" (not foolproof, limits gas)
  • Claiming LOW severity (completely missing criticality)

Contract 2: Token Ownership Manager

1. Primary Vulnerability
CRITICAL - Access Control Failure

Correct Answer: The setOwner() function lacks access control checks. Anyone can call it and become the owner, gaining unlimited minting privileges and control over the protocol.

// VULNERABLE CODE: function setOwner(address newOwner) public { owner = newOwner; // ❌ No require(msg.sender == owner) }
Grading (6 points): Full credit for identifying missing access control on setOwner
2. Exploitation Steps
Attack Scenario:
  1. Discovery: Attacker reads contract code, sees unprotected setOwner()
  2. Ownership Takeover: Attacker calls setOwner(attackerAddress)
  3. Verification: Attacker is now owner, original owner lost control
  4. Exploitation Options:
    • Mint unlimited tokens to themselves
    • Pause all transfers, locking user funds
    • Set ownership to 0x0, permanently bricking the contract
    • Mint tokens and sell on market, crashing price
  5. Impact: Complete protocol takeover, total loss of user trust and value
Grading (8 points): Full credit for clear takeover explanation with exploitation consequences
3. Impact Severity
✓ CRITICAL

Justification: Complete protocol compromise. Attacker gains god-mode privileges. Historical precedent: Multiple DeFi protocols lost millions to access control bugs (Poly Network, Cream Finance).

Grading (2 points): CRITICAL (2 pts), HIGH (1 pt)
4. Proposed Fix
Solution 1: Add Access Control Check
// FIXED CODE: function setOwner(address newOwner) public { require(msg.sender == owner, "Only owner can transfer ownership"); require(newOwner != address(0), "Invalid address"); owner = newOwner; }
Solution 2: Use Two-Step Ownership Transfer
address public pendingOwner; function transferOwnership(address newOwner) public { require(msg.sender == owner, "Only owner"); pendingOwner = newOwner; } function acceptOwnership() public { require(msg.sender == pendingOwner, "Only pending owner"); owner = pendingOwner; pendingOwner = address(0); }
Solution 3: Use OpenZeppelin Ownable
import "@openzeppelin/contracts/access/Ownable.sol"; contract TokenManager is Ownable { // Ownable handles all access control // Use onlyOwner modifier on privileged functions }
Grading (6 points): Any solution adding require(msg.sender == owner) gets full credit
5. Bug Bounty Valuation
Sample Calculation:
Estimated TVL at Risk: $20,000,000
Severity Factor (CRITICAL): 1.0
Estimated Bug Bounty: $2,000,000 (capped at $500K-$1M typically)
Grading (3 points): Accept any calculation in $500K-$2M range

Contract 3: Simple Token Transfer

1. Primary Vulnerability
HIGH - Integer Underflow

Correct Answer: The transfer function lacks balance validation before subtraction. If a user with 0 tokens transfers 1 token, underflow wraps to maximum uint256 value, creating tokens from nothing.

// VULNERABLE CODE: function transfer(address to, uint256 amount) public { balances[msg.sender] -= amount; // ❌ No check if balance >= amount balances[to] += amount; } // Attack: User with balance 0 transfers 1 token // balances[user] = 0 - 1 = 2^256 - 1 (underflow!)

Note: This vulnerability only exists in Solidity versions prior to 0.8.0. In 0.8.0+, this would revert automatically.

Grading (6 points):
  • Full credit: Identifies integer underflow with explanation
  • 4 pts: Identifies "missing balance check" without underflow concept
  • 2 pts: Vague "can transfer more than they have"
2. Exploitation Steps
Attack Scenario:
  1. Setup: Attacker creates new account with 0 token balance
  2. Underflow Trigger: Attacker calls transfer(victimAddress, 1)
  3. Math Underflow: 0 - 1 wraps around to 2^256 - 1 (max uint256)
  4. God Mode: Attacker now has ~115 quattuorvigintillion tokens
  5. Exploitation: Attacker can:
    • Transfer unlimited tokens to other accounts
    • Dump tokens on DEX, draining all liquidity
    • Manipulate governance (if tokens grant voting power)
    • Destroy token economics and price
// Example Attack SimpleToken token = SimpleToken(0x123...); // Attacker has 0 tokens console.log(token.balanceOf(attacker)); // 0 // Transfer 1 token (will underflow) token.transfer(victim, 1); // Attacker now has max tokens console.log(token.balanceOf(attacker)); // 115792089237316195423570985008687907853269984665640564039457584007913129639935
Grading (8 points):
  • Full credit: Explains underflow math (0 - 1 = max value)
  • 6 pts: Explains exploitation without underflow math
  • 4 pts: "Can get infinite tokens" without mechanism
3. Impact Severity
✓ HIGH

Justification: Allows creation of unlimited tokens, destroying token economics. Not CRITICAL because it doesn't directly drain ETH, but still catastrophic. Historical example: BEC token (2018) had similar vulnerability, value went to zero.

Grading (2 points): CRITICAL or HIGH (2 pts), MEDIUM (1 pt)
4. Proposed Fix
Solution 1: Add Balance Check
// FIXED CODE: function transfer(address to, uint256 amount) public { require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount; balances[to] += amount; }
Solution 2: Use SafeMath (Solidity < 0.8.0)
using SafeMath for uint256; function transfer(address to, uint256 amount) public { balances[msg.sender] = balances[msg.sender].sub(amount); // Reverts on underflow balances[to] = balances[to].add(amount); }
Solution 3: Upgrade to Solidity 0.8.0+
// Solidity 0.8.0+ has built-in overflow/underflow protection // No changes needed to the function itself pragma solidity ^0.8.0; function transfer(address to, uint256 amount) public { balances[msg.sender] -= amount; // ✅ Auto-reverts on underflow balances[to] += amount; }
Grading (6 points): Any valid solution (require check, SafeMath, or Solidity 0.8.0+)
5. Bug Bounty Valuation
Sample Calculation:
Estimated TVL at Risk: $30,000,000
Severity Factor (HIGH): 0.5
Estimated Bug Bounty: $1,500,000 (capped at $250K-$750K typically)
Grading (3 points): Accept range $200K-$1.5M

Comparative Analysis Answers

1. Most Severe Vulnerability

Accept Either:

  • Contract 1 (Reentrancy): Most severe because it allows immediate, complete fund drainage with simple setup. The DAO hack demonstrated this can extract tens of millions in minutes.
  • Contract 2 (Access Control): Most severe because it grants god-mode control, allowing attacker to mint unlimited tokens, pause contracts, and permanently brick the protocol.

Key Point: Both are CRITICAL. The "most severe" depends on context - reentrancy for immediate fund loss, access control for permanent protocol compromise.

Grading (5 points):
  • Full credit: Chooses Contract 1 or 2 with solid justification
  • 3 pts: Correct choice but weak reasoning
  • 0 pts: Chooses Contract 3 (clearly less severe than others)
2. Related/Compound Vulnerabilities

Possible Connections:

  • All Three Share: State management issues - improper ordering of checks, effects, and interactions
  • Contracts 1 & 3: Both involve arithmetic on balances without proper validation
  • Compound Scenario: If Contract 2's attacker gains control, they could exploit Contracts 1 or 3 more easily (e.g., mint tokens to themselves before underflow attack)
  • Pattern: All stem from insufficient input validation and state protection
Grading (4 points): Full credit for identifying any valid connection or pattern
3. Game Theory: Attack vs. Report
Scenario Potential Profit Risk/Consequences
Exploit $10M-$50M (protocol TVL) • Federal prosecution (wire fraud, computer fraud)
• 10-20 year prison sentence
• Funds frozen/seized by authorities
• Chain analysis tracks laundering
• Lifetime reputation destruction
Report (Bug Bounty) $500K-$2M bounty • Zero legal risk
• Career enhancement
• Reputation as ethical hacker
• Future employment opportunities
• Community respect
Rational Choice REPORT via bug bounty

Reasoning: Expected value calculation:
• Exploit: $25M × 10% chance of success × 50% laundering success = $1.25M
• Report: $1M × 100% chance = $1M

When factoring in 90% chance of prison (negative $1M+ value), exploitation EV is deeply negative. Reporting is strictly dominant strategy.

Additional Factors:

  • Mechanism Design Success: Bug bounties align incentives, making reporting more profitable than attacking (when accounting for risk)
  • Social Capital: Reputation in crypto is worth millions in future opportunities
  • Moral Hazard: Existence of bounties reduces attacks by ~70% (industry estimates)
Grading (8 points):
  • Full credit: Quantitative comparison with rational choice explanation
  • 6 pts: Qualitative comparison with correct conclusion
  • 4 pts: Lists pros/cons without conclusion
  • 0 pts: Concludes exploitation is rational (misses key risks)
4. Mechanism Design Insights

Key Takeaways:

  • Incentive Alignment: Bug bounties create game where reporting beats exploiting
  • Security as Economics: Code correctness alone insufficient; must incentivize security behavior
  • Adversarial Assumptions: Must design assuming rational attackers constantly probing for exploits
  • Defense in Depth: Multiple layers needed - code audits, bounties, formal verification, time-locks
  • Transparency Trade-off: Open-source code enables both security research and attacker reconnaissance
  • Time Value: Attackers move faster than auditors; bounties provide 24/7 security
  • MEV Considerations: Some vulnerabilities only exploitable with specific on-chain conditions, adding complexity
Grading (5 points):
  • Full credit: 3+ valid insights about mechanism design
  • 3 pts: 1-2 insights
  • 1 pt: Generic "security is important" statement

Bonus Contracts - Brief Answers

Contract 4: Flash Loan Lending Pool
CRITICAL - Price Manipulation via Flash Loan

Vulnerability: The withdraw function calculates user's share based on current pool balance, which can be manipulated with flash loans. Attacker can deposit, take flash loan to artificially inflate balance, withdraw larger share, then repay loan.

Fix: Track deposits separately from total balance, use share-based accounting that's immune to balance manipulation.

Contract 5: Price Oracle Aggregator
CRITICAL - Oracle Manipulation

Vulnerability: Uses spot prices from DEX pools, which can be manipulated in single transaction. Attacker can skew pool ratios with large trades, get inflated price from oracle, borrow maximum, then restore pool ratio.

Fix: Use time-weighted average prices (TWAP), multiple independent oracles, or Chainlink price feeds.

Contract 6: Time-Locked Vault
MEDIUM - Timestamp Manipulation & Logic Flaw

Vulnerabilities:

  1. Timestamp Dependence: Miners can manipulate block.timestamp within ~15 seconds
  2. Lock Extension Bug: extendLock resets unlock time from current block.timestamp, allowing users to lock funds indefinitely or reduce lock time by calling repeatedly

Fix: For extendLock, should be: unlockTime[msg.sender] += additionalTime (add to existing, not reset from now)

Bonus Grading (+10 points max):
  • Each bonus contract: +3.33 points for correct vulnerability identification and fix
  • Partial credit available for partially correct answers

© Joerg Osterrieder 2025-2026. All rights reserved.