本文档只说明链上合约层。当前合约层的目标不是完整生产协议,而是一个可测试、可演示的 ETF token + Yield Swap 原型。
合约源码:
packages/contracts/src/LiYieldVault.solpackages/contracts/src/SettlementIndex.solpackages/contracts/src/YieldSwapMarket.solpackages/contracts/src/MockERC20.sol
测试文件:
packages/contracts/test/LiYield.t.solpackages/contracts/test/LiYieldScenario.t.sol
User underlying asset
|
v
LiYieldVault (ERC-4626)
|
| mints shares
v
liYIELD ETF token
|
| user deposits as collateral
v
YieldSwapMarket
|
| reads realized yield at maturity
v
SettlementIndex
模块职责:
| 合约 | 主要职责 | 当前状态 |
|---|---|---|
LiYieldVault |
接收底层资产,铸造 liYIELD ETF share token,记录 target/position 级 accounting,支持 keeper 部署/回收、oracle 估值、管理费和紧急 idle 赎回 |
Treasury vault 核心 accounting 已实现 |
SettlementIndex |
发布某个 term 的 realized strategy yield | SETTLEMENT_ORACLE_ROLE 发布 |
YieldSwapMarket |
抵押 liYIELD,开多/开空未来收益率,AMM 报价,盯市,清算,到期结算 |
已有核心骨架 |
MockERC20 |
本地 demo 和测试用 ERC-20 | 仅 demo/test |
文件:packages/contracts/src/LiYieldVault.sol
LiYieldVault 是 ETF token 的发行层和单链 treasury accounting 层。它继承 OpenZeppelin ERC4626,并额外加入 AccessControl 和 Pausable:
- 用户存入底层资产,例如 demo 中的
dUSDC。 - vault 给用户铸造 share token。
- share token 名称是
LiYield Index Share,symbol 是liYIELD。 - 这些
liYIELD后续可以作为 Yield Swap 的抵押品。 - keeper 可以把 idle assets 调拨到白名单 treasury/executor 地址,并记录 deployed position value。
- valuation oracle 独立上报外部多链仓位估值。
- vault 记录 managed position id、目标链、executor 和外部 position id,方便 keeper/oracle 对账。
- fee manager 可以配置 management fee,vault 会以增发 share 的方式收取费用。
- emergency role 可以进入紧急模式,暂停常规存取,并允许用户按 share 比例赎回当前 idle assets。
totalAssets()返回idle assets + deployed assets,为后续 NAV 驱动 share price 做准备。
| 功能 | 来源 | 说明 |
|---|---|---|
deposit(assets, receiver) |
ERC-4626 | 存入底层资产,铸造 liYIELD |
mint(shares, receiver) |
ERC-4626 | 指定 shares 数量铸造 |
withdraw(assets, receiver, owner) |
ERC-4626 | 按底层资产数量赎回 |
redeem(shares, receiver, owner) |
ERC-4626 | 按 share 数量赎回 |
balanceOf(user) |
ERC-20 | 用户持有的 liYIELD 数量 |
availableIdleAssets() |
LiYieldVault | 当前 vault 合约中未部署的底层资产 |
totalAssets() |
Override | 返回 availableIdleAssets() + totalDeployedAssets |
totalManagedAssets() |
LiYieldVault | 显式读取 ETF 管理资产总额,当前等于 totalAssets() |
deployedAssets(targetId) |
LiYieldVault | 读取某个 target 的 deployed value |
managedPositions(positionId) |
LiYieldVault | 读取单个 managed position 的 target、目标链、executor、外部 position id、principal 和 reported value |
getTargetManagedPositionIds(targetId) |
LiYieldVault | 读取某个 target 下所有 managed position id |
setTreasuryRecipient(recipient, approved) |
LiYieldVault | treasury role 管理允许接收 vault 资金的 treasury/executor 地址 |
deployToTarget(targetId, recipient, amount) |
LiYieldVault | keeper 从 vault idle assets 调拨资金到白名单 recipient,并增加 deployed accounting |
deployToTargetWithPosition(targetId, recipient, amount, destinationChainId, externalPositionId) |
LiYieldVault | keeper 部署资金并创建 managed position |
recordDivestment(targetId, amount) |
LiYieldVault | keeper 记录某个 target 的 deployed value 减少;外部回款应已进入 vault |
recordPositionDivestment(positionId, valueReduction, closePosition) |
LiYieldVault | keeper 按 managed position 记录退出、部分退出或关闭 |
reportTargetValue(targetId, newValue) |
LiYieldVault | valuation oracle 上报某个 target 当前估值,用于反映收益或损失 |
reportPositionValue(positionId, newValue) |
LiYieldVault | valuation oracle 上报某个 managed position 当前估值 |
setFeeConfig(feeRecipient, managementFeeBps) |
LiYieldVault | fee manager 设置管理费收款地址和年化费率 |
accruedManagementFeeAssets() / accrueManagementFee() |
LiYieldVault | 计算并收取 management fee,费用以新增 liYIELD shares 表示 |
activateEmergencyMode() / deactivateEmergencyMode() |
LiYieldVault | 进入或退出紧急模式 |
emergencyRedeemIdle(shares, receiver, owner) |
LiYieldVault | 紧急模式下按 share 比例赎回 vault 当前 idle assets |
pause() / unpause() |
Pausable | 暂停或恢复用户存取和 keeper 部署/记录操作 |
| 角色 | 权限 |
|---|---|
DEFAULT_ADMIN_ROLE |
授予/回收其他角色 |
KEEPER_ROLE |
deployToTarget、deployToTargetWithPosition、recordDivestment、recordPositionDivestment |
VALUATION_ORACLE_ROLE |
reportTargetValue、reportPositionValue |
TREASURY_ROLE |
setTreasuryRecipient |
PAUSER_ROLE |
pause、unpause |
FEE_MANAGER_ROLE |
setFeeConfig |
EMERGENCY_ROLE |
activateEmergencyMode |
核心状态:
uint256 public totalDeployedAssets;
mapping(bytes32 targetId => TargetPosition position) public targetPositions;
mapping(uint256 positionId => ManagedPosition position) public managedPositions;
mapping(bytes32 targetId => uint256[] positionIds) private targetManagedPositionIds;TargetPosition:
| 字段 | 含义 |
|---|---|
deployedAssets |
当前 target 的已部署资产估值 |
lastUpdated |
最近一次 accounting 更新时间 |
exists |
target 是否已被创建 |
ManagedPosition:
| 字段 | 含义 |
|---|---|
targetId |
该仓位归属的 strategy target / Earn target |
destinationChainId |
keeper 执行后的目标链 id |
executor |
接收 vault 资金的 treasury/executor 地址 |
externalPositionId |
链下或目标协议的外部仓位标识,例如 LI.FI execution id、receipt id 或 keeper 自定义 id |
principalAssets |
该 position 当前剩余 principal 记账值 |
reportedValue |
oracle 最近一次上报的可赎回估值 |
openedAt |
position 创建时间 |
lastUpdated |
最近一次 position accounting 更新时间 |
status |
None、Active、Closed |
NAV 口径:
availableIdleAssets = underlying.balanceOf(vault)
totalAssets = availableIdleAssets + totalDeployedAssets
部署资金:
- keeper 调用
deployToTargetWithPosition(targetId, recipient, amount, destinationChainId, externalPositionId)。 - vault 检查
recipient是否在 treasury recipient 白名单中。 - vault 检查 idle assets 足够。
- vault 增加
targetPositions[targetId].deployedAssets和totalDeployedAssets。 - vault 创建
managedPositions[positionId],记录目标链、executor、外部 position id、principal 和初始 reported value。 - vault 把底层资产转给
recipient,通常是 treasury safe / executor 地址。
兼容函数 deployToTarget(targetId, recipient, amount) 会创建一个 position,目标链默认为当前链,externalPositionId = bytes32(0)。
回收资金:
- 外部目标先把底层资产转回 vault,或者 keeper 已经完成跨链退出并把资产送回 vault。
- keeper 优先调用
recordPositionDivestment(positionId, valueReduction, closePosition),减少 position 和 target 的 deployed accounting。 - 如果只是 target 级别粗记账,keeper 也可以调用
recordDivestment(targetId, amount),但它不会更新 managed position 明细。 totalAssets会自然反映回款金额与 deployed value 的差额。- 如果 oracle 已把 position reported value 上报为 0,keeper 可以用
recordPositionDivestment(positionId, 0, true)关闭该归零仓位。
估值更新:
- valuation oracle 优先调用
reportPositionValue(positionId, newValue)。 - vault 更新该 managed position 的
reportedValue,并同步 target 和totalDeployedAssets。 - 如果只掌握 target 级别估值,也可以调用
reportTargetValue(targetId, newValue)直接更新 target 聚合值。 totalAssets随估值上升或下降。
费用:
- fee manager 调用
setFeeConfig(feeRecipient, managementFeeBps)设置年化 management fee,当前上限是2000 bps。 accruedManagementFeeAssets()按totalAssets * feeBps * elapsed / 365 days计算应计费用资产。accrueManagementFee()将应计费用转换成 shares 并 mint 给feeRecipient。- 常规
deposit/mint/withdraw/redeem前会自动 accrue fee。
紧急模式:
- emergency role 调用
activateEmergencyMode(),vault 同时进入 paused 状态。 - 常规
deposit/mint/withdraw/redeem被阻止。 - 用户可以调用
emergencyRedeemIdle(shares, receiver, owner),按idleAssets * shares / totalSupply拿回当前 idle assets。 - 紧急赎回只覆盖 idle assets,不会强行退出已部署的跨链/Earn 仓位;用户烧掉 shares 后放弃对应 deployed claim,因此这是极端故障路径,不是常规赎回路径。
DEFAULT_ADMIN_ROLE可以调用deactivateEmergencyMode()恢复普通模式。
赎回限制:
maxWithdraw和maxRedeem会被 idle assets 限制。- 即使
totalAssets包含 deployed assets,用户也只能赎回 vault 当前可用的 idle assets。
- 还没有真实 Earn adapter。
- 还没有真实 LI.FI Composer 执行入口。
- 还没有链下 keeper 执行状态机和交易回执校验。
- 还没有 oracle 签名报告、quorum、延迟窗口或 dispute 机制。
- 还没有 performance fee。
- 还没有强制退出已部署跨链/Earn 仓位的 emergency unwind。
LiYieldVault 后续需要从“accounting vault 骨架”升级为“真实跨链 ETF vault”。如果坚持单链部署 vault,则跨链执行和目标链查询主要由链下 keeper/oracle 完成:
- 增加 keeper execution service,用 LI.FI Composer 执行部署/退出。
- 增加 oracle report service,查询多链仓位并上报估值。
- 将
externalPositionId绑定真实 LI.FI execution id、目标协议 receipt token 或 custody ledger id。 reportPositionValue/reportTargetValue应接入可信 oracle / 多签 / 签名报告。- 增加 performance fee、fee high-water mark 和 fee recipient 治理流程。
文件:packages/contracts/src/SettlementIndex.sol
SettlementIndex 是 Yield Swap 的结算指数。它回答一个问题:
某个 term 到期后,这只 ETF strategy 在该期间实际实现的收益率是多少?
例如:
TERM-7D-001- realized yield =
800 bps - 表示该 term 的最终结算收益率为 8%
struct Settlement {
uint256 realizedRateBps;
uint256 publishedAt;
bool isSet;
}
mapping(bytes32 termId => Settlement settlement) public settlements;字段说明:
| 字段 | 含义 |
|---|---|
realizedRateBps |
最终实现收益率,单位 bps,10000 = 100% |
publishedAt |
发布时间 |
isSet |
是否已发布 |
| 函数 | 权限 | 作用 |
|---|---|---|
setSettlement(termId, realizedRateBps) |
SETTLEMENT_ORACLE_ROLE |
发布某个 term 的 realized yield |
getSettlement(termId) |
public view | 查询某个 term 的 realized yield |
| 事件 | 说明 |
|---|---|
SettlementUpdated(termId, realizedRateBps, publishedAt) |
某个 term 的 settlement 被发布或更新 |
- 目前是授权 oracle 角色直接发布,仍属于 trusted oracle。
- 没有检查 term 是否已过期。
- 没有限制重复发布。
- 没有 dispute period。
- 没有多 oracle / quorum。
- 没有把 realized yield 的计算过程上链证明。
- 增加 term 生命周期:
created -> active -> matured -> settling -> settled。 - settlement 只能在 maturity 后发布。
- 增加 dispute window。
- 增加 oracle signer set 或 multisig。
- 支持更新前后事件审计。
- 将 realized yield 计算过程和 snapshot hash 绑定。
文件:packages/contracts/src/YieldSwapMarket.sol
YieldSwapMarket 是收益率多空市场。用户不是直接买卖 ETF 价格,而是交易:
某个 term 内 ETF strategy 的最终 realized yield 会高于还是低于开仓时成交的 fixed rate。
用户用 liYIELD 做抵押,开两类仓位:
LongYield:做多未来收益率。realized rate 高于 fixed rate 时盈利。ShortYield:做空未来收益率。realized rate 低于 fixed rate 时盈利。
| 概念 | 说明 |
|---|---|
termId |
一个收益率结算周期,例如 7D 或 30D term |
fixedRateBps |
用户开仓时锁定的固定收益率 |
realizedRateBps |
term 到期后由 SettlementIndex 发布的实际收益率 |
notional |
仓位名义本金,用于计算 PnL |
collateralAmount |
该仓位锁定的 liYIELD 保证金 |
markRateBps |
当前盯市收益率,用于健康度和清算 |
settlementLiquidity |
协议对手盘流动性,用于支付用户盈利 |
insuranceFundBalance |
保险金,用于补充亏损/坏账 |
struct Position {
address owner;
bytes32 termId;
Side side;
uint256 collateralAmount;
uint256 notional;
uint256 fixedRateBps;
bool settled;
}字段说明:
| 字段 | 含义 |
|---|---|
owner |
仓位所有者 |
termId |
结算周期 |
side |
LongYield 或 ShortYield |
collateralAmount |
该仓位锁定的 collateral |
notional |
名义本金 |
fixedRateBps |
开仓成交固定利率 |
settled |
是否已结算或完全清算 |
struct TermMarket {
uint256 baseRateBps;
uint256 rateScalarBps;
uint256 markRateBps;
uint256 longOpenInterest;
uint256 shortOpenInterest;
bool isConfigured;
bool hasMarkRate;
}字段说明:
| 字段 | 含义 |
|---|---|
baseRateBps |
term 的基础 fixed rate |
rateScalarBps |
AMM-like 曲线斜率 |
markRateBps |
当前盯市收益率 |
longOpenInterest |
long 方向总 OI |
shortOpenInterest |
short 方向总 OI |
isConfigured |
term 是否已配置 |
hasMarkRate |
是否已有 mark rate |
| 变量 | 说明 |
|---|---|
liToken |
抵押资产,即 liYIELD |
settlementIndex |
settlement oracle 合约 |
nextPositionId |
下一个仓位 ID |
settlementLiquidity |
用于支付正 PnL 的协议流动性 |
insuranceFundBalance |
保险基金余额 |
maintenanceMarginBps |
维持保证金率,默认 1500 bps |
liquidationPenaltyBps |
清算罚金,默认 500 bps |
maxLiquidationPortionBps |
单次最大清算比例,默认 5000 bps |
positions |
positionId 到 Position |
collateralBalance |
用户总 collateral |
lockedCollateral |
用户已锁 collateral |
termMarkets |
termId 到 TermMarket |
YieldSwapMarket 可以进一步拆成 7 个功能模块。
职责:
- 接收用户
liYIELD。 - 记录用户 collateral。
- 限制用户只能提取未锁定 collateral。
函数:
| 函数 | 说明 |
|---|---|
depositCollateral(amount) |
用户转入 liYIELD,增加 collateralBalance |
withdrawCollateral(amount) |
用户提取可用 collateral |
availableCollateral(user) |
返回 collateralBalance - lockedCollateral |
事件:
| 事件 | 说明 |
|---|---|
CollateralDeposited(user, amount) |
用户存入抵押品 |
CollateralWithdrawn(user, amount) |
用户提取抵押品 |
风险点:
- 当前没有最小 collateral amount。
- 当前没有 pause。
- 当前不支持多抵押资产。
职责:
- 协议作为对手盘,需要有资金支付用户盈利。
- 保险金用于补足 settlement liquidity 或吸收坏账。
函数:
| 函数 | 权限 | 说明 |
|---|---|---|
seedSettlementLiquidity(amount) |
TREASURY_ROLE |
注入 settlement liquidity |
fundInsurance(amount) |
TREASURY_ROLE |
注入 insurance fund |
事件:
| 事件 | 说明 |
|---|---|
SettlementLiquiditySeeded(amount) |
注入 settlement liquidity |
InsuranceFunded(amount) |
注入 insurance fund |
正 PnL 支付顺序:
- 先从
settlementLiquidity支付。 - 如果不够,再从
insuranceFundBalance支付。 - 如果还不够,revert
InsufficientSettlementLiquidity。
职责:
- 为每个收益率结算周期配置基础报价参数。
函数:
| 函数 | 权限 | 说明 |
|---|---|---|
configureTerm(termId, baseRateBps, rateScalarBps) |
TERM_MANAGER_ROLE |
配置某个 term 的 AMM quote 参数 |
updateMarkRate(termId, markRateBps) |
MARK_RATE_ORACLE_ROLE |
更新 term 当前 mark rate |
事件:
| 事件 | 说明 |
|---|---|
TermConfigured(termId, baseRateBps, rateScalarBps) |
term 配置更新 |
MarkRateUpdated(termId, markRateBps) |
mark rate 更新 |
当前限制:
- term 没有 maturity 时间。
- term 没有 open/close 窗口。
TERM_MANAGER_ROLE可以覆盖 term 参数。
职责:
- 不让用户外部随意传入 fixed rate。
- 每笔仓位的
fixedRateBps由链上曲线根据当前市场状态计算。
函数:
| 函数 | 说明 |
|---|---|
quoteFixedRateBps(termId, side, notional) |
返回这笔交易当前可成交 fixed rate |
当前公式:
openInterestSkew = longOpenInterest - shortOpenInterest
tradeSkew = long ? +notional : -notional
postTradeSkew = openInterestSkew + tradeSkew
effectiveLiquidity = settlementLiquidity + insuranceFundBalance
curveShiftBps = rateScalarBps * postTradeSkew / effectiveLiquidity
quotedRateBps = baseRateBps + curveShiftBps
直觉:
- long OI 越多,继续 long 的 fixed rate 越高。
- short OI 越多,继续 short 会把 fixed rate 压低。
- liquidity 越大,同样 notional 对价格影响越小。
滑点保护:
openLongYield使用maxAcceptedRateBps。openShortYield使用minAcceptedRateBps。- 如果当前 quote 超出用户接受范围,revert
RateSlippage。
职责:
- 锁定用户 collateral。
- 记录独立 Position。
- 更新 term 的 long/short OI。
函数:
| 函数 | 说明 |
|---|---|
openLongYield(termId, collateralAmount, notional, maxAcceptedRateBps) |
开多未来收益率 |
openShortYield(termId, collateralAmount, notional, minAcceptedRateBps) |
开空未来收益率 |
事件:
| 事件 | 说明 |
|---|---|
PositionOpened(positionId, user, termId, side, collateralAmount, notional, fixedRateBps) |
仓位创建 |
开仓流程:
- 检查
availableCollateral(user) >= collateralAmount。 - 用
quoteFixedRateBps计算当前 fixed rate。 - 校验 long/short 滑点限制。
- 分配
positionId。 - 增加
lockedCollateral。 - 写入
positions。 - 记录到
userTermPositionIds。 - 更新
longOpenInterest或shortOpenInterest。
职责:
- 根据 mark rate 计算用户未实现 PnL。
- 根据 equity / notional 计算 margin ratio。
- 判断是否可清算。
函数:
| 函数 | 说明 |
|---|---|
getAccountHealth(user, termId) |
返回 equity、marginRatioBps、isLiquidatable |
公式:
equity = collateralBalance + Σ(unrealizedPnL)
marginRatioBps = equity * 10000 / totalNotional
isLiquidatable = marginRatioBps < maintenanceMarginBps
mark PnL 当前方向:
LongYield markedPnl = notional * (fixedRate - markRate) / 10000
ShortYield markedPnl = notional * (markRate - fixedRate) / 10000
解释:
- 对 long yield 来说,mark rate 上升代表新开仓 fixed rate 更高,旧 long 仓位相对变差。
- 对 short yield 来说,mark rate 上升代表旧 short 仓位相对变好。
当前限制:
- 健康度是按用户 + term 聚合,但 collateral 是用户级 cross-margin。
- 没有 isolated margin。
- 没有自动 keeper 清算。
职责:
- 当账户健康度低于维持保证金时,允许第三方清算部分仓位。
- 减少仓位 notional 和 locked collateral。
- 给 liquidator 支付奖励。
- 给 insurance fund 分配部分罚金。
函数:
| 函数 | 说明 |
|---|---|
liquidatePosition(positionId, closeNotional) |
部分或全部清算一个仓位 |
约束:
- position 必须存在。
- position 不能已 settled。
closeNotional > 0。closeNotional <= position.notional。- 单次关闭比例不能超过
maxLiquidationPortionBps。 getAccountHealth必须返回isLiquidatable == true。
事件:
| 事件 | 说明 |
|---|---|
PositionLiquidated(positionId, liquidator, closedNotional, markRateBps, pnl, penaltyAmount, remainingNotional) |
仓位被清算 |
清算流程:
- 检查 liquidation size。
- 检查账户健康度。
- 按
closeNotional计算该 slice 的 marked PnL。 - 把 PnL 应用到用户 collateral / settlement liquidity / insurance。
- 收取 liquidation penalty。
- 一半 penalty 给 liquidator,一半进入 insurance。
- 减少 locked collateral、position collateral、position notional。
- 减少 long/short OI。
- 如果 notional 归零,则 position 标记 settled。
职责:
- 读取
SettlementIndex发布的 realized yield。 - 将仓位从 open 状态结算为 settled。
- 释放 locked collateral。
- 根据 fixed vs realized 计算最终 PnL。
函数:
| 函数 | 说明 |
|---|---|
settlePosition(positionId) |
到期结算某个仓位 |
PnL 规则:
rateDelta = abs(realizedRateBps - fixedRateBps)
pnlMagnitude = notional * rateDelta / 10000
LongYield wins if realizedRateBps >= fixedRateBps
ShortYield wins if realizedRateBps < fixedRateBps
盈利时:
- 从
settlementLiquidity支付。 - settlement liquidity 不够时,从
insuranceFundBalance支付。 - 用户 collateral 增加。
亏损时:
- 从用户 collateral 中扣除亏损。
- 最多扣到该仓位
collateralAmount。 - 扣除金额进入
settlementLiquidity。
事件:
| 事件 | 说明 |
|---|---|
PositionSettled(positionId, user, pnl, realizedRateBps) |
仓位结算完成 |
合约权限已经从单一 owner 改为 OpenZeppelin AccessControl 多角色模型。部署时传入的 initialAdmin 会获得:
DEFAULT_ADMIN_ROLEKEEPER_ROLEPAUSER_ROLEVALUATION_ORACLE_ROLEFEE_MANAGER_ROLEEMERGENCY_ROLESETTLEMENT_ORACLE_ROLETREASURY_ROLERISK_MANAGER_ROLETERM_MANAGER_ROLEMARK_RATE_ORACLE_ROLE
DEFAULT_ADMIN_ROLE 负责授予和回收其他角色。生产环境中这个角色应该由 multisig/timelock 持有,而不是普通 EOA。
| 操作 | 权限 |
|---|---|
| 用户存入/提取 vault | 用户自己 |
| vault 设置白名单 treasury/executor recipient | LiYieldVault.TREASURY_ROLE |
| vault 部署资金到白名单 recipient | LiYieldVault.KEEPER_ROLE |
| vault 记录 divestment | LiYieldVault.KEEPER_ROLE |
| vault 上报 target value | LiYieldVault.VALUATION_ORACLE_ROLE |
| vault 配置 management fee | LiYieldVault.FEE_MANAGER_ROLE |
| vault 激活 emergency mode | LiYieldVault.EMERGENCY_ROLE |
| vault 退出 emergency mode | DEFAULT_ADMIN_ROLE |
| vault 紧急赎回 idle assets | share owner 或已获 allowance 的 spender |
| vault pause/unpause | LiYieldVault.PAUSER_ROLE |
| 用户抵押/提取 collateral | 用户自己 |
| 用户开 long/short 仓位 | 用户自己 |
用户或任何人调用 settlePosition |
任意地址 |
| 清算仓位 | 任意地址,只要仓位可清算 |
| 发布 realized yield | SettlementIndex.SETTLEMENT_ORACLE_ROLE |
| 配置 term | YieldSwapMarket.TERM_MANAGER_ROLE |
| 更新 mark rate | YieldSwapMarket.MARK_RATE_ORACLE_ROLE |
| 注入 settlement liquidity | YieldSwapMarket.TREASURY_ROLE |
| 注入 insurance fund | YieldSwapMarket.TREASURY_ROLE |
| 更新 risk parameters | YieldSwapMarket.RISK_MANAGER_ROLE |
| 授予/回收角色 | DEFAULT_ADMIN_ROLE |
建议生产角色分配:
| 角色 | 建议持有方 |
|---|---|
DEFAULT_ADMIN_ROLE |
Governance multisig + timelock |
KEEPER_ROLE |
Keeper executor multisig / automation |
PAUSER_ROLE |
Guardian multisig |
VALUATION_ORACLE_ROLE |
Valuation oracle signer / NAV reporter |
FEE_MANAGER_ROLE |
Governance multisig / fee controller |
EMERGENCY_ROLE |
Guardian multisig / emergency council |
SETTLEMENT_ORACLE_ROLE |
Settlement oracle signer / keeper multisig |
MARK_RATE_ORACLE_ROLE |
Mark-rate oracle 或风控 keeper |
TERM_MANAGER_ROLE |
Strategy governance / product ops multisig |
RISK_MANAGER_ROLE |
Risk committee multisig |
TREASURY_ROLE |
Treasury multisig |
| Error | 触发场景 |
|---|---|
ZeroAddress |
构造函数地址为 0 |
InvalidTarget |
vault targetId 或 recipient 无效,或 target 尚未创建 |
InsufficientIdleAssets |
vault idle assets 不足,或紧急赎回可取 idle 为 0 |
DeployedAssetsUnderflow |
vault divestment 超过已部署记账值 |
TreasuryRecipientNotApproved |
keeper 试图向未白名单 treasury/executor 调拨资金 |
InvalidFeeConfig |
fee recipient 为 0 或 management fee 超过上限 |
EmergencyModeNotActive |
非紧急模式下调用 emergency redeem |
EmergencyModeActive |
紧急模式下调用常规存取 |
InsufficientAvailableCollateral |
可用 collateral 不足 |
InvalidPosition |
position 不存在 |
InvalidAmount |
vault/market 输入金额无效 |
AlreadySettled |
position 已结算或已完全清算 |
SettlementNotAvailable |
settlement 尚未发布 |
InsufficientSettlementLiquidity |
settlement + insurance 不足以支付正 PnL |
TermNotConfigured |
term 未配置 |
RateSlippage |
开仓 quote 超出用户限制 |
MarkRateNotAvailable |
mark rate 尚未发布 |
PositionNotLiquidatable |
仓位未达到清算条件 |
InvalidLiquidationSize |
清算数量无效或超过最大比例 |
文件:packages/contracts/test/LiYield.t.sol
| 测试 | 覆盖功能 |
|---|---|
test_openLongYieldAndSettleProfit |
long yield 盈利结算 |
test_openShortYieldAndSettleLoss |
short yield 亏损结算 |
test_initialAdminHasAllProtocolRoles |
部署初始 admin 拥有所有协议角色 |
test_vaultKeeperDeploysAndRecordsDivestment |
vault keeper 部署资金并记录退出 accounting |
test_vaultKeeperCanReportTargetValueChange |
valuation oracle 上报 target value 改变 totalAssets |
test_vaultKeeperCannotDeployToUnapprovedTreasuryRecipient |
vault keeper 不能把资金调拨到未白名单 recipient |
test_vaultKeeperAndValuationOracleRolesAreSeparated |
vault keeper 和 valuation oracle 权限分离 |
test_vaultPositionAccountingTracksExecutionMetadataAndTargetIndex |
vault managed position 记录目标链、executor、外部 position id,并可按 target 查询 position id |
test_vaultPositionValueReportAndDivestmentUpdateAggregates |
position 级估值上报、部分退出和关闭会同步 target/total deployed accounting |
test_vaultCanCloseZeroValuePositionAfterOracleMarksLoss |
oracle 将 position 估值上报为 0 后,keeper 可以关闭归零仓位 |
test_vaultAccruesManagementFeeAsShares |
management fee 以增发 shares 方式计提给 fee recipient |
test_vaultEmergencyRedeemIdleAllowsProRataIdleExitWhilePaused |
emergency mode 下用户可按 share 比例赎回 idle assets |
test_vaultRedeemCapacityIsLimitedByIdleAssets |
vault 资产部署后,用户最大赎回/提现能力受 idle assets 限制 |
test_vaultRolesAreEnforced |
vault keeper/pauser 权限被强制校验 |
test_vaultPauseBlocksUserAndKeeperFlows |
vault pause 后阻止用户和 keeper 操作,unpause 后恢复 |
test_settlementOracleRoleCanBeGrantedSeparately |
settlement oracle 角色可独立授予并发布 settlement |
test_nonSettlementOracleCannotPublishSettlement |
非 settlement oracle 不能发布 settlement |
test_marketRolesAreEnforcedIndependently |
term、mark、risk、treasury 权限彼此独立且被强制校验 |
test_grantedMarketRolesCanPerformOnlyTheirOwnActions |
被授予的市场角色只能执行对应职责 |
test_cannotWithdrawLockedCollateral |
锁定 collateral 不可提取 |
test_longQuoteMovesHigherAfterLongOpenInterest |
AMM quote 随 long OI 增加而抬高 |
test_openLongYieldRevertsWhenRateMovesPastLimit |
stale quote / 滑点保护 |
test_getAccountHealthBecomesLiquidatable |
mark rate 导致账户可清算 |
test_liquidatePositionPartial |
部分清算 |
test_cannotLiquidateHealthyPosition |
健康仓位不可清算 |
文件:packages/contracts/test/LiYieldScenario.t.sol
| 测试 | 覆盖场景 |
|---|---|
test_scenarioVaultDepositCollateralLongYieldAndSettlementWithdrawal |
vault deposit -> collateral -> long yield -> settlement -> withdraw |
test_scenarioShortYieldWinsWhenRealizedYieldFalls |
short yield 在 realized yield 下跌时盈利 |
test_scenarioPositionCannotSettleBeforeSettlementIndexPublishes |
settlement 未发布前不可结算 |
test_scenarioLiquidationCannotCloseMoreThanConfiguredMaxPortion |
清算不能超过最大关闭比例 |
test_scenarioAdverseMarkRateLiquidatesBeforeSettlementThenRemainingPositionSettles |
mark rate 不利 -> 部分清算 -> 剩余仓位结算 |
合约层还没有生产级完成,主要缺口如下:
- 没有 keeper execution service。
- 没有 LI.FI Composer 执行状态机。
- 没有真实 LI.FI execution id / receipt token 校验。
- 没有 oracle 签名验证的外部 position redeem value。
- 没有 performance fee 和 high-water mark。
- 没有 emergency unwind 已部署跨链/Earn 仓位。
- 没有 term maturity。
- 没有防重复发布。
- 没有 dispute window。
- 没有多签/oracle quorum。
- 没有 settlement proof hash。
- 没有 term open/close/maturity 生命周期。
- 没有 funding fee。
- 没有 protocol fee。
- 没有 isolated margin。
- 没有多 collateral。
- 没有批量结算。
- 没有 keeper role。
- 没有 emergency pause。
- 没有 fuzz/invariant tests。
优先补这些合约测试:
| 优先级 | 测试场景 | 目的 |
|---|---|---|
| P0 | settlement liquidity 不足但 insurance 足够 | 验证正 PnL 支付 fallback |
| P0 | settlement liquidity 和 insurance 都不足 | 验证 InsufficientSettlementLiquidity |
| P0 | mark rate 未发布时调用 health | 验证 MarkRateNotAvailable |
| P0 | term 未配置时 quote/open | 验证 TermNotConfigured |
| P1 | 同一用户同一 term 多仓位 | 验证 health 聚合逻辑 |
| P1 | long 和 short OI 对 quote 的相反影响 | 验证 AMM skew |
| P1 | 完全清算仓位 | 验证 position settled 和 OI 完全归零 |
| P1 | RISK_MANAGER_ROLE 更新 risk parameters |
验证清算阈值变化 |
| P1 | vault fee config 权限和 fee 上限 | 验证 fee manager 角色和最大费率 |
| P1 | vault emergency redeem allowance 路径 | 验证 spender 可代 owner 紧急赎回 |
| P2 | fuzz:随机 fixed/realized/notional/collateral | 验证 PnL 不穿透 accounting |
| P2 | invariant:总 liYIELD accounting 不凭空增发 |
验证市场资金守恒 |