L11: Building DeFi Study Guide

Apply your Solidity fundamentals to real DeFi patterns: constant-product AMMs, liquidity provision, escrow with timeouts, Solidity interfaces, and smart contract security.

Estimated Time: 7-10 hours across 3 weeks

Learning Objectives

By the end of this study block, you will be able to:

  • Derive the constant-product AMM formula and calculate swap output amounts
  • Explain impermanent loss and when it occurs for liquidity providers
  • Implement an escrow contract with a timeout and dispute mechanism
  • Define Solidity interfaces and use them to call external contracts safely
  • Identify the top three smart contract vulnerability classes and their mitigations
  • Write unit tests for DeFi contracts using Remix or Hardhat

Week-by-Week Study Plan

Week 1 — AMM Math and Liquidity (2-3 hours)

Read the Uniswap V2 whitepaper (pages 1-4). Derive the constant-product formula x * y = k on paper. Calculate: if a pool has 100 ETH and 300,000 USDC, what is the ETH price? If you buy 10 ETH, how much USDC do you pay (ignoring fees)?

Goal: Build the AMM from the L11 hands-on activity from memory, explaining each line as you go.

Week 2 — Escrow, Interfaces, and Cross-Contract Calls (2-3 hours)

Implement a three-party escrow: buyer deposits funds, seller completes work, arbiter releases or refunds. Add a 30-day timeout after which the buyer can reclaim funds without arbiter approval. Study how Solidity interfaces let you call external contracts without importing their full source code.

Goal: Deploy the escrow on Remix VM, simulate all three resolution paths (release, refund, timeout), and verify each path emits the correct event.

Week 3 — Security, Testing, and Advanced Resources (3-4 hours)

Work through the Ethernaut re-entrancy level (Level 10). Read the ConsenSys Smart Contract Best Practices section on known attacks. Write tests for your AMM: test that k is preserved after swaps, that adding liquidity updates reserves correctly, and that insufficient allowance reverts.

Goal: A test suite where every function in your DEX has at least one passing test and one test for the failure case.

Key Concepts to Review

Constant-Product AMM Formula

Invariant: x * y = k, where x and y are the pool reserves of token A and token B.

Swap output: If you add dx of token A, the new reserve is x + dx. The new y reserve is k / (x + dx). You receive y - k/(x+dx) of token B.

Price impact: Large trades move the price significantly. A swap of 10% of the pool has much higher price impact than 0.1%. This is why traders split large orders.

Fees in real AMMs: Uniswap V2 charges 0.3% on each trade, kept in the pool. k grows slightly after each swap. This is how LPs earn yield.

Impermanent Loss

Definition: The difference in value between holding tokens in an AMM pool vs. simply holding them in a wallet when prices change.

Cause: The AMM rebalances continuously. As token A's price rises, the pool sells A (acquires more B), so LPs end up with less of the appreciating asset compared to HODLers.

Formula: IL = 2 * sqrt(price_ratio) / (1 + price_ratio) - 1. A 2x price change causes ~5.7% IL. A 5x change causes ~25.5% IL.

When it matters: IL is "impermanent" if the price returns to the original ratio. If the price diverges permanently, the loss is permanent. Fees may offset IL for high-volume pools.

Escrow Contract Patterns

Parties: Depositor (buyer), beneficiary (seller), arbiter (trusted third party for disputes).

State machine: AWAITING_DELIVERY → COMPLETE / REFUNDED. Only valid transitions should be allowed.

Timeout: uint256 public deadline = block.timestamp + 30 days;. After deadline, buyer can call refund() without arbiter.

Pull over push: Do not push ETH to addresses. Instead, let them call a withdraw() function to pull their funds. This prevents re-entrancy attacks and failed-send issues.

Solidity Interfaces

Purpose: Define the function signatures of an external contract without importing its full code. Enables calling any contract that matches the interface.

Syntax: interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); }

Usage: IERC20(tokenAddress).transfer(recipient, amount); — casts any address as IERC20 and calls transfer.

Risk: If the address does not actually implement the interface, the call will revert or behave unexpectedly. Always validate addresses in production.

Top Smart Contract Vulnerabilities

Re-entrancy: External call made before state is updated. Attacker re-enters the function and drains funds. Fix: update state before calling external contracts (Checks-Effects-Interactions pattern). The DAO hack ($60M, 2016) was a re-entrancy attack.

Integer overflow/underflow: Arithmetic wraps around in old Solidity. Fixed by default in Solidity 0.8+. In earlier code, SafeMath library was required.

Access control errors: Missing require(msg.sender == owner) on admin functions. Any caller can drain the contract. Always specify who can call privileged functions.

Oracle manipulation: Using on-chain AMM price as a price oracle — attacker flash-loans to manipulate the price in the same transaction. Use time-weighted average prices (TWAP) from Uniswap V2/V3 or Chainlink oracles.

Practice Exercises

Exercise 1: Add slippage protection to the SimpleDEX swapAforB function. The caller should be able to specify a minAmountOut parameter. If the actual output is below this minimum, the transaction should revert with a descriptive error message.
Change the signature to: function swapAforB(uint256 amountAIn, uint256 minAmountOut) external returns (uint256 amountBOut)
After computing amountBOut, add: require(amountBOut >= minAmountOut, "Slippage: insufficient output");
Or using a custom error: error InsufficientOutput(uint256 actual, uint256 minimum); if (amountBOut < minAmountOut) revert InsufficientOutput(amountBOut, minAmountOut);
Slippage protection is critical in real DEXes — without it, a sandwich attack can front-run and back-run your transaction, extracting value.
Exercise 2: Add a timeout to your escrow contract. After 30 days, if the arbiter has not released or refunded funds, the depositor should be able to reclaim their ETH without arbiter approval. Implement the claimTimeout() function.
Add to constructor: uint256 public immutable deadline = block.timestamp + 30 days;
Add state: bool public isResolved;
In release() and refund(), set isResolved = true.
New function:
function claimTimeout() external {
  require(msg.sender == depositor, "Only depositor");
  require(block.timestamp >= deadline, "Deadline not reached");
  require(!isResolved, "Already resolved");
  isResolved = true;
  payable(depositor).transfer(address(this).balance);
}
Note: using transfer() is acceptable for ETH since Solidity 0.8+ reverts on failure. For ERC-20 escrows, use the pull pattern instead.
Exercise 3: Write a Remix unit test for the SimpleDEX that verifies the constant-product invariant k = reserveA * reserveB is preserved (to within rounding) after a swap. What is the expected behavior due to integer division?
In your test, compute k_before = reserveA * reserveB before the swap and k_after = reserveA * reserveB after. Due to integer division in Solidity, k_after may be slightly larger than k_before (rounding always rounds down, which means the pool retains a tiny extra amount).
Test assertion: Assert.ok(k_after >= k_before, "k decreased after swap");
A key insight: integer division means the AMM is slightly biased in favor of the pool (rounding always benefits the pool, never the trader). This is intentional — it prevents k from decreasing due to rounding, which would be an exploit vector.
Exercise 4 (Advanced): The SimpleDEX has a re-entrancy risk if TokenA or TokenB is a malicious contract that calls back into the DEX during transfer(). Identify exactly which line is vulnerable and explain how to fix it using the Checks-Effects-Interactions pattern.
The vulnerable line in swapAforB:
tokenB.transfer(msg.sender, amountBOut); // <-- external call
This is called AFTER reserveB -= amountBOut, which looks correct — but tokenA.transferFrom() is called first, and a malicious token could re-enter swapAforB during that call, when reserveA has not yet been updated.
Fix — Checks-Effects-Interactions:
1. CHECKS: verify inputs (done by require statements)
2. EFFECTS: update all state variables first (reserveA +=, reserveB -=)
3. INTERACTIONS: make external calls last (tokenA.transferFrom, tokenB.transfer)
An alternative: add a nonReentrant modifier using a boolean lock. OpenZeppelin's ReentrancyGuard provides this — inherit from it and add nonReentrant to functions making external calls.

Advanced Resources

Source Code

Security

DeFi Architecture

  • Uniswap V2 Whitepaper PAPER

    4-page whitepaper. Covers price oracles (TWAP), flash swaps, and the fee mechanism. Readable without heavy math background.

  • Aave Developer Docs — Protocol Overview DOCS

    Overview of a lending protocol architecture — how interest rates, liquidations, and collateral ratios are implemented in contracts.

Self-Check Questions

Test your understanding before completing the course

  • If an AMM pool has 1,000 of token A and 4,000 of token B, what is the implied price of A in terms of B? If you buy 100 A, exactly how much B do you pay (using x*y=k, ignoring fees)?
  • What is impermanent loss? Give a specific numerical example showing when an LP loses compared to simply holding.
  • Why does the escrow contract use the "pull over push" pattern for returning funds? What attack does this prevent?
  • What is the Checks-Effects-Interactions pattern? Give an example of code that violates it and explain the attack.
  • Why can't you use an on-chain AMM price directly as a price oracle in a DeFi protocol? What should you use instead?
  • What does a Solidity interface enable? How does it differ from importing the full contract?
  • After adding a swap fee (e.g., 0.3%) to the AMM, does the invariant k = x*y still hold exactly? If not, how does k change?

If you can answer all of these, you have completed the Cryptoeconomics course. Congratulations — you now have the conceptual and practical foundations to read DeFi source code, analyze protocol economics, and contribute to the blockchain ecosystem.

Home Lessons Quizzes Glossary © Joerg Osterrieder 2025-2026. All rights reserved.