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.
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
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.
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.
claimTimeout() function.
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.
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.
transfer(). Identify exactly which line is vulnerable and explain how to fix it using the Checks-Effects-Interactions pattern.
swapAforB:tokenB.transfer(msg.sender, amountBOut); // <-- external callThis 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
-
Uniswap V2 Core — UniswapV2Pair.sol
CODE
The production AMM contract. Compare with your SimpleDEX — find the fee mechanism, minimum liquidity, and TWAP price accumulator.
-
OpenZeppelin ERC-20 Implementation
CODE
The industry-standard ERC-20 base contract. After writing your own, compare line-by-line to see what safety checks were added.
Security
-
ConsenSys Smart Contract Best Practices
GUIDE
Comprehensive guide to known attack patterns and defensive coding. Read the sections on re-entrancy, integer arithmetic, and access control.
-
Smart Contract Weakness Classification Registry (SWC)
REFERENCE
Numbered registry of all known Solidity vulnerability classes with examples. Useful for auditing your own code systematically.
-
Ethernaut — Security Challenges
INTERACTIVE
Complete Levels 10 (Re-entrancy), 11 (Elevator), and 15 (Naught Coin) after finishing L11 to build practical attack/defense intuition.
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.