Skip to content

Pendle finance SY/PT/YT and AMM source code walkthrough (including math libs)

License

Notifications You must be signed in to change notification settings

ret2basic/Pendle-source-code-walkthrough

 
 

Repository files navigation

Pendle V2 Code Reading Plan

Here's a strategic reading plan to understand the core functionality of Pendle Finance V2 efficiently:

Phase 1: Understanding the Token Architecture (Foundation)

Start here to understand the fundamental building blocks:

  1. Standardized Yield (SY) Interface - The wrapper for yield-bearing tokens IStandardizedYield.sol
  2. Principal Token (PT) - Represents the principal component PendlePrincipalToken.sol
  3. Yield Token (YT) Interface - Represents the yield component IPYieldToken.sol
  4. Yield Token Implementation - How PT/YT splitting works PendleYieldToken.sol

Phase 2: The AMM Market (Core Trading Logic)

This is where PT gets traded:

  1. Market Interface - Understanding market operations (mint, burn, swap) IPMarket.sol
  2. Market Implementation - The actual AMM implementation PendleMarketV3.sol
  3. Market Math - The pricing mechanism (most complex but crucial) MarketMathCore.sol

Phase 3: User-Facing Router (How Users Interact)

Skip deep implementation details, focus on interfaces:

  1. Router Actions Overview - Check the router directory structure to understand available actions router
  2. Browse the interface files to understand user operations:
    • IPActionAddRemoveLiqV3.sol - Adding/removing liquidity
    • IPActionSwapPTV3.sol - Swapping PT
    • IPActionSwapYTV3.sol - Swapping YT
    • IPActionMintRedeemStatic.sol - Minting/redeeming PT/YT

Phase 4: Incentive System (vePendle & Gauges)

Understanding the tokenomics:

  1. vePendle System Overview - Read the documentation README.md
  2. Gauge Implementation - How rewards are distributed to markets PendleGauge

Phase 5: Supporting Libraries (Reference as Needed)

Don't read linearly - use as reference when needed:

  1. Key math libraries in contracts/core/libraries/math/:
    • PMath.sol - Basic math operations
    • LogExpMath.sol - Logarithmic/exponential math for pricing
  2. Helper libraries in contracts/core/libraries/:
    • Errors.sol - Error definitions
    • TokenHelper.sol - Token transfer helpers

Reading Strategy Tips:

  1. Follow this order: SY → PT/YT → Market → Router → Incentives
  2. Focus on interfaces first before diving into implementations
  3. Skip the math details initially - understand what it does, not how
  4. Use the Market as your anchor - everything flows through it
  5. Refer back to the README for the incentive system architecture

Key Concepts to Grasp:

  • SY (Standardized Yield): Pendle's wrapper around any yield-bearing token
  • PT (Principal Token): Represents the principal, tradeable on the AMM
  • YT (Yield Token): Represents the yield, collects rewards over time
  • Market: AMM for trading PT against SY using a specialized curve
  • vePendle: Vote-escrowed PENDLE for governance and boosted rewards

What to Skip (The 20%):

  • Cross-chain messaging details (contracts/cross-chain/)
  • Deprecated contracts (contracts/deprecated/)
  • Oracle implementations (unless you're integrating)
  • Limit order system (contracts/limit/)
  • Specific SY implementations for individual protocols

This plan will give you a solid understanding of how Pendle works: wrapping yield tokens into SY, splitting them into PT/YT, trading PT on the AMM, and incentivizing liquidity through vePendle.

Citations

File: contracts/interfaces/IStandardizedYield.sol (L27-166)

interface IStandardizedYield is IERC20Metadata {
    /// @dev Emitted when any base tokens is deposited to mint shares
    event Deposit(
        address indexed caller,
        address indexed receiver,
        address indexed tokenIn,
        uint256 amountDeposited,
        uint256 amountSyOut
    );

    /// @dev Emitted when any shares are redeemed for base tokens
    event Redeem(
        address indexed caller,
        address indexed receiver,
        address indexed tokenOut,
        uint256 amountSyToRedeem,
        uint256 amountTokenOut
    );

    /// @dev check `assetInfo()` for more information
    enum AssetType {
        TOKEN,
        LIQUIDITY
    }

    /// @dev Emitted when (`user`) claims their rewards
    event ClaimRewards(address indexed user, address[] rewardTokens, uint256[] rewardAmounts);

    /**
     * @notice mints an amount of shares by depositing a base token.
     * @param receiver shares recipient address
     * @param tokenIn address of the base tokens to mint shares
     * @param amountTokenToDeposit amount of base tokens to be transferred from (`msg.sender`)
     * @param minSharesOut reverts if amount of shares minted is lower than this
     * @return amountSharesOut amount of shares minted
     * @dev Emits a {Deposit} event
     *
     * Requirements:
     * - (`tokenIn`) must be a valid base token.
     */
    function deposit(
        address receiver,
        address tokenIn,
        uint256 amountTokenToDeposit,
        uint256 minSharesOut
    ) external payable returns (uint256 amountSharesOut);

    /**
     * @notice redeems an amount of base tokens by burning some shares
     * @param receiver recipient address
     * @param amountSharesToRedeem amount of shares to be burned
     * @param tokenOut address of the base token to be redeemed
     * @param minTokenOut reverts if amount of base token redeemed is lower than this
     * @param burnFromInternalBalance if true, burns from balance of `address(this)`, otherwise burns from `msg.sender`
     * @return amountTokenOut amount of base tokens redeemed
     * @dev Emits a {Redeem} event
     *
     * Requirements:
     * - (`tokenOut`) must be a valid base token.
     */
    function redeem(
        address receiver,
        uint256 amountSharesToRedeem,
        address tokenOut,
        uint256 minTokenOut,
        bool burnFromInternalBalance
    ) external returns (uint256 amountTokenOut);

    /**
     * @notice exchangeRate * syBalance / 1e18 must return the asset balance of the account
     * @notice vice-versa, if a user uses some amount of tokens equivalent to X asset, the amount of sy
     he can mint must be X * exchangeRate / 1e18
     * @dev SYUtils's assetToSy & syToAsset should be used instead of raw multiplication
     & division
     */
    function exchangeRate() external view returns (uint256 res);

    /**
     * @notice claims reward for (`user`)
     * @param user the user receiving their rewards
     * @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens`
     * @dev
     * Emits a `ClaimRewards` event
     * See {getRewardTokens} for list of reward tokens
     */
    function claimRewards(address user) external returns (uint256[] memory rewardAmounts);

    /**
     * @notice get the amount of unclaimed rewards for (`user`)
     * @param user the user to check for
     * @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens`
     */
    function accruedRewards(address user) external view returns (uint256[] memory rewardAmounts);

    function rewardIndexesCurrent() external returns (uint256[] memory indexes);

    function rewardIndexesStored() external view returns (uint256[] memory indexes);

    /**
     * @notice returns the list of reward token addresses
     */
    function getRewardTokens() external view returns (address[] memory);

    /**
     * @notice returns the address of the underlying yield token
     */
    function yieldToken() external view returns (address);

    /**
     * @notice returns all tokens that can mint this SY
     */
    function getTokensIn() external view returns (address[] memory res);

    /**
     * @notice returns all tokens that can be redeemed by this SY
     */
    function getTokensOut() external view returns (address[] memory res);

    function isValidTokenIn(address token) external view returns (bool);

    function isValidTokenOut(address token) external view returns (bool);

    function previewDeposit(
        address tokenIn,
        uint256 amountTokenToDeposit
    ) external view returns (uint256 amountSharesOut);

    function previewRedeem(
        address tokenOut,
        uint256 amountSharesToRedeem
    ) external view returns (uint256 amountTokenOut);

    /**
     * @notice This function contains information to interpret what the asset is
     * @return assetType the type of the asset (0 for ERC20 tokens, 1 for AMM liquidity tokens,
        2 for bridged yield bearing tokens like wstETH, rETH on Arbi whose the underlying asset doesn't exist on the chain)
     * @return assetAddress the address of the asset
     * @return assetDecimals the decimals of the asset
     */
    function assetInfo() external view returns (AssetType assetType, address assetAddress, uint8 assetDecimals);

File: contracts/core/YieldContracts/PendlePrincipalToken.sol (L14-62)

contract PendlePrincipalToken is PendleERC20, Initializable, IPPrincipalToken {
    address public immutable SY;
    address public immutable factory;
    uint256 public immutable expiry;
    address public YT;

    modifier onlyYT() {
        if (msg.sender != YT) revert Errors.OnlyYT();
        _;
    }

    modifier onlyYieldFactory() {
        if (msg.sender != factory) revert Errors.OnlyYCFactory();
        _;
    }

    constructor(
        address _SY,
        string memory _name,
        string memory _symbol,
        uint8 __decimals,
        uint256 _expiry
    ) PendleERC20(_name, _symbol, __decimals) {
        SY = _SY;
        expiry = _expiry;
        factory = msg.sender;
    }

    function initialize(address _YT) external initializer onlyYieldFactory {
        YT = _YT;
    }

    /**
     * @dev only callable by the YT correspond to this PT
     */
    function burnByYT(address user, uint256 amount) external onlyYT {
        _burn(user, amount);
    }

    /**
     * @dev only callable by the YT correspond to this PT
     */
    function mintByYT(address user, uint256 amount) external onlyYT {
        _mint(user, amount);
    }

    function isExpired() public view returns (bool) {
        return MiniHelpers.isCurrentlyExpired(expiry);
    }

File: contracts/interfaces/IPYieldToken.sol (L7-61)

interface IPYieldToken is IERC20Metadata, IRewardManager, IPInterestManagerYT {
    event NewInterestIndex(uint256 indexed newIndex);

    event Mint(
        address indexed caller,
        address indexed receiverPT,
        address indexed receiverYT,
        uint256 amountSyToMint,
        uint256 amountPYOut
    );

    event Burn(address indexed caller, address indexed receiver, uint256 amountPYToRedeem, uint256 amountSyOut);

    event RedeemRewards(address indexed user, uint256[] amountRewardsOut);

    event RedeemInterest(address indexed user, uint256 interestOut);

    event CollectRewardFee(address indexed rewardToken, uint256 amountRewardFee);

    function mintPY(address receiverPT, address receiverYT) external returns (uint256 amountPYOut);

    function redeemPY(address receiver) external returns (uint256 amountSyOut);

    function redeemPYMulti(
        address[] calldata receivers,
        uint256[] calldata amountPYToRedeems
    ) external returns (uint256[] memory amountSyOuts);

    function redeemDueInterestAndRewards(
        address user,
        bool redeemInterest,
        bool redeemRewards
    ) external returns (uint256 interestOut, uint256[] memory rewardsOut);

    function rewardIndexesCurrent() external returns (uint256[] memory);

    function pyIndexCurrent() external returns (uint256);

    function pyIndexStored() external view returns (uint256);

    function getRewardTokens() external view returns (address[] memory);

    function SY() external view returns (address);

    function PT() external view returns (address);

    function factory() external view returns (address);

    function expiry() external view returns (uint256);

    function isExpired() external view returns (bool);

    function doCacheIndexSameBlock() external view returns (bool);

    function pyIndexLastUpdatedBlock() external view returns (uint128);

File: contracts/core/YieldContracts/PendleYieldToken.sol (L21-80)

/**
Invariance to maintain:
- address(0) & address(this) should never have any rewards & activeBalance accounting done. This is
    guaranteed by address(0) & address(this) check in each updateForTwo function
*/
contract PendleYieldToken is IPYieldToken, PendleERC20, RewardManagerAbstract, InterestManagerYT {
    using PMath for uint256;
    using SafeERC20 for IERC20;
    using ArrayLib for uint256[];

    struct PostExpiryData {
        uint128 firstPYIndex;
        uint128 totalSyInterestForTreasury;
        mapping(address => uint256) firstRewardIndex;
        mapping(address => uint256) userRewardOwed;
    }

    address public immutable SY;
    address public immutable PT;
    address public immutable factory;
    uint256 public immutable expiry;

    bool public immutable doCacheIndexSameBlock;

    uint256 public syReserve;

    uint128 public pyIndexLastUpdatedBlock;
    uint128 internal _pyIndexStored;

    PostExpiryData public postExpiry;

    modifier updateData() {
        if (isExpired()) _setPostExpiryData();
        _;
        _updateSyReserve();
    }

    modifier notExpired() {
        if (isExpired()) revert Errors.YCExpired();
        _;
    }

    /**
     * @param _doCacheIndexSameBlock if true, the PY index is cached for each block, and thus is
     * constant for all txs within the same block. Otherwise, the PY index is recalculated for
     * every tx.
     */
    constructor(
        address _SY,
        address _PT,
        string memory _name,
        string memory _symbol,
        uint8 __decimals,
        uint256 _expiry,
        bool _doCacheIndexSameBlock
    ) PendleERC20(_name, _symbol, __decimals) {
        SY = _SY;
        PT = _PT;
        expiry = _expiry;
        factory = msg.sender;

File: contracts/interfaces/IPMarket.sol (L11-92)

interface IPMarket is IERC20Metadata, IPGauge {
    event Mint(address indexed receiver, uint256 netLpMinted, uint256 netSyUsed, uint256 netPtUsed);

    event Burn(
        address indexed receiverSy,
        address indexed receiverPt,
        uint256 netLpBurned,
        uint256 netSyOut,
        uint256 netPtOut
    );

    event Swap(
        address indexed caller,
        address indexed receiver,
        int256 netPtOut,
        int256 netSyOut,
        uint256 netSyFee,
        uint256 netSyToReserve
    );

    event UpdateImpliedRate(uint256 indexed timestamp, uint256 lnLastImpliedRate);

    event IncreaseObservationCardinalityNext(
        uint16 observationCardinalityNextOld,
        uint16 observationCardinalityNextNew
    );

    function mint(
        address receiver,
        uint256 netSyDesired,
        uint256 netPtDesired
    ) external returns (uint256 netLpOut, uint256 netSyUsed, uint256 netPtUsed);

    function burn(
        address receiverSy,
        address receiverPt,
        uint256 netLpToBurn
    ) external returns (uint256 netSyOut, uint256 netPtOut);

    function swapExactPtForSy(
        address receiver,
        uint256 exactPtIn,
        bytes calldata data
    ) external returns (uint256 netSyOut, uint256 netSyFee);

    function swapSyForExactPt(
        address receiver,
        uint256 exactPtOut,
        bytes calldata data
    ) external returns (uint256 netSyIn, uint256 netSyFee);

    function redeemRewards(address user) external returns (uint256[] memory);

    function readState(address router) external view returns (MarketState memory market);

    function observe(uint32[] memory secondsAgos) external view returns (uint216[] memory lnImpliedRateCumulative);

    function increaseObservationsCardinalityNext(uint16 cardinalityNext) external;

    function readTokens() external view returns (IStandardizedYield _SY, IPPrincipalToken _PT, IPYieldToken _YT);

    function getRewardTokens() external view returns (address[] memory);

    function isExpired() external view returns (bool);

    function expiry() external view returns (uint256);

    function observations(
        uint256 index
    ) external view returns (uint32 blockTimestamp, uint216 lnImpliedRateCumulative, bool initialized);

    function _storage()
        external
        view
        returns (
            int128 totalPt,
            int128 totalSy,
            uint96 lastLnImpliedRate,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext
        );

File: contracts/core/Market/v3/PendleMarketV3.sol (L12-100)

/**
Invariance to maintain:
- Internal balances totalPt & totalSy not interfered by people transferring tokens in directly
- address(0) & address(this) should never have any rewards & activeBalance accounting done. This is
    guaranteed by address(0) & address(this) check in each updateForTwo function
*/
contract PendleMarketV3 is PendleERC20, PendleGauge, IPMarketV3 {
    using PMath for uint256;
    using PMath for int256;
    using MarketMathCore for MarketState;
    using SafeERC20 for IERC20;
    using PYIndexLib for IPYieldToken;
    using OracleLib for OracleLib.Observation[65535];

    struct MarketStorage {
        int128 totalPt;
        int128 totalSy;
        // 1 SLOT = 256 bits
        uint96 lastLnImpliedRate;
        uint16 observationIndex;
        uint16 observationCardinality;
        uint16 observationCardinalityNext;
        // 1 SLOT = 144 bits
    }

    string private constant NAME = "Pendle Market";
    string private constant SYMBOL = "PENDLE-LPT";

    IPPrincipalToken internal immutable PT;
    IStandardizedYield internal immutable SY;
    IPYieldToken internal immutable YT;

    address public immutable factory;
    uint256 public immutable expiry;

    int256 internal immutable scalarRoot;
    int256 internal immutable initialAnchor;
    uint80 internal immutable lnFeeRateRoot;

    MarketStorage public _storage;

    OracleLib.Observation[65535] public observations;

    modifier notExpired() {
        if (isExpired()) revert Errors.MarketExpired();
        _;
    }

    constructor(
        address _PT,
        int256 _scalarRoot,
        int256 _initialAnchor,
        uint80 _lnFeeRateRoot,
        address _vePendle,
        address _gaugeController
    ) PendleERC20(NAME, SYMBOL, 18) PendleGauge(IPPrincipalToken(_PT).SY(), _vePendle, _gaugeController) {
        PT = IPPrincipalToken(_PT);
        SY = IStandardizedYield(PT.SY());
        YT = IPYieldToken(PT.YT());

        (_storage.observationCardinality, _storage.observationCardinalityNext) = observations.initialize(
            uint32(block.timestamp)
        );

        if (_scalarRoot <= 0) revert Errors.MarketScalarRootBelowZero(_scalarRoot);

        scalarRoot = _scalarRoot;
        initialAnchor = _initialAnchor;
        lnFeeRateRoot = _lnFeeRateRoot;
        expiry = IPPrincipalToken(_PT).expiry();
        factory = msg.sender;
    }

    /**
     * @notice PendleMarket allows users to provide in PT & SY in exchange for LPs, which
     * will grant LP holders more exchange fee over time
     * @dev will mint as much LP as possible such that the corresponding SY and PT used do
     * not exceed `netSyDesired` and `netPtDesired`, respectively
     * @dev PT and SY should be transferred to this contract prior to calling
     * @dev will revert if PT is expired
     */
    function mint(
        address receiver,
        uint256 netSyDesired,
        uint256 netPtDesired
    ) external nonReentrant notExpired returns (uint256 netLpOut, uint256 netSyUsed, uint256 netPtUsed) {
        MarketState memory market = readState(msg.sender);
        PYIndex index = YT.newIndex();

File: contracts/core/Market/MarketMathCore.sol (L11-80)

struct MarketState {
    int256 totalPt;
    int256 totalSy;
    int256 totalLp;
    address treasury;
    /// immutable variables ///
    int256 scalarRoot;
    uint256 expiry;
    /// fee data ///
    uint256 lnFeeRateRoot;
    uint256 reserveFeePercent; // base 100
    /// last trade data ///
    uint256 lastLnImpliedRate;
}

// params that are expensive to compute, therefore we pre-compute them
struct MarketPreCompute {
    int256 rateScalar;
    int256 totalAsset;
    int256 rateAnchor;
    int256 feeRate;
}

// solhint-disable ordering
library MarketMathCore {
    using PMath for uint256;
    using PMath for int256;
    using LogExpMath for int256;
    using PYIndexLib for PYIndex;

    int256 internal constant MINIMUM_LIQUIDITY = 10 ** 3;
    int256 internal constant PERCENTAGE_DECIMALS = 100;
    uint256 internal constant DAY = 86400;
    uint256 internal constant IMPLIED_RATE_TIME = 365 * DAY;

    int256 internal constant MAX_MARKET_PROPORTION = (1e18 * 96) / 100;

    using PMath for uint256;
    using PMath for int256;

    /*///////////////////////////////////////////////////////////////
                UINT FUNCTIONS TO PROXY TO CORE FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    function addLiquidity(
        MarketState memory market,
        uint256 syDesired,
        uint256 ptDesired,
        uint256 blockTime
    ) internal pure returns (uint256 lpToReserve, uint256 lpToAccount, uint256 syUsed, uint256 ptUsed) {
        (int256 _lpToReserve, int256 _lpToAccount, int256 _syUsed, int256 _ptUsed) = addLiquidityCore(
            market,
            syDesired.Int(),
            ptDesired.Int(),
            blockTime
        );

        lpToReserve = _lpToReserve.Uint();
        lpToAccount = _lpToAccount.Uint();
        syUsed = _syUsed.Uint();
        ptUsed = _ptUsed.Uint();
    }

    function removeLiquidity(
        MarketState memory market,
        uint256 lpToRemove
    ) internal pure returns (uint256 netSyToAccount, uint256 netPtToAccount) {
        (int256 _syToAccount, int256 _ptToAccount) = removeLiquidityCore(market, lpToRemove.Int());

        netSyToAccount = _syToAccount.Uint();

File: contracts/router (L1-1)

[{"name":"ActionAddRemoveLiqV3.sol","path":"contracts/router/ActionAddRemoveLiqV3.sol","sha":"08d642f59c05598300d6b8c96737690fb9f0d22e","size":17351,"url":"<https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionAddRemoveLiqV3.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionAddRemoveLiqV3.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/08d642f59c05598300d6b8c96737690fb9f0d22e","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionAddRemoveLiqV3.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionAddRemoveLiqV3.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/08d642f59c05598300d6b8c96737690fb9f0d22e","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionAddRemoveLiqV3.sol"}},{"name":"ActionCallbackV3.sol","path":"contracts/router/ActionCallbackV3.sol","sha":"9634d712459f422a4114bd7ef8d6b9f4b7ee655d","size":5670,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionCallbackV3.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionCallbackV3.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/9634d712459f422a4114bd7ef8d6b9f4b7ee655d","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionCallbackV3.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionCallbackV3.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/9634d712459f422a4114bd7ef8d6b9f4b7ee655d","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionCallbackV3.sol"}},{"name":"ActionCrossChain.sol","path":"contracts/router/ActionCrossChain.sol","sha":"6ef4b5bc2d5f48e8bbd90e4964f1241baaff85bc","size":3008,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionCrossChain.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionCrossChain.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/6ef4b5bc2d5f48e8bbd90e4964f1241baaff85bc","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionCrossChain.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionCrossChain.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/6ef4b5bc2d5f48e8bbd90e4964f1241baaff85bc","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionCrossChain.sol"}},{"name":"ActionMiscV3.sol","path":"contracts/router/ActionMiscV3.sol","sha":"69df8ded10175e4d51b8b334d7e7236084272210","size":17554,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionMiscV3.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionMiscV3.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/69df8ded10175e4d51b8b334d7e7236084272210","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionMiscV3.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionMiscV3.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/69df8ded10175e4d51b8b334d7e7236084272210","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionMiscV3.sol"}},{"name":"ActionSimple.sol","path":"contracts/router/ActionSimple.sol","sha":"d74002724039343badcfee9620f14a041fcb9c83","size":11678,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionSimple.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionSimple.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/d74002724039343badcfee9620f14a041fcb9c83","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionSimple.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionSimple.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/d74002724039343badcfee9620f14a041fcb9c83","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionSimple.sol"}},{"name":"ActionStorageV4.sol","path":"contracts/router/ActionStorageV4.sol","sha":"2c575878f178ed3c2661e42ea19d629ceb323247","size":2023,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionStorageV4.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionStorageV4.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/2c575878f178ed3c2661e42ea19d629ceb323247","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionStorageV4.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionStorageV4.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/2c575878f178ed3c2661e42ea19d629ceb323247","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionStorageV4.sol"}},{"name":"ActionSwapPTV3.sol","path":"contracts/router/ActionSwapPTV3.sol","sha":"fccd85228ee118a03f498a3443cb1078489b67a9","size":3960,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionSwapPTV3.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionSwapPTV3.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/fccd85228ee118a03f498a3443cb1078489b67a9","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionSwapPTV3.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionSwapPTV3.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/fccd85228ee118a03f498a3443cb1078489b67a9","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionSwapPTV3.sol"}},{"name":"ActionSwapYTV3.sol","path":"contracts/router/ActionSwapYTV3.sol","sha":"ae403611ce0a2b536574ee730c7834eaa483117f","size":4280,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionSwapYTV3.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionSwapYTV3.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/ae403611ce0a2b536574ee730c7834eaa483117f","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/ActionSwapYTV3.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/ActionSwapYTV3.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/ae403611ce0a2b536574ee730c7834eaa483117f","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/ActionSwapYTV3.sol"}},{"name":"PendleRouterV4.sol","path":"contracts/router/PendleRouterV4.sol","sha":"ccdb2b8f23b94e43da1f90dfa610f86db55e2876","size":817,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/PendleRouterV4.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/PendleRouterV4.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/ccdb2b8f23b94e43da1f90dfa610f86db55e2876","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/PendleRouterV4.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/PendleRouterV4.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/ccdb2b8f23b94e43da1f90dfa610f86db55e2876","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/PendleRouterV4.sol"}},{"name":"Reflector.sol","path":"contracts/router/Reflector.sol","sha":"426484a953e5e89583b97a1d70abe98db57d6822","size":4975,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/Reflector.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/Reflector.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/426484a953e5e89583b97a1d70abe98db57d6822","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/Reflector.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/Reflector.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/426484a953e5e89583b97a1d70abe98db57d6822","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/Reflector.sol"}},{"name":"RouterStorage.sol","path":"contracts/router/RouterStorage.sol","sha":"49e45315c436ab2865b0df662855ba714efb7aed","size":627,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/RouterStorage.sol?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/RouterStorage.sol","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/49e45315c436ab2865b0df662855ba714efb7aed","download_url":"https://raw.githubusercontent.com/pendle-finance/pendle-core-v2-public/main/contracts/router/RouterStorage.sol","type":"file","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/RouterStorage.sol?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/blobs/49e45315c436ab2865b0df662855ba714efb7aed","html":"https://github.com/pendle-finance/pendle-core-v2-public/blob/main/contracts/router/RouterStorage.sol"}},{"name":"base","path":"contracts/router/base","sha":"c97ff8b755eb8df1b2384b84c1c3d272feb5b1c9","size":0,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/base?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/router/base","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/trees/c97ff8b755eb8df1b2384b84c1c3d272feb5b1c9","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/base?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/trees/c97ff8b755eb8df1b2384b84c1c3d272feb5b1c9","html":"https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/router/base"}},{"name":"math","path":"contracts/router/math","sha":"6293e4eceebacfa8b0f1c8c7ea8b7d0eaf650056","size":0,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/math?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/router/math","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/trees/6293e4eceebacfa8b0f1c8c7ea8b7d0eaf650056","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/math?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/trees/6293e4eceebacfa8b0f1c8c7ea8b7d0eaf650056","html":"https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/router/math"}},{"name":"swap-aggregator","path":"contracts/router/swap-aggregator","sha":"cb6314959eb7cf5b347b0b304ae2c8c5a39fe55b","size":0,"url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/swap-aggregator?ref=main","html_url":"https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/router/swap-aggregator","git_url":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/trees/cb6314959eb7cf5b347b0b304ae2c8c5a39fe55b","download_url":null,"type":"dir","_links":{"self":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/contents/contracts/router/swap-aggregator?ref=main","git":"https://api.github.com/repos/pendle-finance/pendle-core-v2-public/git/trees/cb6314959eb7cf5b347b0b304ae2c8c5a39fe55b","html":"https://github.com/pendle-finance/pendle-core-v2-public/tree/main/contracts/router/swap-aggregator>"}}]

File: contracts/LiquidityMining/README.md (L1-65)

# vePendle For Launch

### Locking PENDLE for vePendle (on ETH)

- Each user can only lock for a maximum of $MAXLOCKTIME$ = 2 years
- vePendle balance for users is represented by:
  $balance_u(t) = bias_u - slope_u * t$

- As such, we just need to store $bias_u$ and $slope_u$ for each user. (Considerations: store $bias_u$ and $t_{expiry}$ for the user instead, because $t_{expiry}$ is always an exact number)
- The amount of PENDLE locked by user $u$ is $locked_u$
- For total supply, it's simply $totalBalance = totalBias - totalSlope * t$
- Possible actions by an user:
  - User $u$ create a new lock by locking $d_{PENDLE}$ of PENDLE tokens, expiring at $t_{expiry}$
    $slope_u = d_{PENDLE} / MAXLOCKTIME$
    $bias_u = slope_u \\cdot t_{expiry}$
  - User $u$ locks an additional of $d_{PENDLE}$ PENDLE tokens, into his existing lock expiring at $t_{expiry}$
    $slope_{u_{new}} = slope_u + d_{PENDLE} / MAXLOCKTIME$
    $bias_{u_{new}} = slope_{u_{new}} \\cdot t_{expiry}$
  - User $u$ extends his expiry from $t_{expiry}$ to $t_{expiry_{new}}$
    $bias_{u_{new}} = slope_{u} \\cdot t_{expiry_{new}}$
- Similar to Curve:
  - We store the checkpoints for when a user's $slope$ or $bias$ changes
  - We restrict the possible expiries to exact weeks from UNIX timestamp 0
  - We store the global bias and slope changes due to expiry of user's vote-locks per week, and process it when the week comes

### Cross-chain messaging module

- There are contracts inheritting `CelerSender` on governance chain - Ethereum, with a function `sendMessage(chain Y, sender, message)` to send messages to other chains.
- On each non-governance chain, we have contracts inheritting `CelerReceiver`. Each has a function `afterReceivingMessage(sender, message)` to receive the message from governance.
- When `sendMessage(chain Y, sender, message)` is called on governance chain, `afterReceivingMessage(sender, message)` will be called on chain Y, thanks to the cross-chain messaging module
- The current mechanics for the module is using Celer
- The cross-chain messaging module can be plugged in/plugged out by the governance address (which will initially be controlled by a team multisig)

### Voting for incentives on market and chains

- On Ethereum, there is a `VotingController` contract to control the voting on incentives for the different markets on the different chains.
- Adding a new chain:
  - Only the governance address will be able to add markets and a new chains' GaugeController.
- There is a list of `(market)` that are elligible for the voting, added by governance
  - markets that are expired will be removed by governance
- vePendle holders can allocate their vePendle to multiple markets on multiple chains
- Similar to Curve, we store the bias and slope of the total vePendle voted for every market
  - Similary, we adjust the slope/bias changes due to expired vePendle at the weekly mark
- There is a global PENDLE per second rate for all the incentives across all the chains. This is set by governance.
- The `VotingController` does not hold any fund. The incentivizing PENDLE is transferred directly to GaugeController by governacne. \\* GaugeControllers: admin can withdraw PENDLE from it
- In each chain, there is a `GaugeController` contract that is responsible for keeping PENDLE incentives and distributing PENDLE to incentivise different markets.
- At the finalization of each week (starting of new week), anyone can call a function `finalizeVotingResults` and then `broadcastVotes()` in `VotingController` to broadcast to any chain:
  1. The PENDLE allocation of each market for the next week
  2. The timestamp to mark which week is the incentivization coming from
     When this happens, the `GaugeController` in each chain will update the PENDLE per second among the different markets
- At any time, the governacne has the voting power equivalent to locking X PENDLE in 2 years

### GaugeController

Gauge controller will receive the voting results from VotingController and incentivize the amount of PENDLE from `block.timestamp` to `block.timestamp + 1 WEEK`.

If there is still leftover reward in each pool, the gauge controller will remove and take the leftover to top up the incentivize for the current week.

### Gauge & Market

- The Gauge/Market will receive rewardTokens from SCY as well as claiming the PENDLE token from gauge controller. All of the reward tokens (including PENDLE) will be distributed with boosting mechanism

### Broadcasting vePendle balance & different address for boosting rewards for each chain

- At anytime, an address A could send a cross message to update the vePendle balance on a specific chain

File: contracts/core/Market/PendleGauge.sol (L1-1)

// SPDX-License-Identifier: GPL-3.0-or-later

About

Pendle finance SY/PT/YT and AMM source code walkthrough (including math libs)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Solidity 99.9%
  • TypeScript 0.1%