Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Part 3 β€” Module 3: Yield Tokenization

Difficulty: Advanced

Estimated reading time: ~35 minutes | Exercises: ~3-4 hours


πŸ“š Table of Contents

The Fixed-Rate Problem

Core Mechanism: The PT/YT Split

ERC-5115: Standardized Yield

Pendle Architecture

The Pendle AMM

Strategies & Composability

Wrap Up


πŸ’‘ The Fixed-Rate Problem

πŸ’‘ Concept: Why Yield Tokenization?

The problem: All yield in DeFi is variable. Staking APR fluctuates daily. Aave supply rates change every block. Vault yields swing with market conditions. There is no native way to lock in a fixed rate.

This matters for everyone:

  • Treasuries need predictable income (DAOs, protocols with runway)
  • Risk-averse users want staking yield without rate uncertainty
  • Speculators want leveraged exposure to yield direction
  • Market makers want to trade yield as a separate asset

Traditional finance solved this decades ago with zero-coupon bonds and interest rate swaps. DeFi had no equivalent β€” until yield tokenization.

The solution: Split any yield-bearing asset into two components:

  • PT (Principal Token) β€” claim on the underlying asset at maturity
  • YT (Yield Token) β€” claim on all yield generated until maturity

This separation creates a fixed-rate market: buying PT at a discount locks in a known return at maturity, regardless of what happens to variable rates.

Traditional Finance               DeFi Equivalent (Pendle)
─────────────────────              ─────────────────────────
Zero-coupon bond         ←→       PT (Principal Token)
Floating-rate note       ←→       YT (Yield Token)
Bond yield               ←→       Implied rate
Maturity date            ←→       Maturity date
Coupon stripping         ←→       Tokenization (splitting)

πŸ”— The Zero-Coupon Bond Analogy

A zero-coupon bond pays no interest during its life. You buy it at a discount ($970 for a $1000 face value) and receive $1000 at maturity. The difference IS your return, locked at purchase.

PT works identically:

  • Buy 1 PT-wstETH for 0.97 wstETH today
  • At maturity, redeem 1 PT for 1 wstETH
  • Return: 0.03 / 0.97 = 3.09% for the period (fixed, locked at purchase)
  • Variable rates can crash to 0% or spike to 20% β€” your return is fixed

YT is the complement β€” it captures whatever variable yield actually materializes:

  • Buy 1 YT-wstETH for 0.03 wstETH today
  • Until maturity, receive ALL staking yield on 1 wstETH
  • If actual yield > 3.09% β†’ profit (you paid 0.03 for more than 0.03 worth of yield)
  • If actual yield < 3.09% β†’ loss
  • This is leveraged yield exposure: ~33x leverage for 0.03 cost
1 wstETH deposited
       β”‚
       β”œβ”€β”€β†’ 1 PT-wstETH (buy at 0.97, redeem at 1.00)
       β”‚         β”‚
       β”‚         └──→ Fixed 3.09% return ← buyer locks this in
       β”‚
       └──→ 1 YT-wstETH (buy at 0.03, receive variable yield)
                 β”‚
                 └──→ Variable staking yield ← speculator bets on direction
                      (could be 2%, 5%, 10%...)

Invariant: PT price + YT price = 1 underlying (arbitrage-enforced)

πŸ’‘ Key insight: Yield tokenization doesn’t create yield β€” it separates existing yield into fixed and variable components, letting each participant take the side they prefer.


πŸ’‘ Core Mechanism: The PT/YT Split

πŸ’‘ Concept: How Splitting Works

The mechanism is elegant in its simplicity:

Minting (splitting):

  1. User deposits 1 yield-bearing token (e.g., 1 wstETH via SY wrapper)
  2. Contract mints 1 PT + 1 YT, both with the same maturity date
  3. The yield-bearing token stays locked in the contract

Before maturity:

  • PT trades at a discount (< 1 underlying) β€” the discount IS the implied fixed rate
  • YT has positive value β€” it represents remaining yield entitlement
  • Users can β€œunsplit”: return 1 PT + 1 YT β†’ get back 1 yield-bearing token

At maturity:

  • PT is redeemable 1:1 for the underlying
  • YT stops accruing yield and becomes worthless (value β†’ 0)
  • The β€œunsplit” option is no longer needed

After maturity:

  • PT can still be redeemed for 1 underlying (no expiry on redemption)
  • Any unclaimed YT yield can still be collected
Timeline for PT-wstETH (6-month maturity):

Time ──────────────────────────────────────────────→

T=0 (Mint)           T=3mo                 T=6mo (Maturity)
PT price: 0.970      PT price: 0.985       PT price: 1.000
YT price: 0.030      YT price: 0.010       YT price: 0.000
                                            β”‚
                                            β”œβ”€β”€ PT redeemable for 1 wstETH
                                            └── YT has paid out all yield
                                                (worthless now)

Sum always = 1.000   Sum always = 1.000    Sum = 1.000

πŸ’‘ Time decay: YT loses value as maturity approaches because there’s less time remaining to earn yield. This is exactly like options time decay (theta). The yield that hasn’t been earned yet decreases as the earning window shrinks.

πŸ” Deep Dive: Implied Rate Math

The implied rate is the annualized fixed return you lock in by buying PT at a discount. Understanding this math is fundamental to yield tokenization.

The basic relationship:

  • PT trades at a discount to the underlying
  • At maturity, PT = 1 underlying
  • The return = (1 - ptPrice) / ptPrice for the remaining period
  • Annualize to get the implied rate

Simple compounding formula (used in most DeFi implementations):

                     (1 - ptPrice)     YEAR
impliedRate =  ─────────────────  Γ—  ──────────────────
                     ptPrice          timeToMaturity

Or equivalently:

                  1                          YEAR
impliedRate = ( ─────── - 1 )  Γ—  ──────────────────
                ptPrice              timeToMaturity

Step-by-step example:

Given:
  PT price     = 0.97 underlying (3% discount)
  Maturity     = 6 months (182.5 days)

Step 1: Period return
  periodReturn = (1 - 0.97) / 0.97
               = 0.03 / 0.97
               = 0.03093 (3.09% for 6 months)

Step 2: Annualize
  impliedRate  = 0.03093 Γ— (365 / 182.5)
               = 0.03093 Γ— 2.0
               = 0.06186 (6.19% annual)

Verification: if you invest 0.97 at 6.19% for 6 months:
  0.97 Γ— (1 + 0.0619 Γ— 182.5/365) = 0.97 Γ— 1.03093 = 1.0 βœ“

The inverse β€” PT price from a target rate:

                          YEAR
ptPrice =  ───────────────────────────────────────
            YEAR + (impliedRate Γ— timeToMaturity)

Example: What PT price gives a 5% annual rate with 3 months to maturity?

ptPrice = 365 / (365 + 0.05 Γ— 91.25)
        = 365 / 369.5625
        = 0.98766

Check: (1 - 0.98766) / 0.98766 Γ— 365/91.25 = 0.01249 Γ— 4.0 = 5.0% βœ“

In Solidity (18-decimal fixed-point):

uint256 constant WAD = 1e18;
uint256 constant SECONDS_PER_YEAR = 365 days; // 31_536_000

/// @notice Calculate implied annual rate from PT price and time to maturity.
/// @param ptPriceWad PT price in WAD (e.g., 0.97e18)
/// @param timeToMaturity Seconds until maturity
function getImpliedRate(uint256 ptPriceWad, uint256 timeToMaturity)
    public pure returns (uint256)
{
    // rate = (WAD - ptPrice) * YEAR / (ptPrice * timeToMaturity / WAD)
    // Rearranged to avoid overflow:
    // rate = (WAD - ptPrice) * YEAR * WAD / (ptPrice * timeToMaturity)
    return (WAD - ptPriceWad) * SECONDS_PER_YEAR * WAD
           / (ptPriceWad * timeToMaturity);
}

πŸ’» Quick Try:

Deploy this in Remix to build intuition for PT pricing:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract PTCalculator {
    uint256 constant WAD = 1e18;
    uint256 constant YEAR = 365 days;

    /// @notice PT price β†’ implied annual rate
    function getRate(uint256 ptPrice, uint256 timeToMaturity) external pure returns (uint256) {
        return (WAD - ptPrice) * YEAR * WAD / (ptPrice * timeToMaturity);
    }

    /// @notice Target annual rate β†’ PT fair price
    function getPrice(uint256 annualRate, uint256 timeToMaturity) external pure returns (uint256) {
        return YEAR * WAD / (YEAR + annualRate * timeToMaturity / WAD);
    }
}

Deploy and try:

  • getRate(0.97e18, 182.5 days) β†’ should return ~6.19% (0.0619e18)
  • getPrice(0.05e18, 91.25 days) β†’ should return ~0.9877e18 (PT at 5% with 3 months left)
  • getRate(0.9999e18, 1 days) β†’ see how even a tiny discount implies a big annualized rate
  • Try getPrice(0.05e18, 1 days) β†’ PT price β‰ˆ 0.99986e18 (nearly 1.0 β€” convergence!)

This is the math that drives every Pendle market. Notice how the same rate produces wildly different prices depending on time to maturity.

Multiple maturities β€” same underlying, different rates:

wstETH Yield Tokenization Markets (hypothetical):

Maturity        PT Price    Implied Rate    Interpretation
─────────────   ────────    ────────────    ───────────────────
3 months        0.9876      5.02%           Market expects ~5% staking yield
6 months        0.9700      6.19%           Higher rate = yield might increase
1 year          0.9300      7.53%           Even higher = bullish on staking yield

A rising term structure (short < long) suggests the market expects
yields to increase over time. Sound familiar? It's a yield curve β€”
the same concept from bond markets, now in DeFi.

πŸ’‘ Continuous compounding note: Pendle internally uses a continuous compounding model: ptPrice = e^(-rate Γ— timeToMaturity). This requires ln() and exp() functions in Solidity (Pendle has custom implementations). For exercise purposes, simple compounding is accurate enough for short maturities (< 1 year) and avoids complex math libraries.

πŸ” Deep Dive: YT Yield Accumulator β€” The Pattern Returns

If you completed Module 2’s FundingRateEngine exercise, this will feel familiar. The YT yield tracking uses the exact same accumulator pattern β€” a global counter that grows over time, with per-user snapshots at entry.

The pattern across DeFi:

Protocol            Global Accumulator          Per-User Snapshot
────────────────    ──────────────────────      ──────────────────────
Compound            borrowIndex                 borrowIndex at borrow
Aave                liquidityIndex              userIndex at deposit
ERC-4626            share price (assets/shares) shares at deposit
Module 2 (Perps)    cumulativeFundingPerUnit    entryFundingIndex
Pendle (YT)         pyIndex (yield index)       userIndex at purchase

How YT yield tracking works:

The yield-bearing token’s exchange rate naturally increases over time (that’s what β€œyield-bearing” means). This exchange rate IS the accumulator:

Time        Exchange Rate       Yield Accrued (per unit)
────        ──────────────      ────────────────────────
T=0         1.000               β€”
T=1mo       1.004               0.4% (1 month of staking yield)
T=2mo       1.008               0.8%
T=3mo       1.013               1.3%
T=6mo       1.027               2.7%

The exchange rate only goes up. Each snapshot lets us compute
the yield earned between any two points in time.

Per-user yield calculation:

Alice buys YT when exchange rate = 1.004 (T=1mo)
  β†’ entryRate = 1.004

At T=4mo, exchange rate = 1.017
  β†’ yield per unit = (1.017 - 1.004) / 1.004 = 0.01295 = 1.29%
  β†’ For 100 YT: yield = 100 Γ— 0.01295 = 1.295 underlying

Bob buys YT when exchange rate = 1.013 (T=3mo)
  β†’ entryRate = 1.013

At T=4mo, exchange rate = 1.017
  β†’ yield per unit = (1.017 - 1.013) / 1.013 = 0.00395 = 0.39%
  β†’ For 100 YT: yield = 100 Γ— 0.00395 = 0.395 underlying

Each user's yield depends on WHEN they entered β€” captured by their
snapshot of the exchange rate. O(1) per calculation, no iteration.

In Solidity:

// The vault exchange rate IS the yield accumulator
// No separate index needed β€” it's already there!

function getAccruedYield(address user) public view returns (uint256) {
    Position memory pos = positions[user];
    uint256 currentRate = vault.convertToAssets(1e18); // current exchange rate

    // yield = ytBalance Γ— (currentRate - entryRate) / entryRate
    // This is: how much each unit has grown since user's entry
    uint256 yieldPerUnit = (currentRate - pos.entryRate) * WAD / pos.entryRate;
    return pos.ytBalance * yieldPerUnit / WAD;
}

πŸ’‘ The insight: In Module 2’s FundingRateEngine, you built the accumulator from scratch (computing rate Γ— time, accumulating it). Here, the vault’s exchange rate IS the accumulator β€” it’s already maintained by the underlying protocol (Lido, Aave, etc.). YT yield tracking simply snapshots this existing accumulator. Same pattern, different source.

What happens to the locked shares?

When yield is claimed from YT, the contract needs to pay out actual tokens. The math works elegantly:

At tokenization:
  100 vault shares deposited (rate = 1.0)
  β†’ 100 underlying worth of principal (PT claim)
  β†’ 100 underlying worth of yield entitlement (YT claim)
  β†’ Contract holds: 100 shares

Later (rate = 1.05):
  100 shares now worth 105 underlying
  PT claim: 100 underlying = 100/1.05 = 95.24 shares
  YT yield: 5 underlying = 5/1.05 = 4.76 shares
  Total: 95.24 + 4.76 = 100 shares βœ“

The yield comes from the shares becoming MORE valuable.
Fewer shares are needed to cover the fixed principal,
and the "excess" shares fund the yield payout.

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œExplain how Pendle creates fixed-rate products in DeFi.”

    • Good answer: β€œPendle splits yield-bearing tokens into PT (principal) and YT (yield). PT trades at a discount and can be redeemed at par at maturity, giving buyers a fixed rate.”
    • Great answer: β€œPendle wraps yield-bearing tokens into SY (ERC-5115), then splits them into PT and YT with a shared maturity. PT is a zero-coupon bond β€” buying at a discount locks in a fixed rate calculated as (1/ptPrice - 1) * year/timeToMaturity. YT captures variable yield using a global exchange rate accumulator with per-user snapshots, the same O(1) pattern as Compound’s borrowIndex. The custom AMM trades in rate-space rather than price-space, which is essential because PT must converge to 1.0 at maturity β€” something constant-product AMMs can’t handle.”
  2. β€œHow does Pendle’s YT track yield?”

    • Good answer: β€œIt uses the SY exchange rate to calculate accrued yield per holder.”
    • Great answer: β€œPendle uses the same accumulator pattern as Compound/Aave. The global pyIndexStored tracks the latest SY exchange rate. Each user has a userIndex snapshotted at purchase or last claim. Accrued yield is ytBalance * (pyIndexStored - userIndex) / userIndex β€” an O(1) calculation. Critically, YT overrides _beforeTokenTransfer to settle yield before any transfer. Without this, transferring YT would incorrectly shift accumulated yield to the recipient. This settlement-on-transfer pattern appears in every token that tracks per-holder rewards.”

Interview Red Flags:

  • 🚩 Confusing PT and YT roles (which one gives fixed rate vs variable yield exposure?)
  • 🚩 Not recognizing the accumulator pattern as the same O(1) mechanism used in Compound and Aave
  • 🚩 Thinking yield tokenization creates yield (it only separates existing yield into two components)

Pro tip: When explaining PT/YT splitting, connect it to the accumulator pattern across protocols β€” Compound’s borrowIndex, Aave’s liquidityIndex, Pendle’s pyIndex. Showing you see the same mathematical pattern in three different contexts signals deep DeFi fluency.


🎯 Build Exercise: Yield Tokenizer

Exercise 1: YieldTokenizer

Workspace:

  • Scaffold: workspace/src/part3/module3/exercise1-yield-tokenizer/YieldTokenizer.sol
  • Tests: workspace/test/part3/module3/exercise1-yield-tokenizer/YieldTokenizer.t.sol

Build the core PT/YT splitting mechanism from an ERC-4626 vault:

  • Accept vault shares β†’ internally mint PT + YT balances
  • Track yield using the vault’s exchange rate as the accumulator
  • YT holders claim accrued yield (paid out in vault shares)
  • PT holders redeem at maturity (principal value in vault shares)
  • Before maturity: β€œunsplit” by burning PT + YT balances

5 TODOs: tokenize(), getAccruedYield(), claimYield(), redeemAtMaturity(), redeemBeforeMaturity()

🎯 Goal: Implement the same accumulator pattern from Module 2’s FundingRateEngine, but now driven by an external exchange rate instead of an internal calculation.

Run: forge test --match-contract YieldTokenizerTest -vvv


πŸ“‹ Summary: Yield Tokenization Fundamentals

Covered:

  • The fixed-rate problem in DeFi and why variable yields create uncertainty
  • Zero-coupon bond analogy: buy at a discount, redeem at par at maturity
  • PT/YT split mechanism β€” separating principal and yield into tradable tokens
  • Implied rate math: how PT price and time-to-maturity determine the fixed rate
  • YT yield accumulator pattern: tracking accrued yield via exchange rate snapshots
  • Maturity mechanics: PT redemption, YT expiry, and pre-maturity unsplitting

Next: ERC-5115 (Standardized Yield) β€” Pendle’s generalization of ERC-4626 for wrapping arbitrary yield sources.


πŸ’‘ ERC-5115: Standardized Yield

πŸ’‘ Concept: SY vs ERC-4626

Pendle introduced ERC-5115 (Standardized Yield) because ERC-4626 wasn’t general enough for all yield sources.

ERC-4626 limitations:

  • Requires a single underlying asset() for deposit/withdraw
  • Assumes the vault IS the yield source
  • Some yield-bearing tokens don’t fit the vault model (e.g., stETH rebases, GLP has custom minting)

ERC-5115 (SY) extends this:

  • Supports multiple input tokens (deposit with ETH, stETH, or wstETH β†’ same SY)
  • Supports multiple output tokens (redeem to ETH or wstETH)
  • Works with any yield-bearing token regardless of its native interface
  • Standard exchangeRate() function for yield tracking
ERC-4626 (Vault):                    ERC-5115 (SY):
─────────────────                    ─────────────────
One asset in/out                     Multiple tokens in/out
deposit(assets) β†’ shares             deposit(tokenIn, amount) β†’ syAmount
redeem(shares) β†’ assets              redeem(tokenOut, syAmount) β†’ amount
asset() β†’ address                    yieldToken() β†’ address
convertToAssets(shares)              exchangeRate() β†’ uint256

Example: SY-wstETH accepts:
  β”œβ”€β”€ ETH    (auto-stakes via Lido)
  β”œβ”€β”€ stETH  (wraps to wstETH)
  └── wstETH (direct wrap)
  All produce the same SY-wstETH token.

Why SY matters for developers:

  • SY is the universal adapter layer β€” write one integration, support any yield source
  • All PT/YT markets are denominated in SY, not the raw yield token
  • exchangeRate() is the single function that drives the entire yield tokenization math

πŸ” Exchange Rate Mechanics

The SY exchange rate is the foundation of all yield calculations:

// SY-wstETH exchange rate example
function exchangeRate() external view returns (uint256) {
    // 1 SY = how much underlying?
    // For wstETH: returns stETH per wstETH (increases over time)
    return IWstETH(wstETH).stEthPerToken(); // e.g., 1.156e18
}

The exchange rate ONLY increases (for non-rebasing tokens). This monotonic growth is what makes it a natural accumulator. Note the contrast with Module 2’s cumulativeFundingPerUnit, which can move in both directions (positive during net-long skew, negative during net-short). The exchange rate is strictly monotonic β€” a simpler accumulator that never reverses.

SY-wstETH Exchange Rate Over Time:

Rate
1.20 β”‚                                          β•±
1.18 β”‚                                      ╱──╱
1.16 β”‚                                 ╱──╱─
1.14 β”‚                            ╱──╱─
1.12 β”‚                       ╱──╱─
1.10 β”‚                  ╱──╱─
1.08 β”‚             ╱──╱─
1.06 β”‚        ╱──╱─
1.04 β”‚   ╱──╱─
1.02 │╱─╱─
1.00 │─
     └──────────────────────────────────────────→ Time
     T=0    3mo    6mo    9mo    12mo   15mo

Each point on this curve is a "snapshot" opportunity.
YT yield = the vertical distance between entry and exit.

πŸ’‘ Pendle Architecture

πŸ’‘ Concept: System Overview

Pendle V2 (current version) has a clean layered architecture:

User Layer:         PendleRouter (single entry point)
                         β”‚
                    β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
                    β”‚         β”‚
Split Layer:   YieldContractFactory    PendleMarket (AMM)
                    β”‚                       β”‚
                β”Œβ”€β”€β”€β”΄β”€β”€β”€β”              β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”
                β”‚       β”‚              β”‚         β”‚
Token Layer:   PT      YT             PT        SY
                β”‚       β”‚              β”‚         β”‚
                β””β”€β”€β”€β”¬β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
                    β”‚                       β”‚
Yield Layer:       SY (ERC-5115 wrapper)    β”‚
                    β”‚                       β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
Raw Asset:            Yield-bearing token
                    (wstETH, aUSDC, sDAI...)

The flow:

  1. User deposits yield-bearing token β†’ SY wrapper creates SY token
  2. SY token β†’ YieldContractFactory splits into PT + YT (same maturity)
  3. PT trades against SY in the PendleMarket (AMM)
  4. YT accrues yield from the underlying via the SY exchange rate
  5. At maturity: PT redeemable for SY β†’ unwrap to yield-bearing token

πŸ—οΈ YieldContractFactory: Minting PT + YT

The factory creates PT/YT pairs for each (SY, maturity) combination:

// Simplified from Pendle's YieldContractFactory
function createYieldContract(address SY, uint256 expiry)
    external returns (address PT, address YT)
{
    // Each (SY, expiry) pair gets exactly one PT and one YT
    // PT address is deterministic (CREATE2)
    PT = _deployPT(SY, expiry);
    YT = _deployYT(SY, expiry, PT);
}

Maturity encoding: Pendle uses quarterly maturities (March, June, September, December) for major markets. Each maturity creates a separate market with its own implied rate. As one market approaches maturity, liquidity migrates to the next (β€œrolling” β€” same as futures markets in TradFi).

πŸ—οΈ PendleYieldToken: Yield Tracking

The YT contract maintains the yield accumulator that we discussed above:

// Simplified from PendleYieldToken
contract PendleYieldToken {
    uint256 public pyIndexStored;     // Global: last recorded exchange rate
    mapping(address => uint256) public userIndex; // Per-user: rate at last claim

    function _updateAndDistributeYield(address user) internal {
        uint256 currentIndex = SY.exchangeRate();

        if (currentIndex > pyIndexStored) {
            // Yield has accrued since last global update
            pyIndexStored = currentIndex;
        }

        uint256 userIdx = userIndex[user];
        if (userIdx == 0) userIdx = pyIndexStored; // first interaction

        if (pyIndexStored > userIdx) {
            // User has unclaimed yield
            uint256 yieldPerUnit = (pyIndexStored - userIdx) * WAD / userIdx;
            uint256 yield = balanceOf(user) * yieldPerUnit / WAD;
            // Transfer yield to user...
            userIndex[user] = pyIndexStored;
        }
    }

    // CRITICAL: yield must be settled on every transfer
    function _beforeTokenTransfer(address from, address to, uint256) internal {
        if (from != address(0)) _updateAndDistributeYield(from);
        if (to != address(0)) _updateAndDistributeYield(to);
    }
}

πŸ’‘ Why settle on transfer? If Alice transfers YT to Bob without settling, the yield Alice earned would incorrectly flow to Bob (his entry index would be lower than it should be). By settling before every transfer, each user’s accumulated yield is correctly attributed. This is the same reason Compound settles interest before any borrow/repay operation.

πŸ—οΈ PendlePrincipalToken: Maturity Redemption

PT is simpler β€” it’s essentially a zero-coupon bond token:

// Simplified from PendlePrincipalToken
contract PendlePrincipalToken {
    uint256 public expiry;
    address public SY;
    address public YT;

    function redeem(uint256 amount) external {
        require(block.timestamp >= expiry, "Not matured");
        _burn(msg.sender, amount);

        // 1 PT = 1 underlying at maturity
        // Convert to SY amount using current exchange rate
        uint256 syAmount = amount * WAD / SY.exchangeRate();
        SY.transfer(msg.sender, syAmount);
    }
}

Post-maturity behavior: PT can be redeemed at any time after maturity. There’s no penalty for late redemption. However, the PT holder foregoes any yield earned between maturity and redemption β€” that yield effectively belongs to the protocol or is distributed to other participants.

πŸ“– Code Reading Strategy for Pendle

Repository: pendle-core-v2-public

Reading order:

  1. Start with SY β€” SYBase.sol and one concrete implementation (e.g., SYWstETH.sol). Understand exchangeRate(), deposit(), redeem(). This is the yield abstraction layer.
  2. Read PT/YT minting β€” YieldContractFactory.sol. See how createYieldContract() deploys PT + YT with deterministic addresses.
  3. Study YT yield tracking β€” PendleYieldToken.sol. Focus on pyIndexStored, userIndex, and _updateAndDistributeYield(). This is the accumulator.
  4. Trace a swap β€” PendleMarketV7.sol. Start with swapExactPtForSy(). Follow the AMM curve math.
  5. Read the Router β€” PendleRouter.sol. See how user-facing functions compose the lower-level operations.

Don’t get stuck on: The AMM curve math internals (MarketMathCore.sol). The formulas involve ln() and exp() approximations that are dense. Understand the CONCEPT (rate-space trading) first, then optionally deep-dive into the math.

Key test files: test/core/Market/ β€” tests for AMM operations, especially around maturity edge cases.


πŸ’‘ The Pendle AMM

πŸ’‘ Concept: Why Constant Product Fails for PT

Standard AMMs (Uniswap’s x Γ— y = k) assume the two tokens have an independent, freely floating price relationship. PT breaks this assumption because PT has a known future value: at maturity, 1 PT = 1 underlying. Always.

The problem with x Γ— y = k:

Standard AMM pool: PT / Underlying

At T=0 (6 months to maturity):
  Pool: 1000 PT + 970 underlying (PT at 3% discount)
  Works fine β€” normal trading, reasonable slippage

At T=5.5 months (2 weeks to maturity):
  PT should trade at ~0.998 (0.2% discount for 2 weeks)
  But x*y=k still allows wide price swings
  A moderate swap could move PT price to 0.95 β€” absurd for a near-maturity asset

At maturity:
  PT MUST trade at exactly 1.0
  But x*y=k has no concept of time or convergence
  The pool would still allow trades at 0.90 or 1.10
  Massive arbitrage opportunities, broken pricing
Price
1.10 β”‚
     β”‚
1.05 β”‚                    x*y=k range at maturity
     β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
1.00 β”‚ Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·β”‚Β· Β· SHOULD Β· β”‚Β· Β· Β· Β· Β· ← PT = 1.0 here
     β”‚                    β”‚ BE HERE!    β”‚
0.95 β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
     β”‚
0.90 β”‚
     └──────────────────────────────────────→ Time
     T=0                               Maturity

Problem: x*y=k doesn't know about maturity.
It allows prices that make no economic sense.

πŸ’‘ Analogy: Imagine a bond market where the exchange allows a 1-year Treasury to trade at 50 cents on the dollar with 1 day until maturity. No rational market would allow this. But a standard AMM has no mechanism to prevent it.

πŸ’‘ Concept: Rate-Space Trading: The Key Insight

Pendle’s AMM (inspired by Notional Finance) solves this by trading in rate space instead of price space.

The insight: Instead of asking β€œwhat price should PT trade at?”, ask β€œwhat implied interest rate should the market express?” Then derive the price from the rate.

Price-space trading (standard AMM):
  "1 PT costs 0.97 underlying"
  β†’ No concept of time decay
  β†’ Wide price range even near maturity

Rate-space trading (Pendle AMM):
  "The market implies a 6.19% annual rate"
  β†’ Rate naturally has bounded behavior
  β†’ Near maturity, even large rate changes produce tiny price changes
  β†’ At maturity, any finite rate maps to price β‰ˆ 1.0

Why rate-space works:

Rate = 6.19%          Rate = 6.19%           Rate = 6.19%
Time = 6 months       Time = 1 month         Time = 1 day
────────────────      ────────────────       ────────────────
PT price = 0.970      PT price = 0.995       PT price = 0.99983

Same rate, but:
  - 6mo out β†’ 3% price discount (meaningful)
  - 1mo out β†’ 0.5% discount (small)
  - 1 day out β†’ 0.002% discount (negligible)

As maturity approaches, rate-space naturally compresses
the price range toward 1.0. The AMM doesn't need special
logic for convergence β€” it falls out of the math.

πŸ” Deep Dive: The AMM Curve

Pendle’s AMM uses a modified logit curve with time-dependent parameters. The pool contains PT and SY (not PT and underlying directly).

Conceptual formula (simplified):

                ln(ptProportion / syProportion)
impliedRate = ──────────────────────────────────── Γ— scalar
                     timeToMaturity

Where:
  ptProportion = ptReserve / totalLiquidity
  syProportion = syReserve / totalLiquidity
  scalar = amplification parameter (like Curve's A)
  timeToMaturity = seconds remaining (decreases over time)

Key properties:

  1. Time-to-maturity in the denominator: As maturity approaches, the same reserve change produces a larger rate movement. But since price = f(rate, time), and time is shrinking, the net effect is that price movements get SMALLER. The curve β€œflattens” near maturity.

  2. Scalar (amplification): Controls rate sensitivity. Higher scalar β†’ more concentrated liquidity around the current rate β†’ lower slippage for normal trades, but larger slippage for rate-moving trades. Similar to Curve Finance’s A parameter.

  3. Anchor rate: The initial implied rate at pool creation. The curve is centered around this rate. LPs implicitly express a view on rates by providing liquidity.

Pendle AMM Curve at Different Times to Maturity:

PT Price
  β”‚
1.000 │·········································╱── T=1 day
  β”‚                                        β•±     (very flat, price β‰ˆ 1.0)
0.998 │······························╱───────╱
  β”‚                           ╱─── T=1 month
0.990 │····················╱────────╱          (flatter, price β‰ˆ 0.99)
  β”‚               ╱────╱
0.980 │·········╱────────╱
  β”‚      ╱──╱            T=3 months
0.970 │──╱──╱               (moderate curve)
  β”‚ β•±β•±
0.960 β”‚β•±    T=6 months
  β”‚     (widest curve)
  └──────────────────────────────────────────→ Pool Imbalance
   More SY          Balanced          More PT

As maturity approaches:
  - Curve FLATTENS (less price sensitivity)
  - Price CONVERGES to 1.0
  - Any finite implied rate maps to PT β‰ˆ 1.0

Comparison with standard AMMs:

Feature              Uniswap V2 (x*y=k)     Curve (StableSwap)     Pendle
─────────────────    ───────────────────     ──────────────────     ──────────────
Curve shape          Hyperbola               Flat near peg          Time-dependent
Time awareness       None                    None                   Built-in
Price convergence    No                      No                     Yes (at maturity)
Rate discovery       No                      No                     Yes
Best for             Independent tokens      Pegged assets          Time-decaying assets

πŸ—οΈ LP Considerations

LPing in Pendle pools has unique properties compared to standard AMMs:

Impermanent loss dynamics:

  • In standard AMMs, IL is permanent if prices diverge
  • In Pendle pools, PT converges to 1.0 at maturity
  • This means IL DECREASES over time as the pool naturally rebalances
  • LPs in Pendle pools near maturity have almost zero IL

Triple yield for Pendle LPs:

  1. Swap fees β€” from traders buying/selling PT
  2. PT discount β€” the SY side of the pool earns yield from the underlying
  3. Underlying yield β€” the PT side also implicitly earns (it converges to 1.0)

When LP is most attractive:

  • High trading volume (fees)
  • Moderate time to maturity (enough fee income, declining IL)
  • Volatile rates (more trading, more fees)

πŸ’‘ LP convergence insight: A Pendle LP position held to maturity essentially has zero IL because both sides of the pool converge to the same value (1 SY = 1 PT = 1 underlying). This is unique among AMM designs.

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œWhy can’t you use Uniswap’s x*y=k AMM for PT trading?”

    • Good answer: β€œPT converges to 1.0 at maturity, and standard AMMs don’t account for time.”
    • Great answer: β€œx*y=k treats both assets as having independent, freely floating prices. PT has a deterministic future value β€” it equals 1 underlying at maturity. Near maturity, a standard AMM would still allow wide price swings, enabling trades at absurd discounts or premiums. Pendle’s AMM operates in rate-space: the curve uses ln(ptProportion/syProportion) / timeToMaturity * scalar, which naturally flattens as maturity approaches. This is inspired by Notional Finance’s logit curve, and the scalar parameter plays a role analogous to Curve’s amplification factor A.”
  2. β€œHow does PT pricing change as maturity approaches?”

    • Good answer: β€œPT price converges to 1.0 as maturity approaches.”
    • Great answer: β€œUsing simple compounding, ptPrice = year / (year + rate * timeToMaturity). As timeToMaturity approaches 0, ptPrice approaches 1.0 regardless of the implied rate. Even at a 100% implied rate, with 1 day to maturity, PT trades at 0.99726. This convergence is built into Pendle’s AMM curve β€” the timeToMaturity in the denominator of the rate formula means the curve naturally flattens, reducing price sensitivity of swaps. This is why Pendle LPs experience decreasing IL over time, unlike standard AMMs where IL is path-dependent and potentially permanent.”

Interview Red Flags:

  • 🚩 Not knowing why a constant-product AMM fails for PT (the time-convergence problem)
  • 🚩 Thinking Pendle’s AMM is just a Uniswap fork with different parameters
  • 🚩 Unable to explain how the scalar parameter relates to Curve’s amplification factor A

Pro tip: Pendle’s AMM is one of the most sophisticated in DeFi β€” understanding rate-space trading and the logit curve (inspired by Notional Finance) shows you can reason about purpose-built AMMs, not just x*y=k variations.


🎯 Build Exercise: PT Rate Oracle

Exercise 2: PTRateOracle

Workspace:

  • Scaffold: workspace/src/part3/module3/exercise2-pt-rate-oracle/PTRateOracle.sol
  • Tests: workspace/test/part3/module3/exercise2-pt-rate-oracle/PTRateOracle.t.sol

Build a rate oracle that computes and tracks implied rates from PT prices:

  • Calculate implied annual rate from PT price and time-to-maturity
  • Calculate PT fair value from a target annual rate
  • Record rate observations with timestamps
  • Compute Time-Weighted Average Rate (TWAR) β€” same accumulator pattern as Uniswap V2’s TWAP oracle
  • Calculate YT break-even rate for profitability analysis

5 TODOs: getImpliedRate(), getPTPrice(), recordObservation(), getTimeWeightedRate(), getYTBreakEven()

🎯 Goal: Master the implied rate math and connect rate-oracle tracking to the TWAP accumulator pattern from Part 2 Module 3 (Oracles).

Run: forge test --match-contract PTRateOracleTest -vvv


πŸ“‹ Summary: Pendle Architecture & AMM

Covered:

  • ERC-5115 Standardized Yield (SY) β€” wrapping diverse yield sources into a common interface
  • SY vs ERC-4626: reward token handling, multi-asset support, and exchange rate mechanics
  • Pendle system overview: SY wrapping, YieldContractFactory, PT/YT minting
  • Why constant-product AMMs fail for yield tokens (PT converges to 1:1 at maturity)
  • Rate-space trading: Pendle’s key insight of trading implied rates instead of prices
  • TWAR oracle: time-weighted average rate using the cumulative accumulator pattern
  • LP considerations: impermanent loss profile and the fee/rate trade-off

Next: Yield tokenization strategies and composability β€” how to use PT/YT for fixed income, leveraged yield, and structured products.


πŸ’‘ Strategies & Composability

πŸ’‘ Concept: Strategy 1: Fixed Income β€” Buy PT

Mechanism: Buy PT at a discount β†’ hold to maturity β†’ redeem at 1:1.

Worked example:

Scenario: Lock in a fixed staking yield on wstETH

Step 1: Buy 100 PT-wstETH at 0.97 price
  Cost: 97 wstETH

Step 2: Hold until maturity (6 months)

Step 3: Redeem 100 PT for 100 wstETH

Result:
  Paid: 97 wstETH
  Received: 100 wstETH
  Profit: 3 wstETH (3.09% over 6 months = 6.19% annualized)
  Rate locked at purchase β€” doesn't matter if staking yield drops to 2%

Risk analysis:

  • Smart contract risk: Pendle or underlying protocol bug
  • Underlying failure: If the yield source (e.g., Lido) has a slashing event, PT may not be worth 1.0
  • Opportunity cost: If rates spike to 20%, you’re locked at 6.19%
  • Liquidity risk: Selling PT before maturity incurs AMM slippage
  • No impermanent loss: This isn’t an LP position β€” just a buy and hold

Use cases: DAO treasury management, yield hedging, risk-off positioning.

πŸ’‘ Concept: Strategy 2: Yield Speculation β€” Buy YT

Mechanism: Buy YT β†’ receive all yield on the underlying until maturity.

Worked example:

Scenario: Bet that wstETH staking yield increases

Step 1: Buy 100 YT-wstETH at 0.03 price
  Cost: 3 wstETH (for yield entitlement on 100 wstETH!)
  β†’ This is ~33x leverage on yield

Step 2: Over 6 months, actual average staking yield = 4.5%

Step 3: Yield received = 100 wstETH Γ— 4.5% Γ— 0.5 year = 2.25 wstETH

Result:
  Cost: 3 wstETH
  Received: 2.25 wstETH
  Loss: 0.75 wstETH

Break-even analysis:
  Need actual yield to equal implied rate: 6.19% annual = 3.09% over 6 months
  Need 100 Γ— 3.09% = 3.09 wstETH in yield to break even on 3 wstETH cost
  Any average yield above ~6.19% annual β†’ profit

The points/airdrop meta: In 2024, YT became hugely popular for airdrop farming. If an underlying protocol distributes points or airdrops to holders, YT holders receive them (since YT represents yield entitlement). Buying 100 YT for 3 wstETH gives airdrop exposure on 100 wstETH β€” massive leverage on potential airdrops.

πŸ’‘ Concept: Strategy 3: LP in Pendle Pool

LPing in Pendle pools provides exposure to both sides with unique IL characteristics:

Pendle LP Yield Sources:

1. Swap fees         ← From traders (PT buyers/sellers)
2. SY yield          ← The SY portion of pool earns yield
3. PENDLE rewards    ← Gauge emissions (vePENDLE-boosted)
4. PT convergence    ← IL decreases over time (free yield!)

Total APY can be attractive: 5-15% on stable pools, higher on volatile ones

πŸ’‘ Concept: PT as Collateral

The insight: PT has a known minimum value at maturity (1 underlying). This makes it excellent collateral β€” lenders know exactly what it’s worth at a specific date.

Morpho Blue + Pendle PT:

  • Morpho accepts Pendle PT tokens as collateral for borrowing
  • The PT discount provides a built-in safety margin
  • Example: Borrow 0.95 USDC against 1 PT-aUSDC (LTV ~97%)
  • At maturity, PT = 1.0 β†’ comfortable collateral ratio

Looping strategy:

  1. Deposit yield-bearing asset β†’ get SY β†’ split to PT + YT
  2. Use PT as collateral on Morpho β†’ borrow more underlying
  3. Repeat β†’ leveraged fixed-rate exposure

πŸ”— The LST + Pendle Pipeline

Combining Module 1 (LSTs) with yield tokenization creates a full-stack yield management system:

ETH β†’ Lido β†’ stETH β†’ wrap β†’ wstETH β†’ Pendle SY β†’ PT + YT
 β”‚                                                   β”‚    β”‚
 β”‚                                                   β”‚    └── YT: speculate on
 β”‚                                                   β”‚        staking yield direction
 β”‚                                                   β”‚
 β”‚                                                   └──── PT: lock in fixed
 β”‚                                                         staking yield
 β”‚
 └── Originally earning variable ~3-4% staking yield
     Now separated into fixed and variable components

DeFi composability at its finest:
  - Ethereum staking (L1)
  - Lido (liquid staking)
  - Pendle (yield tokenization)
  - Morpho (lending against PT)
  Each layer adds a new financial primitive.

πŸ’Ό Job Market Context

What DeFi teams expect you to know:

  1. β€œWhat are the risks of buying YT?”
    • Good answer: β€œTime decay β€” YT loses value as maturity approaches. If actual yield is lower than implied, you lose money.”
    • Great answer: β€œYT is leveraged long yield exposure with time decay. The break-even rate equals the implied rate at purchase β€” if average actual yield stays below that, YT is unprofitable. Key risks: (1) Time decay β€” shorter remaining period means less yield to capture, (2) Rate compression β€” if staking yields fall, YT can lose most of its value rapidly, (3) Smart contract risk on both Pendle and the underlying protocol, (4) Liquidity risk β€” YT markets are thinner than PT markets, so exiting a position can have high slippage. The leverage works both ways β€” a small cost buys yield exposure on a large notional, but the maximum loss is 100% of the YT purchase price.”

Interview Red Flags:

  • 🚩 Ignoring time decay when analyzing YT profitability (treating it like a spot position)
  • 🚩 Not knowing the break-even condition: average actual yield must exceed implied rate at purchase
  • 🚩 Overlooking PT-as-collateral composability with lending protocols like Morpho

Pro tip: Yield tokenization is one of the most innovative DeFi primitives of 2023-2024. Understanding it deeply signals you follow cutting-edge DeFi. Bonus points for knowing how PT-as-collateral works in Morpho and for connecting the accumulator pattern across Compound, Aave, and Pendle.


πŸ“‹ Summary: Yield Tokenization

βœ“ Covered:

  • The fixed-rate problem and zero-coupon bond analogy
  • PT/YT splitting mechanics and invariant (PT + YT = 1 underlying)
  • Implied rate math with worked examples (annualization, inverse formula)
  • YT yield accumulator β€” same O(1) pattern as Compound, Aave, Module 2 funding rate
  • ERC-5115 (Standardized Yield) vs ERC-4626
  • Pendle architecture: SY β†’ Factory β†’ PT/YT β†’ AMM β†’ Router
  • Why x*y=k fails for time-decaying assets
  • Rate-space trading and the Pendle AMM curve
  • Strategies: fixed income (PT), yield speculation (YT), LP, PT as collateral
  • LST + Pendle pipeline (Module 1 integration)

Key insight: The accumulator pattern appears for the third time in this curriculum. Whether it’s vault share pricing (P2M7), funding rates (P3M2), or yield tracking (P3M3), the math is identical: global growing counter + per-user snapshot + delta = amount owed.

Next: Cross-module concept links and resources.


The accumulator pattern (3rd appearance):

ModuleAccumulatorWhat it tracksUpdate trigger
P2M7ERC-4626 share priceVault yield per shareDeposit/withdraw
P3M2cumulativeFundingPerUnitFunding payments per unitPosition open/close
P3M3Exchange rate (pyIndex)Yield per unit of SYYT claim/transfer

Each is the SAME mathematical pattern: a global counter that grows, per-user snapshots at entry, delta = amount owed. The only difference is what’s being accumulated (vault yield, funding payments, staking yield).

Time-decaying assets (new pattern):

  • PT value converges to 1.0 at maturity
  • Options value decays (theta) as expiry approaches
  • Bond price converges to par at maturity
  • Any AMM for time-decaying assets needs a time-aware curve

Fixed rate from variable rate (financial engineering pattern):

  • PT/YT splitting in Pendle
  • Interest rate swaps in TradFi
  • Notional Finance (fixed-rate lending)
  • All achieve the same goal: converting floating exposure to fixed

πŸ“– Production Study Order

Study these codebases in order β€” each builds on the previous one’s patterns:

#RepositoryWhy Study ThisKey Files
1Pendle SYBase.solERC-5115 standardized yield implementation β€” the abstraction layer that wraps any yield sourcecontracts/core/StandardizedYield/SYBase.sol, contracts/core/StandardizedYield/implementations/
2Pendle PendleYieldToken.solYield accumulator pattern, reward tracking, per-user snapshot math β€” the core accountingcontracts/core/YieldContracts/PendleYieldToken.sol
3Pendle PendlePrincipalToken.solMaturity redemption, PT value convergence, mint/burn tied to YT lifecyclecontracts/core/YieldContracts/PendlePrincipalToken.sol
4Pendle MarketMathCore.solRate-space AMM math β€” the time-decaying curve that makes PT/YT trading workcontracts/core/Market/MarketMathCore.sol
5Pendle PendleMarketV7.solAMM pool implementation, LP mechanics, fee structure, swap executioncontracts/core/Market/PendleMarketV7.sol
6Spectra (formerly APWine)Alternative yield tokenization β€” compare design choices with PendleCore contracts

Reading strategy: Start with SYBase.sol to understand the yield abstraction layer β€” this is the adapter pattern that makes Pendle protocol-agnostic. Then read PendleYieldToken.sol for the accumulator math (compare with P2M7 ERC-4626 and P3M2 funding accumulators β€” same pattern). Study PendlePrincipalToken.sol to see how PT and YT are coupled. Only then tackle MarketMathCore.sol β€” this is the hardest file, focus on the rate-space transformation before the implementation details. Finally, read the market contract to see the AMM in action.


πŸ“š Resources

Production Code

Standards

Documentation

Further Reading


Navigation: ← Module 2: Perpetuals & Derivatives | Part 3 Overview | Next: Module 4 β€” DEX Aggregation & Intents β†’