Skip to content

Latest commit

 

History

History
660 lines (507 loc) · 45.7 KB

File metadata and controls

660 lines (507 loc) · 45.7 KB

LiYield 后端测试规格

本文档说明 LiYield 后端测试体系。这里的“后端”不是单指 HTTP server,而是三层:

  • contracts:链上状态层,负责 vault、ETF token、Yield Swap 仓位、结算、清算。
  • keeper:链下执行层,负责根据策略结果生成调仓任务、生成 allocation plan,后续会负责执行 LI.FI/Earn 交易、清算 watcher、settlement 发布。
  • server:API 与数据层,负责 ETF/strategy 数据、价格历史、snapshot、LI.FI quote 封装、keeper/admin 接口。

目标是每个模块都能独立测试,之后再逐步接入:

  1. 先测纯函数和模块行为。
  2. 再测单个服务的输入输出。
  3. 再测跨模块业务场景。
  4. 最后测真实链上/外部 API 集成。

运行命令

运行所有后端测试:

npm run backend:test

只运行 server + keeper 测试:

npm run server:test

只运行合约测试:

cd packages/contracts
forge test

类型检查:

npm run typecheck -w @liyield/server

模块总览

模块 代码位置 当前职责 当前测试方式
LiYieldVault packages/contracts/src/LiYieldVault.sol ERC-4626 vault,用户存入底层资产并获得 liYIELD ETF token;记录 target/position 级 idle/deployed accounting;支持 keeper 部署/回收、oracle 估值、management fee 和 emergency idle redeem Foundry 合约测试、链上场景测试
SettlementIndex packages/contracts/src/SettlementIndex.sol 发布某个 term 的 realized strategy yield,作为 Yield Swap 到期结算基准 Foundry 合约测试、链上场景测试
YieldSwapMarket packages/contracts/src/YieldSwapMarket.sol 抵押 liYIELD、AMM-like fixed rate quote、开多/开空收益率、盯市、清算、到期结算 Foundry 合约测试、链上场景测试
strategyEngine packages/server/src/services/strategyEngine.ts 按过去 7 天 APY 排名、选择 Top N、计算权重、生成 strategy summary Node 单元测试
allocationPlan packages/server/src/services/allocationPlan.ts 按权重拆分 treasury 可投资金额,对每个目标生成 LI.FI quote 计划 Node 单元测试、场景测试,quote fetcher 使用 mock
earn packages/server/src/services/earn.ts 接 LI.FI Earn Data API,读取 vault APY、TVL、transactional capability 和 portfolio positions,并提供静态 fallback Node 单元测试,外部 fetch 使用 mock
earnSnapshot packages/server/src/services/earnSnapshot.tsearnSnapshotStore.ts 定时采集候选 Earn targets 的 APY/TVL/权重/排名并持久化历史快照 Node 单元测试、API 测试,持久化使用临时文件隔离
rebalancePlanner packages/server/src/keeper/rebalancePlanner.ts 把 snapshot diff 转换成 keeper 可执行任务:exitenterrebalance Node 单元测试、场景测试
keeperExecution packages/server/src/keeper/execution.tsorchestrator.tsreconcile.tsonchain.tsrunStore.tslock.tsservice.ts 把 allocation plan / rebalance diff 转换成 keeper 执行状态机,支持 dry-run、vault position 注册、LI.FI route 执行、exit reconciliation、自动调仓、执行锁和本地 run 存储 Node 单元测试、场景测试;链上 adapter 已实现,默认测试使用 mock
oracleReporter packages/server/src/oracle/reporter.tsreportStore.tsservice.tsonchain.ts 汇总 position value、生成 target NAV 报告、计算 settlement realized rate、读取 LI.FI portfolio 作为估值输入、发布 oracle 报告、报告 hash 和本地报告存储 Node 单元测试、场景测试,默认使用 mock publisher / value source
marketOracle packages/server/src/oracle/market.tsonchain.ts 计算 active term 的 markRateBps、ETF collateral USD price,支持每小时 scheduler 和上链发布到 YieldSwapMarket Node 单元测试、API 测试
app packages/server/src/app.ts HTTP API 路由,不启动 listener,可直接用 app.request() 测试 Node API 测试
refresh/price/snapshot packages/server/src/services/refresh.tsprice.tssnapshotFile.ts 定时刷新 ETF snapshot、记录 price history、读写本地 SQLite 后续需要补独立数据库路径测试
lifi packages/server/src/services/lifi.ts 封装 LI.FI token metadata 和 quote API 当前通过 mock quote 间接测试;真实 API 集成测试待补

Contracts 测试

模块功能

LiYieldVault

职责:

  • 接收底层资产,例如 demo 里的 dUSDC
  • 根据 ERC-4626 规则铸造 vault share,也就是 liYIELD
  • 为 Yield Swap 提供可抵押资产。
  • 通过 TREASURY_ROLE 管理白名单 treasury/executor recipient。
  • 通过 KEEPER_ROLE 把 idle assets 调拨到白名单 recipient,并记录 divestment。
  • 通过 VALUATION_ORACLE_ROLE 上报 deployed positions 的估值变化。
  • 记录 managed position id、目标链、executor、外部 position id、principal、reported value 和状态。
  • 通过 FEE_MANAGER_ROLE 配置 management fee,并以增发 share 的方式计提。
  • 通过 EMERGENCY_ROLE 启动紧急模式,允许用户按 share 比例赎回 idle assets。
  • 通过 PAUSER_ROLE 暂停用户存取和 keeper 资金操作。

当前限制:

  • keeper execution service 已有可测试状态机,但真实链上交易 adapter 尚未接入。
  • LI.FI Composer 执行目前是可注入 adapter,测试使用 mock,尚未接生产私钥/RPC。
  • 还没有真实 LI.FI execution id / receipt token 校验。
  • oracle report service 已有可测试报告与发布流程,但外部 position value source、签名验证、quorum 或 dispute window 尚未接入。
  • 还没有 performance fee 和已部署仓位的 emergency unwind。

SettlementIndex

职责:

  • SETTLEMENT_ORACLE_ROLE 发布某个 termId 的最终 realized yield。
  • YieldSwapMarket 结算时读取该 realized yield。

当前限制:

  • 当前是 trusted oracle role 发布。
  • 没有 dispute window、oracle 多签、延迟生效、链下证明。

YieldSwapMarket

职责:

  • 用户把 liYIELD 存为 collateral。
  • 用户开 LongYieldShortYield 仓位。
  • 合约根据 term 的 AMM-like 曲线计算每笔仓位的 fixedRateBps
  • MARK_RATE_ORACLE_ROLE 更新 mark rate,合约计算 account health。
  • 不健康仓位可以被部分清算。
  • 到期后根据 SettlementIndex.realizedRateBps 逐仓结算 PnL。

关键状态:

  • collateralBalance[user]:用户总抵押余额。
  • lockedCollateral[user]:被仓位占用的抵押余额。
  • settlementLiquidity:协议作为对手盘支付盈利的一层资金。
  • insuranceFundBalance:保险金,吸收坏账或补充正 PnL。
  • termMarkets[termId]:每个 term 的 base rate、rate scalar、mark rate、long/short open interest。
  • positions[positionId]:每笔 Yield Swap 仓位。

已实现合约单元/行为测试

文件:packages/contracts/test/LiYield.t.sol

测试用例 测试目标 关键断言
test_openLongYieldAndSettleProfit 用户做多未来收益率,到期 realized rate 高于 fixed rate,用户盈利 仓位 settled;用户 collateral 增加;locked collateral 归零
test_openShortYieldAndSettleLoss 用户做空未来收益率,到期 realized rate 高于 fixed rate,用户亏损 用户 collateral 减少;locked collateral 归零
test_initialAdminHasAllProtocolRoles 初始 admin 拥有所有协议角色 SettlementIndex 和 YieldSwapMarket 的角色都授予给 admin
test_vaultKeeperDeploysAndRecordsDivestment vault keeper 部署资金并记录退出 idle/deployed/totalAssets accounting 正确
test_vaultKeeperCanReportTargetValueChange valuation oracle 上报 target value 改变 target deployed value 和 totalAssets 正确反映收益
test_vaultKeeperCannotDeployToUnapprovedTreasuryRecipient keeper 不能向未白名单 recipient 调拨资金 调用 deployToTarget revert TreasuryRecipientNotApproved
test_vaultKeeperAndValuationOracleRolesAreSeparated keeper 和 valuation oracle 权限分离 keeper 不能上报估值,oracle 不能记录 divestment
test_vaultPositionAccountingTracksExecutionMetadataAndTargetIndex managed position metadata 和 target index position 记录目标链、executor、外部 position id;target 可查询对应 position id
test_vaultPositionValueReportAndDivestmentUpdateAggregates position 级估值和退出同步聚合记账 reportPositionValue、部分退出、关闭仓位都会同步 target/total deployed accounting
test_vaultCanCloseZeroValuePositionAfterOracleMarksLoss 归零估值仓位可关闭 oracle 上报 position value 为 0 后,keeper 可用零金额关闭仓位
test_vaultAccruesManagementFeeAsShares management fee 计提 fee recipient 获得新增 shares;totalSupply 增加
test_vaultEmergencyRedeemIdleAllowsProRataIdleExitWhilePaused emergency idle redeem emergency mode 下用户按 share 比例赎回 idle assets,shares 被 burn
test_vaultRedeemCapacityIsLimitedByIdleAssets vault 资产已部署后限制赎回能力 maxWithdraw/maxRedeem 受 idle assets 限制
test_vaultRolesAreEnforced vault 角色权限校验 非 keeper/pauser 不能执行管理动作
test_vaultPauseBlocksUserAndKeeperFlows vault pause 行为 pause 后 deposit/withdraw/deploy 都失败,unpause 后恢复
test_settlementOracleRoleCanBeGrantedSeparately settlement oracle 角色可独立授予 被授权地址可以发布 settlement
test_nonSettlementOracleCannotPublishSettlement 非 settlement oracle 不能发布 settlement 未授权地址调用 setSettlement revert
test_marketRolesAreEnforcedIndependently market 的 term/mark/risk/treasury 角色独立校验 非授权地址调用管理函数全部 revert
test_grantedMarketRolesCanPerformOnlyTheirOwnActions 被授予的 market 角色只能执行对应动作 term manager 可配 term,mark oracle 可更新 mark,不能越权
test_cannotWithdrawLockedCollateral 用户不能提走被仓位锁住的 collateral withdrawCollateral revert InsufficientAvailableCollateral
test_longQuoteMovesHigherAfterLongOpenInterest long OI 上升后,继续开 long 的 fixed rate 变贵 第二次 long quote 大于第一次 long quote
test_openLongYieldRevertsWhenRateMovesPastLimit stale quote 不能绕过滑点保护 第二个用户用旧 quote 开 long 时 revert RateSlippage
test_getAccountHealthBecomesLiquidatable mark rate 不利变化后,账户健康度跌破维持保证金 isLiquidatable == true
test_liquidatePositionPartial 可清算仓位可以被部分清算 position notional 减半;liquidator 获得奖励;insurance fund 增加;剩余仓位未 settled
test_cannotLiquidateHealthyPosition 健康仓位不能被清算 revert PositionNotLiquidatable

已实现合约场景测试

文件:packages/contracts/test/LiYieldScenario.t.sol

场景编号 测试用例 场景说明 覆盖模块
C-SC-001 test_scenarioVaultDepositCollateralLongYieldAndSettlementWithdrawal 用户存入底层资产拿到 liYIELD,抵押 liYIELD,开 long yield,到期 realized yield 高于 fixed rate,结算盈利并提走 collateral Vault、Market、SettlementIndex
C-SC-002 test_scenarioShortYieldWinsWhenRealizedYieldFalls 用户做空未来收益率,到期 realized yield 低于 fixed rate,short 仓位盈利 Market、SettlementIndex
C-SC-003 test_scenarioPositionCannotSettleBeforeSettlementIndexPublishes 用户开仓后,SettlementIndex 尚未发布 realized yield,提前 settle 必须失败 Market、SettlementIndex
C-SC-004 test_scenarioLiquidationCannotCloseMoreThanConfiguredMaxPortion 仓位已可清算,但 liquidator 试图一次性关闭超过最大比例,必须失败 Market 风控
C-SC-005 test_scenarioAdverseMarkRateLiquidatesBeforeSettlementThenRemainingPositionSettles 用户开 long 后 mark rate 不利变化,仓位被部分清算,剩余仓位之后到期结算 Market 清算、SettlementIndex
C-SC-006 test_scenarioLpProvidesLiquidityTraderPaysFeesAndLpWithdrawsProfit LP 存入 liYIELD 提供流动性,交易员开仓并支付开/平仓 fee,LP 在 open interest 归零后提取更多 assets LP 池、Market fee、开平仓流程
C-SC-007 test_scenarioCollateralOpenReduceCloseAndWithdrawAll 用户抵押后开 long,oracle 更新 mark rate 后先减仓再平仓,最后提走全部可用 collateral 抵押、开仓、减仓、平仓、提取 collateral
C-SC-008 test_scenarioMaturedTermSettlesAfterOraclePublishesRealizedRate 带 maturity 的 term 到期后,SettlementIndex 发布 realized rate,用户结算并提走 collateral Term maturity、SettlementIndex、到期结算
C-SC-009 test_scenarioOracleUpdatesMoveHealthAndStaleOracleBlocksRiskActions oracle 更新 mark rate / collateral price 后账户进入可清算状态;oracle 过期时健康度、平仓、清算全部被阻止;刷新后恢复 Oracle 更新、健康度、OracleDataStale、清算前置条件
C-SC-010 test_scenarioMultipleLpsShareTraderLossProRata 两个 LP 按不同比例提供流动性,交易员亏损后 LP 池增厚,两个 LP 按 share 比例提取收益 多 LP、LP 份额、亏损回流 LP
C-SC-011 test_scenarioQuoteCurveMovesWithLongShortSkewAcrossTraders 第一位交易员开 long 后,fresh long / short 报价随 skew 改变;第二位交易员开对手 short 后,报价回到初始水平 多交易员、long-short skew、AMM 报价曲线
C-SC-012 test_scenarioLargeTraderProfitConsumesLpThenSettlementThenInsurance 大额盈利仓位到期后,正 PnL 依次消耗 LP pool、settlement liquidity 和 insurance fund,验证三层偿付顺序 大仓位结算、LP 池、协议准备金、保险金

Keeper 测试

模块功能

rebalancePlanner

职责:

  • 输入:两次 ETF snapshot 的权重差异 RebalanceDiff[]
  • 输出:keeper 可执行任务:
    • exit:上期有权重,本期权重为 0。
    • enter:上期权重为 0,本期有权重。
    • rebalance:两期都有权重,但权重发生变化。
    • null:权重无变化,不需要任务。

当前限制:

  • 只做任务分类,不直接执行交易。
  • 执行状态机在 keeperExecution 中处理。
  • 持久化和执行锁在 keeperExecution 中处理。
  • 还没有自动重试 worker。

allocationPlan

职责:

  • 输入 treasury 可投资金额和当前 ETF target weights。
  • weightBps 拆分资金。
  • 对每个目标池调用 quote fetcher,生成可执行路由信息。
  • 某个目标 quote 失败时,不让整个 plan 失败,而是在该 allocation item 上挂 error

当前限制:

  • 只生成计划,不执行 LI.FI/Earn 交易。
  • 当前测试里 quote fetcher 是 mock,不打真实 LI.FI。

keeperExecution

职责:

  • 输入 allocation plan。
  • 将每个 allocation item 转换成 keeper execution task。
  • 支持 enterexit 两类执行任务。
  • quote 缺失或金额为 0 的 allocation 自动标记为 skipped
  • 支持 dry-run,不调用 vault/LI.FI adapter。
  • 非 dry-run 时按任务执行:
    • 生成 externalPositionId
    • 调用 vault position registrar,生产环境对应 LiYieldVault.deployToTargetWithPosition
    • 调用 route executor,生产环境对应 LI.FI Composer / Earn 执行。
    • 记录 positionId、vault tx hash、route execution id、route tx hash。
    • exit task 会记录后续 reconciliation 所需的 position reductions。
    • 单个任务失败标记为 failed,不吞掉错误原因。

当前限制:

  • 已实现基于 viem 的 signer/RPC adapter,支持 vault 调用和 LI.FI transactionRequest 提交。
  • route status reconciliation 已实现,但默认仍依赖 LI.FI status API,不包含更复杂的 watcher / alerting。
  • 当前持久化默认使用本地 SQLite,尚未接生产级数据库拓扑和高可用。
  • 已有执行锁和手动 retry,但尚未实现自动 retry/backoff worker。

keeperOrchestrator / keeperReconcile

职责:

  • 从上次 snapshot 和当前 snapshot 生成 exit / enter 调仓动作。
  • 自动读取 vault idle assets,构建真正可执行的 enter allocation plan。
  • 读取 keeper executor 在 LI.FI Earn 中的 portfolio positions,推导 exit route 的 fromTokenamount
  • 读取 vault active managed positions,推导 exit 完成后应执行的 recordPositionDivestment 列表。
  • 轮询 LI.FI status;当 exit route 完成后,再把退出金额记回 vault deployed accounting。
  • 提供自动 scheduler,周期性执行 reconcile + rebalance。

当前限制:

  • 当前 exit route 仍然依赖 target toToken 和 keeper executor portfolio 的匹配;更复杂的多 receipt-token/多 wallet 归属仍需额外抽象。
  • scheduler 目前是单进程模式,分布式锁和多实例协调尚未接入。

oracleReporter

职责:

  • 输入 active managed positions 和 value source,生成 position valuation report。
  • targetId 汇总 target value。
  • 支持 position 级发布,生产环境对应 LiYieldVault.reportPositionValue
  • 支持 target 级发布,生产环境对应 LiYieldVault.reportTargetValue
  • 根据 startValue/endValue/netFlows 计算 realized rate bps。
  • 发布 settlement report,生产环境对应 SettlementIndex.setSettlement

当前限制:

  • 当前 value source 以 LI.FI Earn portfolio positions 为主,还没有做更强的多 source 校验、链下签名证明或 receipt-token 级核验。
  • 已接入基于 viem 的真实 oracle signer/RPC adapter,但默认测试仍使用 mock publisher。
  • settlement realized rate 默认将负收益 clamp 到 0,因为当前 SettlementIndex 使用 uint256 realizedRateBps
  • 当前报告存储默认使用本地 SQLite,尚未接生产级数据库拓扑和高可用。
  • 还没有 oracle quorum、签名报告、延迟生效和 dispute window。

marketOracle

职责:

  • 读取当前 ETF index snapshot,得到最新 ETF priceUsd
  • 读取当前 active trading term。
  • 根据入选 vault 的加权 recentApy7d 计算 markRateBps
  • 当没有可用入选 vault 时,回退到 active term 的 baseRateBps
  • 支持把 markRateBpscollateralPriceUsd 写到链上的 YieldSwapMarket
  • 支持独立 scheduler,默认按 1 小时周期执行。

当前限制:

  • 当前 markRateBps 来源是策略选中池子的加权 recentApy7d,还不是更复杂的 forward curve / term structure。
  • 当前 collateralPriceUsd 取自 ETF index snapshot,而不是独立价格预言机网络。
  • 当前没有单独的 market oracle 历史存储,主要依赖链上事件和 server 日志。

已实现 Keeper 单元测试

文件:packages/server/src/keeper/rebalancePlanner.test.ts

测试用例 测试目标 关键断言
classifyRebalanceDiff maps diff rows to keeper task kinds 验证 diff 到 keeper task kind 的分类逻辑 正确返回 exitenterrebalancenull
buildKeeperRebalancePlan groups exits, entries, and weight changes 验证从一组 diff 生成分组任务计划 exits/entries/rebalances 分组正确

文件:packages/server/src/keeper/execution.test.ts

测试用例 测试目标 关键断言
buildKeeperExecutionTasks creates executable tasks and skips failed quotes allocation item 到 execution task 的转换 quote 成功的任务为 pending,quote 失败的任务为 skipped
executeKeeperAllocationPlan dry-run does not call execution adapters dry-run 行为 不调用 vault/route adapter,任务状态为 dry_run
executeKeeperAllocationPlan registers vault position and executes route 成功执行路径 调用 vault registrar 和 route executor,记录 position id 与 execution id
executeKeeperAllocationPlan records failed adapter execution without stopping other task accounting 失败执行路径 adapter 抛错后任务为 failed,错误信息保留
retryFailedKeeperExecutionRun retries only failed tasks 失败任务重试 只重试 failed task,成功后 attempts 增加

文件:packages/server/src/keeper/orchestrator.test.ts

测试用例 测试目标 关键断言
buildKeeperExitTasks builds exit routes and divestment reductions from snapshot diff 基于 snapshot diff 构建 exit task 生成 exit route,退出金额和 recordPositionDivestment 列表正确
runKeeperCycle builds enter allocation from vault idle assets and executes mocked tasks 自动 keeper cycle 自动读取 investable assets,生成 enter plan,并执行 enter run

文件:packages/server/src/keeper/reconcile.test.ts

测试用例 测试目标 关键断言
reconcileKeeperExecutionRun finalizes completed exit routes exit route 完成后的对账 LI.FI status 为 DONE 时,keeper 调用 exit finalizer 并把 reconciliation 标记为 completed
reconcileKeeperExecutionRun marks failed LI.FI routes as failed route 失败的对账 LI.FI status 为 FAILED 时,reconciliation 标记为 failed

文件:packages/server/src/keeper/runStore.test.ts

测试用例 测试目标 关键断言
keeper run store appends, filters, and returns newest runs first keeper run 本地存储 run 可追加、按 ETF 过滤、按时间倒序读取
keeper run store upserts by run id keeper run 状态更新 相同 runId 可被覆盖更新,用于 reconciliation 回写

文件:packages/server/src/oracle/reporter.test.ts

测试用例 测试目标 关键断言
buildValuationReport aggregates position values by target position value 汇总为 target value 同一 target 的 position value 正确相加
collectValuationReport fetches only active positions from a value source 从外部 value source 采集估值 closed position 被跳过
publishPositionValuations and publishTargetValuations call the configured publisher oracle publisher 调用 position/target publisher 收到正确 value
computeRealizedRateBps subtracts net flows and clamps losses by default settlement realized rate 计算 净流入被扣除,负收益默认归 0
publishSettlementReport publishes realized rate through configured publisher settlement 发布 publisher 收到计算后的 realized rate

文件:packages/server/src/oracle/reportStore.test.ts

测试用例 测试目标 关键断言
oracle report store persists valuation reports with hashes oracle valuation 本地存储 report hash 正确生成并持久化
oracle report store filters settlement reports by term oracle settlement 本地存储 可按 termId 查询历史报告

文件:packages/server/src/oracle/service.test.ts

测试用例 测试目标 关键断言
buildLifiPortfolioValuationReport maps LI.FI portfolio values onto managed positions LI.FI portfolio 到 vault positions 的估值映射 按 target token + chain 匹配,并按 position basis 比例分配 target value
runOracleSettlementCycle derives settlement from stored valuation reports 从历史 valuation report 推导 settlement 读取起止 report,总和 target NAV,计算 realized rate 并写入 settlement store

已实现 Keeper 场景测试

文件:packages/server/src/scenarios/weeklyRebalanceScenario.test.ts

场景编号 测试用例 场景说明 覆盖模块
K-SC-001 scenario: weekly strategy rotation creates one exit and one entry keeper task 上周 Top 3 是 ethena/morpho/renzo,本周 Top 3 是 morpho/renzo/etherfi;keeper 应生成一个 exit、一个 enter、一个 rebalance diffSnapshotsrebalancePlanner
K-SC-002 scenario: first keeper rebalance treats every selected target as an entry 系统首次运行,没有 previous snapshot;当前所有 selected targets 都应被视为 entry diffSnapshotsrebalancePlanner
K-SC-003 scenario: unchanged weekly top 3 creates no keeper tasks 上周和本周成分、权重都没变;keeper 不应生成任何任务 diffSnapshotsrebalancePlanner
K-SC-004 scenario: keeper allocation plan quotes every selected target and preserves per-target amounts treasury 有 1000 单位资金,三个目标权重约等于 1/3;allocation plan 应拆成 333/333/334 并逐个 quote allocationPlan、mock quote fetcher
K-SC-005 scenario: keeper execution creates positions, oracle reports values, settlement rate is published keeper 执行 allocation 创建两个 position;oracle 采集 position value 并发布;settlement oracle 计算并发布 realized yield keeperExecutionoracleReporter、mock publisher

文件:packages/server/src/scenarios/keeperOracleOnchainScenario.test.ts

场景编号 测试用例 场景说明 覆盖模块
K-SC-006 scenario: contracts, keeper, and oracle link together through on-chain adapters 本地启动 Anvil,部署真实合约;keeper 通过 LiYieldVault.deployToTargetWithPosition 注册并部署仓位;oracle 基于 LI.FI portfolio mock 生成 valuation report 并调用链上 publisher 更新 position value;最后根据两份 valuation report 生成 settlement 并写入 SettlementIndex Deploy.s.solkeeper/onchain.tskeeper/execution.tsoracle/service.tsoracle/onchain.tsLiYieldVaultSettlementIndex
K-SC-007 scenario: oracle loss path marks position down and settlement clamps negative yield to zero 本地启动 Anvil,keeper 建仓后 oracle 将 position value 从 1000 下调到 920;结算层验证负收益会被 clamp 为 0,并写入链上 SettlementIndex keeper/onchain.tsoracle/service.tsoracle/onchain.tsLiYieldVaultSettlementIndex
K-SC-008 scenario: target-level oracle publish updates aggregate target value while position accounting stays unchanged keeper 建仓后 oracle 使用 target 级发布模式,将 target deployed value 从 1000 更新到 1040;position 级 reported value 保持原值,用于验证 target publish 路径 oracle/service.tsoracle/onchain.tsLiYieldVault
K-SC-009 scenario: multi-position oracle valuation distributes target value proportionally across on-chain positions keeper 在同一 target 下创建两笔 on-chain positions;oracle 根据单个 target portfolio value 按 principal 比例分配到两笔 position,并逐笔上链发布 keeper/execution.tsoracle/service.tsoracle/reporter.tsoracle/onchain.tsLiYieldVault

Server 测试

模块功能

app

职责:

  • 提供 HTTP API。
  • 把 API 路由和 server listener 分离,便于测试。
  • 当前对外接口包括:
    • /health
    • /api/etfs
    • /api/index
    • /api/earn/targets
    • /api/earn/vaults
    • /api/earn/portfolio/:address
    • /api/earn/snapshots
    • /api/etfs/:etfId/price
    • /api/etfs/:etfId/price/history
    • /api/keeper/allocation-plan
    • /api/keeper/execute-allocation-plan
    • /api/keeper/runs
    • /api/keeper/retry-run
    • /api/oracle/valuation-report
    • /api/oracle/auto-valuation
    • /api/oracle/settlement-report
    • /api/oracle/settlement-from-valuations
    • /api/oracle/valuation-reports
    • /api/oracle/settlement-reports
    • /api/keeper/rebalance
    • /api/admin/refresh
    • /api/admin/earn-snapshot
    • /api/admin/snapshot

当前限制:

  • admin auth 只是本地可用的 bearer token 保护;未设置 token 时默认开放。
  • API 测试覆盖不依赖外网的基础接口、keeper dry-run / rebalance / reconcile 接口、oracle valuation / auto-valuation / settlement 接口,以及 Earn snapshot 手动落盘接口。

strategyEngine

职责:

  • 按过去 7 天 APY 排序。
  • strategy 模式下选 Top N。
  • equal 模式下返回所有候选 targets。
  • 计算等权 weightBps
  • 生成 strategy summary 和 next rebalance 时间。

indexEngine

职责:

  • 读取 ETF 配置和 strategy 配置。
  • 解析候选 target。
  • 选择目标 target 并生成 ETF snapshot。
  • 拉取 LI.FI token metadata,补充 symbol、price 等信息。

当前限制:

  • Earn targets 的候选范围仍由本地配置控制。
  • APY、TVL、LP token price 和 Composer capability 会优先从 LI.FI Earn Data API 拉取,失败时 fallback 到静态配置。
  • 真实 pool discovery endpoint 已封装为 /api/earn/vaults,但还没有自动把所有 LI.FI vault 纳入策略候选集。
  • priceUsd 仍不是链上 vault NAV 驱动。

earn

职责:

  • 调用 LI.FI Earn Data API 的 vault detail / vault list / portfolio positions。
  • 将 Earn 返回结构归一化为服务内部字段:apyTotalapy7dtvlUsdlpTokenPriceUsdisTransactionalisRedeemable
  • 将小数 APY 转换为百分比 APY,例如 0.0825 -> 8.25
  • indexEngine 提供 target runtime data,驱动 strategy 排名和 dashboard 展示。
  • API 失败时返回空数据或静态配置 fallback,保证本地 demo 不因为外部网络失败而不可用。

当前限制:

  • 当前没有把 LI.FI 全量 vault discovery 自动写回本地 strategy candidate list。
  • portfolio endpoint 已封装,并已接入 oracle 自动估值流程;更强的链上校验和多 source 验证仍待补。
  • 真实 LI.FI Earn API 的外部集成测试不进默认 CI,默认测试使用 mock fetch。

earnSnapshot / earnSnapshotStore

职责:

  • 基于 indexEngine 采集每个 ETF 的候选 Earn targets。
  • 记录每个 target 的 APY、7d APY、TVL、LP token price、是否可交易、是否被策略选中、weight、rank 和数据来源。
  • 定时将 Earn 数据快照 append 到本地 SQLite store。
  • 支持按 etfIdtargetIdlimit 查询历史快照。
  • 支持每个 ETF 保留最近 N 条快照,避免本地文件无限增长。

当前限制:

  • 当前 store 是本地 SQLite,不是生产级数据库集群。
  • 定时任务采集的是本地 strategy candidate list,不是 LI.FI 全量 vault discovery。
  • 没有补历史 backfill、压缩归档、去重或数据质量告警。

price/refresh/snapshotFile

职责:

  • refresh 定时生成 snapshot 并追加 price history。
  • price 读取实时 price 或历史 price。
  • snapshotFile 读写 SQLite 中的 latest snapshot 和 price history。

当前限制:

  • price 当前仍然是 navUsd / totalShares
  • 文件系统测试还没有隔离临时目录。

已实现 Server API 测试

文件:packages/server/src/app.test.ts

测试用例 测试目标 关键断言
GET /health returns service status without starting a listener 不启动实际端口,直接测试 Hono app 返回 ok: trueservice: liyield-server
GET /api/etfs returns configured ETFs without external network calls 测试 ETF 注册表接口,不依赖外部网络 返回 liyield-coreweekly-top3-apy7d
POST /api/keeper/execute-allocation-plan returns dry-run execution plan keeper dry-run API 返回 execution summary,任务进入 dry_run
POST /api/oracle/valuation-report aggregates and publishes position values oracle valuation API position value 汇总为 target value,并调用 mock publisher
POST /api/oracle/auto-valuation runs the LI.FI-backed oracle valuation cycle oracle 自动估值 API 调用 LI.FI portfolio-backed valuation runner,并返回 owner / report / published / stored
POST /api/oracle/settlement-report computes and publishes realized yield oracle settlement API 计算 realized rate 并调用 mock publisher
POST /api/oracle/settlement-from-valuations derives settlement from stored valuation history oracle 自动结算 API 根据 valuation report id 生成 settlement,并返回 publish 结果
POST /api/admin/earn-snapshot persists Earn data snapshots and exposes history Earn 数据手动落盘 API 手动采集后写入 store,GET /api/earn/snapshots 可读出历史
POST /api/keeper/execute-rebalance persists exit and enter runs from the keeper cycle 自动 keeper cycle API keeper cycle 返回的 exit / enter run 会被持久化
POST /api/keeper/reconcile-runs returns updated keeper runs keeper reconciliation API 返回已完成对账的 updated runs

已实现 Server Service 测试

文件:packages/server/src/services/strategyEngine.test.ts

测试用例 测试目标 关键断言
rankTargetsByApy7d sorts targets by trailing 7d yield descending APY 排序正确 返回顺序为高 APY 到低 APY
selectTargets selects top N for strategy mode and all ranked targets for equal mode 策略模式和 equal 模式行为不同 strategy 只取 Top N;equal 返回所有候选
computeEqualWeights always sums to 10000 bps 等权权重精度 任意 count 权重总和为 10000
buildStrategySummary includes cadence, selection metadata, and next rebalance strategy summary 正确 selected/candidate 数量和 next rebalance 时间正确

文件:packages/server/src/services/allocationPlan.test.ts

测试用例 测试目标 关键断言
splitWeightedAmounts allocates rounding remainder to the last target 整数拆分资金时处理尾差 100 按 3334/3333/3333 拆成 33/33/34
buildAllocationPlan splits treasury amount and calls quote fetcher per target allocation plan 正确调用 quote fetcher 每个 target 有一个 quote call;金额按权重拆分
buildAllocationPlan keeps failed LI.FI quote as item-level error 单个 quote 失败不影响整体计划 失败 target 的 quote: null,并保留 error message

文件:packages/server/src/services/earn.test.ts

测试用例 测试目标 关键断言
fetchEarnVault normalizes LI.FI Earn vault analytics into percent APY and USD TVL vault detail 数据归一化 APY 小数转百分比,TVL、protocol、capability 正确
fetchEarnVaults sends filtering query params and normalizes list responses vault list 查询参数和返回解析 chainId/asset/protocol/sortBy/limit 参数正确,列表归一化
fetchEarnPortfolioPositions normalizes portfolio positions portfolio positions 解析 position 的链、协议、资产、余额字段正确
mergeEarnVaultData falls back to static target data when Earn data is unavailable Earn API fallback API 无数据时继续使用静态 APY 和 composerReady

文件:packages/server/src/services/earnSnapshot.test.ts

测试用例 测试目标 关键断言
buildEarnDataSnapshot records selected targets and all candidate Earn vault rows 单次 Earn 数据采集 snapshot 记录 selected target ids,并包含全部候选 vault rows
recordEarnDataSnapshot persists the collected Earn snapshot 采集并落盘 store 中能读到刚采集的 Earn snapshot

文件:packages/server/src/services/earnSnapshotStore.test.ts

测试用例 测试目标 关键断言
earn snapshot store appends, filters, and trims per ETF Earn snapshot 本地持久化 可 append、按 ETF / target 过滤、按时间倒序读取,并按每个 ETF 上限裁剪

文件:packages/server/src/services/indexEngine.test.ts

测试用例 测试目标 关键断言
buildIndexSnapshot ranks targets using LI.FI Earn APY data when available index snapshot 使用 LI.FI Earn APY 驱动策略排名 mock Earn APY 最高的三个 vault 被选为 Top 3,且 target 标记为 earnDataSource: lifi-earn

场景测试用例库

下面是建议长期维护的场景测试库。状态说明:

  • implemented:已经有自动化测试。
  • next:下一批最值得实现。
  • planned:后续生产化阶段应补。

ETF / Strategy / Keeper 场景

ID 状态 场景 期望结果
ETF-SC-001 implemented 首次启动,没有 previous snapshot 当前 Top 3 全部生成 enter 任务
ETF-SC-002 implemented 周调仓后一个旧池掉出,一个新池进入 生成一个 exit、一个 enter,保留池如果权重变动则生成 rebalance
ETF-SC-003 implemented 周调仓后 Top 3 完全不变,权重也不变 keeper 不生成任务
ETF-SC-004 implemented allocation plan 对三个目标等权拆分 1000 金额为 333/333/334,每个目标都有 quote
ETF-SC-005 implemented-server LI.FI Earn vault detail 返回 APY/TVL/capability 数据归一化后可用于 snapshot 和 strategy ranking
ETF-SC-006 implemented-server LI.FI Earn vault list 按 chain/asset/protocol/sortBy/limit 查询 查询参数正确,返回 vault 列表被归一化
ETF-SC-007 implemented-server LI.FI Earn portfolio positions 返回用户仓位 position balance 和资产字段被归一化
ETF-SC-008 implemented-server LI.FI Earn 数据不可用 target 使用静态 APY fallback,demo 仍可运行
ETF-SC-008A implemented-server 单次采集 Earn 数据快照 记录 selected target ids,并包含全部候选 vault rows
ETF-SC-008B implemented-server Earn snapshot 持久化后查询历史 可按 ETF / target 过滤,按时间倒序返回,并按上限裁剪
ETF-SC-009 next Top 3 完全轮换,例如 A/B/C 变成 D/E/F 生成 3 个 exit 和 3 个 enter
ETF-SC-010 next 某个目标 quote 失败,其余目标 quote 成功 plan 返回成功,失败项带 error,keeper 可选择跳过或重试
ETF-SC-011 next 可投资金额为 0 不应生成有效 quote 任务,或明确返回 validation error
ETF-SC-012 next 候选池数量少于 Top N 策略只选择实际存在的池,权重仍总和 10000
ETF-SC-013 planned 策略切换,例如 Top 3 APY 切换为风险加权策略 snapshot 中 strategy id 和 target 选择符合新策略
ETF-SC-014 implemented-server keeper 执行过程中某条 LI.FI 交易失败 任务进入 failed,错误原因被记录
ETF-SC-015 implemented-server keeper 重试成功 任务从 failed/retrying 变为 succeeded,并记录最终 tx hash
ETF-SC-016 implemented-server rebalance 正在执行时又触发新的 rebalance 应有执行锁,避免重复部署资金
ETF-SC-017 implemented-server 周调仓产生 exit task keeper 根据 portfolio balance 和 managed positions 生成 exit route 与 divestment reductions
ETF-SC-018 implemented-server exit route 完成后做 reconciliation LI.FI status 为 DONE 后,vault 执行 recordPositionDivestment

NAV / Price 场景

ID 状态 场景 期望结果
NAV-SC-001 implemented-contract vault 有 idle cash,没有 deployed positions totalAssets 等于 idle assets
NAV-SC-002 implemented-contract 一个 deployed target 上报收益 totalAssetsreportTargetValue 上升
NAV-SC-003 implemented-contract 某个 managed position 上报收益、部分退出、最终关闭 position、target、total deployed accounting 同步变化
NAV-SC-004 implemented-contract 某个 managed position 被 oracle 标记为 0 keeper 可关闭归零仓位,deployed accounting 归零
NAV-SC-005 implemented-contract management fee 经过时间后计提 fee recipient shares 增加,totalSupply 增加
NAV-SC-006 implemented-contract emergency mode 下用户赎回 idle assets 用户按 share 比例拿回 idle assets,常规存取保持暂停
NAV-SC-007 implemented-server oracle 采集多个 position value 并汇总 target value valuation report 输出 position 明细和 target 汇总
NAV-SC-008 planned keeper 调仓产生执行滑点 NAV 扣除滑点损耗
NAV-SC-009 planned price refresh 多次执行 history 按时间追加,limit 参数只返回最近 N 条
NAV-SC-010 planned totalShares 为 0 price 返回 0 或明确错误,不能除零
NAV-SC-011 next 某个 Earn position 亏损或可赎回价值下降 NAV 下降,price history 反映价格下跌

Yield Swap 场景

ID 状态 场景 期望结果
YS-SC-001 implemented 用户 deposit vault -> 抵押 liYIELD -> long yield -> 到期盈利 -> withdraw 用户最终 liYIELD 增加,locked collateral 归零
YS-SC-002 implemented short yield,realized yield 低于 fixed rate short 用户盈利
YS-SC-003 implemented settlement 尚未发布就尝试结算 revert SettlementNotAvailable
YS-SC-004 implemented 不利 mark rate 触发部分清算,剩余仓位再结算 position notional 减少;liquidator 收奖励;剩余仓位可 settle
YS-SC-005 implemented 清算关闭比例超过最大允许比例 revert InvalidLiquidationSize
YS-SC-006 implemented long OI 增加后继续开 long fixed rate 抬高
YS-SC-007 implemented 使用 stale quote 开仓 revert RateSlippage
YS-SC-008 implemented 健康仓位被清算 revert PositionNotLiquidatable
YS-SC-009 next 同一用户同一 term 下有多笔 long/short 仓位 health 计算聚合全部未结算仓位
YS-SC-010 next settlement liquidity 不足但 insurance 足够 用户盈利优先从 settlement liquidity 支付,不足部分由 insurance 支付
YS-SC-011 next settlement liquidity 和 insurance 都不足 settle 应 revert InsufficientSettlementLiquidity
YS-SC-012 next 仓位被完全清算 position settled == true,OI 完全扣减
YS-SC-013 planned mark rate 未发布就读取 health revert MarkRateNotAvailable
YS-SC-014 planned term 未配置就 quote/open revert TermNotConfigured
YS-SC-015 planned RISK_MANAGER_ROLE 更新 risk parameters 新参数影响后续清算判断
YS-SC-016 planned 多用户同时开 long 和 short AMM quote 围绕 open interest skew 动态变化

Server API 场景

ID 状态 场景 期望结果
API-SC-001 implemented 不启动 listener,直接请求 /health 返回服务状态
API-SC-002 implemented 请求 /api/etfs 返回已配置 ETF
API-SC-003 implemented 请求 /api/keeper/execute-allocation-plan dry-run 返回 allocation plan 和 execution dry-run summary
API-SC-004 implemented 请求 /api/oracle/valuation-report 返回 position 明细和 target 汇总
API-SC-005 implemented 请求 /api/oracle/settlement-report 返回 realized rate,并可通过 publisher 发布
API-SC-005A implemented 请求 /api/oracle/auto-valuation 返回基于 LI.FI portfolio 的 valuation report,并可选发布
API-SC-005B implemented 请求 /api/admin/earn-snapshot 后再请求 /api/earn/snapshots Earn 数据快照被持久化并可读取
API-SC-005C implemented 请求 /api/keeper/execute-rebalance 返回 keeper cycle,并持久化 exit / enter runs
API-SC-005D implemented 请求 /api/keeper/reconcile-runs 返回对账后更新的 keeper runs
API-SC-005E implemented 请求 /api/oracle/settlement-from-valuations 基于历史 valuation report 生成 settlement 并返回 publish 结果
API-SC-006 next 请求 /api/index?mode=strategy 返回 Top 3 strategy snapshot
API-SC-007 next 请求 /api/index?mode=equal 返回所有候选 targets 且等权
API-SC-008 next 请求不存在的 ETF id 返回明确错误
API-SC-009 next admin token 设置后,未带 token 请求 keeper/admin 接口 返回 401
API-SC-010 next admin token 设置后,带正确 token 请求 keeper/admin 接口 返回 200
API-SC-011 planned /api/admin/refresh 写入 snapshot 和 price history 文件中出现对应记录
API-SC-012 planned /api/etfs/:id/price/history?limit=3 只返回最近 3 条

当前覆盖统计

Area Test Count 状态
Contracts unit / behavior 25 Passing
Contracts scenarios 5 Passing
Keeper unit 13 Passing
Keeper / oracle scenarios 9 Passing
Oracle services 9 Passing
Server API 10 Passing
Server services 15 Passing
Total backend tests 86 Passing

当前缺口

  • LI.FI Earn Data API 已接入数据层并有 mock 测试;Earn snapshot 已支持定时落盘到本地 SQLite;真实外部 API 集成测试未进入默认 CI。
  • Keeper 已实现基于 viem 的 signer/RPC adapter、vault position 注册、LI.FI route 提交、exit reconciliation、手动 retry、自动 scheduler 和执行锁,但生产数据库、分布式锁、自动重试 worker 仍未接入。
  • Earn snapshot store 当前默认使用本地 SQLite,还没有接生产数据库、去重、backfill、归档和数据质量告警。
  • 合约层已有 vault deployed accounting 测试,但 server price 还没有接链上 NAV,目前仍基于配置里的 navUsd / totalShares
  • Oracle 已有 LI.FI portfolio-backed valuation source、基于 viem 的链上 publisher、valuation/settlement report、report hash、本地 SQLite 报告存储和自动估值测试,但还没有签名报告、quorum、dispute window 和生产数据库。
  • Vault 已有 position accounting、management fee、emergency idle redeem 测试,但还没有真实 LI.FI execution id、receipt token 和跨链 unwind 集成测试。
  • Price history 文件系统测试尚未隔离临时目录。
  • Yield Swap 还没有 fuzz / invariant 测试。
  • SettlementIndex 还没有 oracle 争议、延迟窗口、多签发布等生产级测试。
  • 前端 UI 测试不在当前范围内。