Here's a strategic reading plan to understand the core functionality of Pendle Finance V2 efficiently:
Start here to understand the fundamental building blocks:
- Standardized Yield (SY) Interface - The wrapper for yield-bearing tokens
IStandardizedYield.sol - Principal Token (PT) - Represents the principal component
PendlePrincipalToken.sol - Yield Token (YT) Interface - Represents the yield component
IPYieldToken.sol - Yield Token Implementation - How PT/YT splitting works
PendleYieldToken.sol
This is where PT gets traded:
- Market Interface - Understanding market operations (mint, burn, swap)
IPMarket.sol - Market Implementation - The actual AMM implementation
PendleMarketV3.sol - Market Math - The pricing mechanism (most complex but crucial)
MarketMathCore.sol
Skip deep implementation details, focus on interfaces:
- Router Actions Overview - Check the router directory structure to understand available actions
router - Browse the interface files to understand user operations:
IPActionAddRemoveLiqV3.sol- Adding/removing liquidityIPActionSwapPTV3.sol- Swapping PTIPActionSwapYTV3.sol- Swapping YTIPActionMintRedeemStatic.sol- Minting/redeeming PT/YT
Understanding the tokenomics:
- vePendle System Overview - Read the documentation
README.md - Gauge Implementation - How rewards are distributed to markets
PendleGauge
Don't read linearly - use as reference when needed:
- Key math libraries in
contracts/core/libraries/math/:PMath.sol- Basic math operationsLogExpMath.sol- Logarithmic/exponential math for pricing
- Helper libraries in
contracts/core/libraries/:Errors.sol- Error definitionsTokenHelper.sol- Token transfer helpers
- Follow this order: SY → PT/YT → Market → Router → Incentives
- Focus on interfaces first before diving into implementations
- Skip the math details initially - understand what it does, not how
- Use the Market as your anchor - everything flows through it
- Refer back to the README for the incentive system architecture
- 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
- 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.
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