Skip to content

Latest commit

 

History

History
834 lines (635 loc) · 31.9 KB

File metadata and controls

834 lines (635 loc) · 31.9 KB

LiYield 合约模块说明

本文档只说明链上合约层。当前合约层的目标不是完整生产协议,而是一个可测试、可演示的 ETF token + Yield Swap 原型。

合约源码:

  • packages/contracts/src/LiYieldVault.sol
  • packages/contracts/src/SettlementIndex.sol
  • packages/contracts/src/YieldSwapMarket.sol
  • packages/contracts/src/MockERC20.sol

测试文件:

  • packages/contracts/test/LiYield.t.sol
  • packages/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

1. LiYieldVault

文件:packages/contracts/src/LiYieldVault.sol

功能定位

LiYieldVault 是 ETF token 的发行层和单链 treasury accounting 层。它继承 OpenZeppelin ERC4626,并额外加入 AccessControlPausable

  • 用户存入底层资产,例如 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 部署/记录操作

Vault 角色

角色 权限
DEFAULT_ADMIN_ROLE 授予/回收其他角色
KEEPER_ROLE deployToTargetdeployToTargetWithPositionrecordDivestmentrecordPositionDivestment
VALUATION_ORACLE_ROLE reportTargetValuereportPositionValue
TREASURY_ROLE setTreasuryRecipient
PAUSER_ROLE pauseunpause
FEE_MANAGER_ROLE setFeeConfig
EMERGENCY_ROLE activateEmergencyMode

Vault Accounting

核心状态:

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 NoneActiveClosed

NAV 口径:

availableIdleAssets = underlying.balanceOf(vault)
totalAssets = availableIdleAssets + totalDeployedAssets

部署资金:

  1. keeper 调用 deployToTargetWithPosition(targetId, recipient, amount, destinationChainId, externalPositionId)
  2. vault 检查 recipient 是否在 treasury recipient 白名单中。
  3. vault 检查 idle assets 足够。
  4. vault 增加 targetPositions[targetId].deployedAssetstotalDeployedAssets
  5. vault 创建 managedPositions[positionId],记录目标链、executor、外部 position id、principal 和初始 reported value。
  6. vault 把底层资产转给 recipient,通常是 treasury safe / executor 地址。

兼容函数 deployToTarget(targetId, recipient, amount) 会创建一个 position,目标链默认为当前链,externalPositionId = bytes32(0)

回收资金:

  1. 外部目标先把底层资产转回 vault,或者 keeper 已经完成跨链退出并把资产送回 vault。
  2. keeper 优先调用 recordPositionDivestment(positionId, valueReduction, closePosition),减少 position 和 target 的 deployed accounting。
  3. 如果只是 target 级别粗记账,keeper 也可以调用 recordDivestment(targetId, amount),但它不会更新 managed position 明细。
  4. totalAssets 会自然反映回款金额与 deployed value 的差额。
  5. 如果 oracle 已把 position reported value 上报为 0,keeper 可以用 recordPositionDivestment(positionId, 0, true) 关闭该归零仓位。

估值更新:

  1. valuation oracle 优先调用 reportPositionValue(positionId, newValue)
  2. vault 更新该 managed position 的 reportedValue,并同步 target 和 totalDeployedAssets
  3. 如果只掌握 target 级别估值,也可以调用 reportTargetValue(targetId, newValue) 直接更新 target 聚合值。
  4. totalAssets 随估值上升或下降。

费用:

  1. fee manager 调用 setFeeConfig(feeRecipient, managementFeeBps) 设置年化 management fee,当前上限是 2000 bps
  2. accruedManagementFeeAssets()totalAssets * feeBps * elapsed / 365 days 计算应计费用资产。
  3. accrueManagementFee() 将应计费用转换成 shares 并 mint 给 feeRecipient
  4. 常规 deposit/mint/withdraw/redeem 前会自动 accrue fee。

紧急模式:

  1. emergency role 调用 activateEmergencyMode(),vault 同时进入 paused 状态。
  2. 常规 deposit/mint/withdraw/redeem 被阻止。
  3. 用户可以调用 emergencyRedeemIdle(shares, receiver, owner),按 idleAssets * shares / totalSupply 拿回当前 idle assets。
  4. 紧急赎回只覆盖 idle assets,不会强行退出已部署的跨链/Earn 仓位;用户烧掉 shares 后放弃对应 deployed claim,因此这是极端故障路径,不是常规赎回路径。
  5. DEFAULT_ADMIN_ROLE 可以调用 deactivateEmergencyMode() 恢复普通模式。

赎回限制:

  • maxWithdrawmaxRedeem 会被 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 治理流程。

2. SettlementIndex

文件: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 绑定。

3. YieldSwapMarket

文件: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 保险金,用于补充亏损/坏账

核心数据结构

Position

struct Position {
    address owner;
    bytes32 termId;
    Side side;
    uint256 collateralAmount;
    uint256 notional;
    uint256 fixedRateBps;
    bool settled;
}

字段说明:

字段 含义
owner 仓位所有者
termId 结算周期
side LongYieldShortYield
collateralAmount 该仓位锁定的 collateral
notional 名义本金
fixedRateBps 开仓成交固定利率
settled 是否已结算或完全清算

TermMarket

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

4. YieldSwapMarket 功能模块

YieldSwapMarket 可以进一步拆成 7 个功能模块。

4.1 抵押品模块

职责:

  • 接收用户 liYIELD
  • 记录用户 collateral。
  • 限制用户只能提取未锁定 collateral。

函数:

函数 说明
depositCollateral(amount) 用户转入 liYIELD,增加 collateralBalance
withdrawCollateral(amount) 用户提取可用 collateral
availableCollateral(user) 返回 collateralBalance - lockedCollateral

事件:

事件 说明
CollateralDeposited(user, amount) 用户存入抵押品
CollateralWithdrawn(user, amount) 用户提取抵押品

风险点:

  • 当前没有最小 collateral amount。
  • 当前没有 pause。
  • 当前不支持多抵押资产。

4.2 协议流动性和保险金模块

职责:

  • 协议作为对手盘,需要有资金支付用户盈利。
  • 保险金用于补足 settlement liquidity 或吸收坏账。

函数:

函数 权限 说明
seedSettlementLiquidity(amount) TREASURY_ROLE 注入 settlement liquidity
fundInsurance(amount) TREASURY_ROLE 注入 insurance fund

事件:

事件 说明
SettlementLiquiditySeeded(amount) 注入 settlement liquidity
InsuranceFunded(amount) 注入 insurance fund

正 PnL 支付顺序:

  1. 先从 settlementLiquidity 支付。
  2. 如果不够,再从 insuranceFundBalance 支付。
  3. 如果还不够,revert InsufficientSettlementLiquidity

4.3 Term 配置模块

职责:

  • 为每个收益率结算周期配置基础报价参数。

函数:

函数 权限 说明
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 参数。

4.4 AMM-like 报价模块

职责:

  • 不让用户外部随意传入 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

4.5 开仓模块

职责:

  • 锁定用户 collateral。
  • 记录独立 Position。
  • 更新 term 的 long/short OI。

函数:

函数 说明
openLongYield(termId, collateralAmount, notional, maxAcceptedRateBps) 开多未来收益率
openShortYield(termId, collateralAmount, notional, minAcceptedRateBps) 开空未来收益率

事件:

事件 说明
PositionOpened(positionId, user, termId, side, collateralAmount, notional, fixedRateBps) 仓位创建

开仓流程:

  1. 检查 availableCollateral(user) >= collateralAmount
  2. quoteFixedRateBps 计算当前 fixed rate。
  3. 校验 long/short 滑点限制。
  4. 分配 positionId
  5. 增加 lockedCollateral
  6. 写入 positions
  7. 记录到 userTermPositionIds
  8. 更新 longOpenInterestshortOpenInterest

4.6 盯市和健康度模块

职责:

  • 根据 mark rate 计算用户未实现 PnL。
  • 根据 equity / notional 计算 margin ratio。
  • 判断是否可清算。

函数:

函数 说明
getAccountHealth(user, termId) 返回 equitymarginRatioBpsisLiquidatable

公式:

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 清算。

4.7 清算模块

职责:

  • 当账户健康度低于维持保证金时,允许第三方清算部分仓位。
  • 减少仓位 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) 仓位被清算

清算流程:

  1. 检查 liquidation size。
  2. 检查账户健康度。
  3. closeNotional 计算该 slice 的 marked PnL。
  4. 把 PnL 应用到用户 collateral / settlement liquidity / insurance。
  5. 收取 liquidation penalty。
  6. 一半 penalty 给 liquidator,一半进入 insurance。
  7. 减少 locked collateral、position collateral、position notional。
  8. 减少 long/short OI。
  9. 如果 notional 归零,则 position 标记 settled。

4.8 到期结算模块

职责:

  • 读取 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

盈利时:

  1. settlementLiquidity 支付。
  2. settlement liquidity 不够时,从 insuranceFundBalance 支付。
  3. 用户 collateral 增加。

亏损时:

  1. 从用户 collateral 中扣除亏损。
  2. 最多扣到该仓位 collateralAmount
  3. 扣除金额进入 settlementLiquidity

事件:

事件 说明
PositionSettled(positionId, user, pnl, realizedRateBps) 仓位结算完成

5. 权限模型

合约权限已经从单一 owner 改为 OpenZeppelin AccessControl 多角色模型。部署时传入的 initialAdmin 会获得:

  • DEFAULT_ADMIN_ROLE
  • KEEPER_ROLE
  • PAUSER_ROLE
  • VALUATION_ORACLE_ROLE
  • FEE_MANAGER_ROLE
  • EMERGENCY_ROLE
  • SETTLEMENT_ORACLE_ROLE
  • TREASURY_ROLE
  • RISK_MANAGER_ROLE
  • TERM_MANAGER_ROLE
  • MARK_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

6. 错误类型

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 清算数量无效或超过最大比例

7. 已实现测试覆盖

单元/行为测试

文件: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 不利 -> 部分清算 -> 剩余仓位结算

8. 当前缺口

合约层还没有生产级完成,主要缺口如下:

Vault 缺口

  • 没有 keeper execution service。
  • 没有 LI.FI Composer 执行状态机。
  • 没有真实 LI.FI execution id / receipt token 校验。
  • 没有 oracle 签名验证的外部 position redeem value。
  • 没有 performance fee 和 high-water mark。
  • 没有 emergency unwind 已部署跨链/Earn 仓位。

SettlementIndex 缺口

  • 没有 term maturity。
  • 没有防重复发布。
  • 没有 dispute window。
  • 没有多签/oracle quorum。
  • 没有 settlement proof hash。

YieldSwapMarket 缺口

  • 没有 term open/close/maturity 生命周期。
  • 没有 funding fee。
  • 没有 protocol fee。
  • 没有 isolated margin。
  • 没有多 collateral。
  • 没有批量结算。
  • 没有 keeper role。
  • 没有 emergency pause。
  • 没有 fuzz/invariant tests。

9. 下一批建议测试

优先补这些合约测试:

优先级 测试场景 目的
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 不凭空增发 验证市场资金守恒