【项目学习】Pendle 项目的简单调研
项目介绍
将 1 ETH 质押成 1 stETH,年利率为 5%,那么到期后 1 stETH 就能收回 1 ETH(本金)+ 0.05 ETH(收益)。而 Pendle 所做的就是把 1 stETH 代表的生息代币(SY),拆分成了本金(Principal)和收益(Yield)两部分,使其可以分别流通于市场上。
YT 和 PT 的定义:
- YT (Yield Token) -> 代表该仓位的应收收益:1 YT 使您有权在到期前获得 1 单位标的资产(例如 1 ETH、1 DAI、1 USDe 等)的收益,并可实时索取。
- PT(Principal Token)->代表本金金额:1 PT 赋予您在到期时赎回 1 单位标的资产(例如 1 ETH、1 DAI、1 USDe 等)的权利。
用户可以将生息代币(1 stETH)拆分成 PT 和 YT ,然后使其在市场上流通。也可以通过存入等量的 PT 和 YT 来赎回标的资产。到期后,PT 可以赎回其标的资产(1 ETH),无需对应的 YT(这是因为到期的 YT 价值为 0,因为它们不再产生收益)。
本篇文章分析时用的代码版本是:https://github.com/pendle-finance/pendle-core-v2-public/tree/260e8d3a807ae2bd195a77cdefb869f494c53ebb
流动性池
在项目介绍章节,有提到过 PT 和 YT 可以分别流通于市场上,也可以存入等量的 PT 和 YT 兑换成 SY。为了实现上面提到的功能,Pendle 实现了一个基于 [SY, PT] 的 AMM Pool,用户可以通过它来兑换 SY,PT,YT 三种资产(很奇妙吧,一个 pair 兑换三种资产)。
由于到期后 PT 的价值和 SY 的价值是相等的,所以在 [SY, PT] Pool 中添加流动性可以被认为在到期后不会产生无常损失。
Pendle 收益代币化的核心是将收益代币拆分为 PT 和 YT。两种代币的价格相加就是基础资产的价格。因此,PT和YT的价格一定是负相关的——YT价格越高,PT价格越低,反之亦然。
那么,到底是怎么样通过 [SY, PT] Pool 来进行 PT/SY → YT 的兑换呢?官方文档给出了下面的描述:
- 买方将 PT/SY 发送到 Router 中
- Router 从池中闪电贷出 SY
- 将这笔 SY 铸造成 PT + YT
- 将 YT 发送给买家
- 将 PT 出售兑换成 SY,以返还步骤 2 中的闪电贷

转换成公式表达
Input:
					= x * PT
Flashloan SY:
					= x * PT + y * SY
					= x * PT + y * (PT + YT)
					= (x + y) * PT + y * YT
Sell to repay flashloan:
					= y * YT
Output:
					= y * YT
===================================
In flashloan:
					y * SY = (x + y) * PT
基于上面的公式,x 作为输入的 PT 数量,计算出 YT 的输出数量 y。
那么这个计算过程是怎么通过实现的?在 Pendle 的代码实现中没有通过公式计算,而是采用了二分查找法直接找出 y 的值(没想到吧)。
PT → YT 的合约入口:
contracts/router/ActionSwapYTV3.sol
// ------------------ SWAP PT FOR YT ------------------
    /// @notice For details on the parameters (input, guessPtSwapToSy, limit, etc.), please refer to IPAllActionTypeV3.
    function swapExactPtForYt(
        address receiver,
        address market,
        uint256 exactPtIn,   **// @note `x` amount**
        uint256 minYtOut,
        ApproxParams calldata guessTotalPtToSwap
    ) external returns (uint256 netYtOut, uint256 netSyFee) {
        (, IPPrincipalToken PT, IPYieldToken YT) = IPMarket(market).readTokens();
        uint256 totalPtToSwap;
        **// @note `y` == netYtOut, `x + y` == totalPtToSwap**
        (netYtOut, totalPtToSwap, netSyFee) = _readMarket(market).approxSwapExactPtForYt(
            YT.newIndex(),
            exactPtIn,
            block.timestamp,
            guessTotalPtToSwap
        );
        _transferFrom(IERC20(PT), msg.sender, market, exactPtIn);
				**// @note swap PT to `receiver`**
        IPMarket(market).swapExactPtForSy(
            address(YT),
            totalPtToSwap,
            _encodeSwapExactPtForYt(receiver, exactPtIn, minYtOut, YT)
        );
        emit SwapPtAndYt(msg.sender, market, receiver, exactPtIn.neg(), netYtOut.Int());
    }
contracts/router/math/MarketApproxLib.sol
/**
 * @dev algorithm:
 *     - Bin search the amount of PT to swap to SY
 *     - Flashswap the corresponding amount of SY out
 *     - Tokenize all the SY into PT + YT
 *     - PT to repay the flashswap, YT transferred to user
 *     - Stop when the additional amount of PT to pull to repay the loan approx the exactPtIn
 *     - guess & approx is for totalPtToSwap
 */
function approxSwapExactPtForYt(
    MarketState memory market,
    PYIndex index,
    uint256 exactPtIn,
    uint256 blockTime,
    ApproxParams memory approx
) internal pure returns (uint256, /*netYtOut*/ uint256, /*totalPtToSwap*/ uint256 /*netSyFee*/) {
    MarketPreCompute memory comp = market.getMarketPreCompute(index, blockTime);
    **// @note `guessOffchain` is the value of `y` calculated offline**
    **// @note `approx` is the upper and lower limits of the binary search**
    if (approx.guessOffchain == 0) {
        approx.guessMin = PMath.max(approx.guessMin, exactPtIn);
        approx.guessMax = PMath.min(approx.guessMax, calcMaxPtIn(market, comp));
        validateApprox(approx);
    }
		**// @note binary search**
    for (uint256 iter = 0; iter < approx.maxIteration; ++iter) {
        uint256 guess = nextGuess(approx, iter);    **// @note `guess` == (x + y) ?**
			  **// @note (x + y) * PT -> y * SY**
	      (uint256 netSyOut, uint256 netSyFee, ) = calcSyOut(market, comp, index, guess);  
				**// @note calculate the actual vaule of SY (becasuse its value will change with the change of `index`)
				// @note `netAssetOut` == y ?**
        uint256 netAssetOut = index.syToAsset(netSyOut);
        // guess >= netAssetOut since we are swapping PT to SY
        uint256 netPtToPull = guess - netAssetOut;  **// @note `netPtToPull` == x ?**
        if (netPtToPull <= exactPtIn) {
		        **// @note if the gap of `netPtToPull` and `x` is acceptable**
            if (PMath.isASmallerApproxB(netPtToPull, exactPtIn, approx.eps)) {
		            **// @note `y` == `netAssetOut`, `x + y` == `guess`**
                return (netAssetOut, guess, netSyFee);
            }
            approx.guessMin = guess;
        } else {
            approx.guessMax = guess - 1;
        }
    }
    revert("Slippage: APPROX_EXHAUSTED");
}
当然除了 PT → YT,Pendle 还支持 SY,PT,YT 三种代币两两互相交换,在 contracts/router 目录下可以找到每种兑换方式的实现。读者感兴趣可以自行了解。
为什么采用 netAssetOut 来计算 PT 的数量,而不是直接采用 SY 来计算?
在 approxSwapExactPtForYt 函数中,通过 uint256 netAssetOut = index.syToAsset(netSyOut); 将 SY 的数量 netSyOut 转换成了 Asset 的数量 netAssetOut ,然后再计算出所需要的 PT 数量 netPtToPull。
PT 是代表到期赎回本金权利的代币,到期后按照 PT : Asset = 1 : 1 的比例进行赎回。而 SY 包含了收益部分,其价值会随着时间的增加而增加。所以在计算 PT 数量的时候选择将 SY 换算成 Asset 进行计算,是为了统一计算单位。
官方例子:https://docs.pendle.finance/Developers/HighLevelArchitecture#principal-token-pt
At redemption,
1 PT = X SY, whereXsatisfies the condition thatX SY = 1 Asset. For example, assuming1 wstETH = 1.2 stETHon 1/1/2024,1 PT-wstETH-01JAN2024will be redeemable to0.8928 wstETHat maturity.
用户可以向流动性池子添加流动性,以通过多种途径获得收益:
- 基础资产的协议收益/奖励
- 池中 PT 部分的固定收益(添加流动性时将以折扣价格买入 PT)
- Swap fees
- $PENDLE 激励

经济模型
用户可以将添加流动性或其他渠道获得的 PENDLE 代币进行锁定,得到 vePENDLE 代币,而这个代币将在 Pendle 的治理与收益分配中起到重要作用。整个 Pendle 的经济模型如下图所示。

用户可以将手里的 PENDLE 通过 VotingEscrowPendleMainchain 合约进行锁定,合约会将用户的锁定时间和代币数量记录到 position 中。vePENDLE 不具有流动性,这在锁定期到期之前它无法转移。
contracts/LiquidityMining/VotingEscrow/VotingEscrowPendleMainchain.sol

在获得 vePENDLE 后,用户可以通过 PendleVotingControllerUpg 合约向池子进行投票。
contracts/LiquidityMining/VotingController/PendleVotingControllerUpg.sol

使用 vePENDLE 进行投票将获得的收益:
- 将 $PENDLE 激励投入流动性池
- 获得投票池 swap fee:
- vePENDLE 投票者能够从投票池中获得 80% 的 swap fee
 
- 接收基础APY
- Pendle 从 YT 累积的所有收益(包括积分)中收取 3% 的费用。目前,该费用 100% 分配给 vePENDLE 持有者
- 到期未赎回 PT 的部分收益也将按比例分配给 vePENDLE 持有者。
 
- 增加 LP 奖励(高达 250%):
- 如果您在持有 vePENDLE 的同时将 LP 加入到池中,那么您所有 LP 的 PENDLE 激励和奖励也将进一步增加,根据您的 vePENDLE 价值最多可增加 250%。
 
由于 vePENDLE 投票可以给投票用户带来收益,那么为了将这个收益最大化,需要有组织地对 vePENDLE 代币进行投票操作。由此衍生出了一批在 Pendle 基础上建立起来的 DeFi 协议:Penpie、Equilibria 和 StakeDAO 等。
简单介绍一下其中一个平台 Penpie
官方文档:https://docs.penpiexyz.io/penpie-ecosystem/introduction
Penpie 是 Magpie 在 Pendle Finance 基础上打造的一个为 Pendle 用户提供收益提升服务的 DeFi 平台。
Penpie 为 PENDLE 持有者提供通过 PENDLE 转换为 mPENDLE 来赚取可观收益。用户可以通过 Penpie 质押 mPENDLE,以获得平台上增强的活跃用户参与奖励。当用户将其 PENDLE 转换为 mPENDLE 时,Penpie 会自动将转换后的 PENDLE 在 Pendle Finance 中锁定为 vePENDLE。这一机制使 PENDLE 持有者能够通过 mPENDLE 获得更大的奖励,同时还授予 Penpie 治理权,并作为 Pendle Finance 的流动性提供者获得更高的年化利率 (APR%)。由于 Penpie 持有 vePENDLE,流动性提供者可以将资产存入平台并赚取提升的 PENDLE,而无需自己将任何 PENDLE 锁定为 vePENDLE。
Penpie 平台在为用户带来更高收益的同时,也遭受了项目攻击的风险。详情可见:【漏洞分析】Penpie 攻击事件:重入攻击构造奖励金额
这种在 DeFi 上建立 DeFi 的做法无疑是为用户带来了更高的收益,但是由于用户资金在投资过程中涉及的项目增加,遭受合约漏洞攻击而产生资金损失的风险也随之增大。在投资过程中选择经过专业安全审计机构审计过的项目十分重要。那么安全审计哪家强?!【此处招商,价格公道,童叟无欺!】
【项目学习】Pendle 项目的简单调研的更多相关文章
- Spring Boot 项目学习 (一) 项目搭建
		0 引言 本文主要记录借用Idea 开发环境下,搭建 Spring Boot 项目框架的过程. 1 系列文档目录 Spring Boot 项目学习 (一) 项目搭建 Spring Boot 项目学习 ... 
- Spring Boot 项目学习 (四) Spring Boot整合Swagger2自动生成API文档
		0 引言 在做服务端开发的时候,难免会涉及到API 接口文档的编写,可以经历过手写API 文档的过程,就会发现,一个自动生成API文档可以提高多少的效率. 以下列举几个手写API 文档的痛点: 文档需 ... 
- Spring Boot 项目学习 (三) Spring Boot + Redis 搭建
		0 引言 本文主要介绍 Spring Boot 中 Redis 的配置和基本使用. 1 配置 Redis 1. 修改pom.xml,添加Redis依赖 <!-- Spring Boot Redi ... 
- Spring Boot 项目学习 (二) MySql + MyBatis 注解 + 分页控件 配置
		0 引言 本文主要在Spring Boot 基础项目的基础上,添加 Mysql .MyBatis(注解方式)与 分页控件 的配置,用于协助完成数据库操作. 1 创建数据表 这个过程就暂时省略了. 2 ... 
- 转:从开源项目学习 C 语言基本的编码规则
		从开源项目学习 C 语言基本的编码规则 每个项目都有自己的风格指南:一组有关怎样为那个项目编码约定.一些经理选择基本的编码规则,另一些经理则更偏好非常高级的规则,对许多项目而言则没有特定的编码规则,项 ... 
- 转: 学习开源项目的若干建议(infoq)
		转: http://www.infoq.com/cn/news/2014/04/learn-open-source 学习开源项目的若干建议 作者 崔康 发布于 2014年4月11日 | 注意:GTLC ... 
- 华为章宇:如何学习开源项目及Ceph的浅析
		转自http://www.csdn.net/article/2014-04-10/2819247-how-to-learn-opensouce-project-&-ceph 摘要:开源技术的学 ... 
- android开源项目学习
		FBReaderJ FBReaderJ用于Android平台的电子书阅读器,它支持多种电子书籍格式包括:oeb.ePub和fb2.此外还支持直接读取zip.tar和gzip等压缩文档. 项目地址:ht ... 
- node+vue进阶【课程学习系统项目实战详细讲解】打通前后端全栈开发(1):创建项目,完成登录功能
		第一章 建议学习时间8小时·分两次学习 总项目预计10章 学习方式:详细阅读,并手动实现相关代码(如果没有node和vue基础,请学习前面的vue和node基础博客[共10章]) 视频教程地 ... 
- WPF项目学习.四
		信息收录项目 版权声明:本文为博主初学经验,未经博主允许不得转载. 一.前言 记录在学习与制作WPF过程中遇到的解决方案. 需求文案.设计思路.简要数据库结构.简要流程图和明细代码,动图细化每步操作 ... 
随机推荐
- 商业银行国际结算规模创新高,合合信息AI助力金融行业智能处理多版式文档
			随着我国外贸新业态的快速增长,银行国际结算业务在服务实体经济发展.促进贸易投资便利化进程中发挥了越来越重要的作用.根据中国银行业协会近日发布的<中国贸易金融行业发展报告(2023-2024)&g ... 
- OCR技术的昨天今天和明天!2023年最全OCR技术指南!
			OCR是一项科技革新,通过自动化大幅减少人工录入的过程,帮助用户从图像或扫描文档中提取文字,并将这些文字转换为计算机可读格式.这一功能在许多需要进一步处理数据的场景中,如身份验证.费用管理.自动报销. ... 
- CSS – 实战 Font
			前言 这篇想整理一下在网页开发中, 字体是如果被处理的. 先看完: 平面设计 – 字体 CSS – Font / Text 属性 CSS – Font Family CSS – word-break, ... 
- .NET全局静态可访问IServiceProvider(支持Blazor)
			DependencyInjection.StaticAccessor 前言 如何在静态方法中访问DI容器长期以来一直都是一个令人苦恼的问题,特别是对于热爱编写扩展方法的朋友.之所以会为这个问题苦恼,是 ... 
- Shiro-认证绕过漏洞(CVE-2020-1957)
			漏洞原理 核心点就是shiro和spring对uri进行配合匹配的时候有缺陷导致的,shiro中很轻易就能绕过,其次spring中对;分号好像情有独钟,被大牛们发现后也就一下子绕过了. 主流paylo ... 
- dfs 验证搜索二叉树——leetcode98
			代码来自leetcode官方 一开始我自己写这个代码时只注意当前节点是否会存在空指针,并没有注意到他的孩子节点也有可能为空,绕了我好久...... /** * Definition for a bin ... 
- 运输小猫娘之再续 5k 传奇之寻找人道主义素数
			原文 前情提要 本章主角 5k_sync_closer 第一章 从再续前缘到苦心寻找满足最优条件的人道主义美丽素数 上回书说到,5k 因为拯救大家被炸断了 \(1000000007\) 米中的十五千米 ... 
- 在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
			ArkTS 是鸿蒙生态的应用开发语言,它在 TypeScript 的基础上进行了优化和定制,以适应鸿蒙系统的需求. 以下是在 ArkTS 中进行有效的内存管理和避免内存泄漏: 1. 使用 const ... 
- 16 模块time、datetime、random、hashlib、logging
			1. 时间模块time.datetime 在python中,表示时间有三种方式:时间戳 格式化的时间字符串(Format String): '2022-03-01' 时间元组(struct_time) ... 
- wpf之样式
			在Window.Resources中书写样式 : <Window.Resources> <Style TargetType="Button" > </ ... 
