审计报告BNB Chainv2.0 Final整体评级 · 低风险

NovaStake 质押金库协议安全审计报告

NovaStake 是一套部署于 BNB Chain 的质押 + 收益金库协议(Vault / StakingRewards / 可升级代理)。 本次审计共发现 10 个问题:2 个严重、3 个高危、5 个中危。所有问题均已在复审中由客户修复并验证通过, 残余资金风险敞口为 $0,复审结论为「通过,可上线」。本编号 UMB-2026-0142 可在 umibit 报告登记处独立核验。

报告编号
UMB-2026-0142
客户
NovaStake(示例项目 · DeFi 质押 / 收益金库)
区块链
BNB Chain
语言
Solidity 0.8.24
资金敞口
$0(残余敞口)
报告版本
v2.0 Final
报告日期
2026-06-26
整体评级
低风险

受审合约

本次审计覆盖的合约 / 文件及其可核验身份(commit 与链上地址)。

名称类型commit地址
VaultUUPS.sol可升级金库(UUPS 代理)7f3c9a2bd60x4D7c1B2a3E5f60718293A4b5C6d7E8f901234ABC
StakingRewards.sol奖励分发7f3c9a2bd60xB1a2C3d4E5F60718293a4B5c6D7e8F90DEAD0001
RewardToken.sol奖励代币(BEP-20)7f3c9a2bd60x9F8e7D6c5B4a39281706F5e4D3c2B1A0FEED0002
VaultTimelock.sol治理时间锁7f3c9a2bd6

漏洞汇总

严重 2 3 5
ID标题严重状态类别
NS-01claimRewards() 提现重入可重复领取奖励严重Resolved 已修复重入
NS-02UUPS 代理 initialize() 未锁定,可被任意账户抢先初始化夺取管理员严重Resolved 已修复初始化
NS-03奖励结算顺序错误,新存款可稀释已存款者的应计奖励Resolved 已修复业务逻辑
NS-04emergencyWithdraw() 绕过会计,破坏 totalStaked 全局不变量Resolved 已修复业务逻辑
NS-05owner 可无时间锁修改奖励速率并提走全部奖励储备Resolved 已修复中心化权限
NS-06份额汇率读取现货储备,可被闪电贷操纵Resolved 已修复预言机 / 闪电贷
NS-07deposit/withdraw 缺少最小输出/滑点保护Resolved 已修复业务逻辑
NS-08奖励整数取整向下舍入,小额质押者长期被吞奖励Resolved 已修复整数运算
NS-09setRewardRate() 缺上限校验,可设置极端值Resolved 已修复访问控制
NS-10ERC20 转账未检查返回值,非标准代币可静默失败Resolved 已修复外部调用

执行摘要

NovaStake 是一套部署于 BNB Chain 的质押 / 收益金库协议,由一个 UUPS 可升级金库VaultUUPS)、 一个 奖励分发合约StakingRewards)、奖励代币与治理时间锁组成。用户存入资产换取份额,协议按 rewardRate 持续向质押者分发奖励代币。

本次审计采用 AI 全覆盖 + 专家研判 的混合模式:自动化引擎对全量代码做高覆盖率初筛与不变量枚举, 安全工程师对所有产出逐条人工验证(剔除误报、确认可利用性、编写 PoC),并独立完成业务逻辑与经济模型分析。

共发现 10 个问题:2 严重 / 3 高危 / 5 中危。两个严重问题——领奖重入代理初始化可被抢占 ——任一被利用都可直接造成金库资金或控制权丢失。客户在复审窗口内 修复了全部 10 项,umibit 复审逐条验证 通过,残余资金风险敞口为 $0,最终评级 低风险,结论为 「通过,可上线」

本报告是该时间点、该指定 commit(7f3c9a2bd6e1)范围内的安全状况快照,不构成对项目绝对安全的保证, 亦不构成任何投资建议。审计后对代码的任何改动均不在覆盖范围内。

如何核验本报告: 本报告编号 UMB-2026-0142。任何人可在 umibit 报告登记处 的「报告编号核验」中输入该编号,确认其真实有效、并比对项目、链、版本与 签发日期;同时请将链上部署字节码与上方所列受审 commit 比对,确认线上运行的正是被审计的同一版本代码。

NS-01 · claimRewards() 提现重入可重复领取奖励

严重等级: Critical 状态: Resolved 类别: 重入 位置: StakingRewards.sol:212-238 · claimRewards()

claimRewards() 先向用户转出奖励代币、之后 才把该用户的 rewards[msg.sender] 清零。若奖励代币带有 转账回调(如 ERC-777 / 带 hook 的代币),攻击者可在回调里 重入 claimRewards(),在余额清零前反复领取, 直至掏空奖励储备。违反了「检查—生效—交互(CEI)」顺序。

概念验证(PoC):

// 攻击合约在 tokensReceived 回调里重入
function tokensReceived(...) external {
    if (staking.earned(address(this)) > 0) {
        staking.claimRewards(); // 余额尚未清零,可重复领取
    }
}

修复建议: 严格遵循 CEI——先清零 rewards[msg.sender] 再转账;并对 claimRewards()withdraw()emergencyWithdraw()nonReentrant

修复情况: 客户已调整为先清零再转账,并对所有外部可调用的提现路径加 nonReentrant,复审验证通过。

NS-02 · UUPS 代理 initialize() 未锁定,可被抢先初始化夺取管理员

严重等级: Critical 状态: Resolved 类别: 初始化 位置: VaultUUPS.sol:64-82 · initialize()

实现合约未在构造函数中调用 _disableInitializers(),且 initialize() 仅由 initializer 修饰。若部署脚本 未在同一笔交易内完成初始化,任何人可抢先调用 initialize() 把自己设为 owner / upgrader,进而通过 UUPS 升级把代理指向恶意实现、接管整个金库。

概念验证(PoC):

// 部署后、官方初始化前的竞态窗口
vault.initialize(attacker);                 // attacker 成为 owner & upgrader
vault.upgradeToAndCall(malicious, "");       // 把代理指向恶意实现,接管资金

修复建议: 在实现合约构造函数中调用 _disableInitializers();部署与初始化放在同一笔交易/同一脚本原子完成; 将 upgrader 角色交由时间锁 + 多签治理。

修复情况: 客户已加入 _disableInitializers(),并改为通过工厂在单笔交易内原子初始化、upgrader 交由 VaultTimelock 管理,复审验证通过。

NS-03 · 奖励结算顺序错误,新存款稀释已存款者

严重等级: High 状态: Resolved 类别: 业务逻辑 位置: StakingRewards.sol:150-176 · _updateReward()

deposit()更新全局 rewardPerTokenStored 之前 就增加了 totalStaked,导致自上次结算以来累积的奖励 被按 包含新存款 的更大基数重新分摊。已存款者本应独享的应计奖励被新进入者稀释,可被抢跑套利。

修复建议: 用标准 updateReward(account) 修饰器,在任何改变余额的操作 之前 先结算 rewardPerToken 与该账户的 earned,再变更 totalStaked / balances

修复情况: 客户改用 updateReward 修饰器统一前置结算,并补充了多用户时序的不变量测试,复审验证通过。

NS-04 · emergencyWithdraw() 绕过会计,破坏 totalStaked 不变量

严重等级: High 状态: Resolved 类别: 业务逻辑 位置: VaultUUPS.sol:201-219 · emergencyWithdraw()

emergencyWithdraw() 直接转出用户本金,但 未递减 totalStaked,使「totalStaked == Σ balances」这一 核心不变量被打破。后续奖励分摊与汇率计算据此出错,长期可造成会计漂移与坏账。

修复建议: 紧急提现也必须走完整会计:递减 balancestotalStaked、结算并清零奖励;并加 nonReentrant

修复情况: 客户补齐了会计更新,并加入「totalStaked 等于各账户余额之和」的链上不变量断言测试,复审通过。

NS-05 · owner 可无时间锁修改奖励速率并提走全部奖励储备

严重等级: High 状态: Resolved 类别: 中心化权限 位置: StakingRewards.sol:288 · setRewardRate() / recoverRewards()

setRewardRate()recoverRewards() 均由 onlyOwner 即时生效、无时间锁。owner 私钥一旦被控或团队作恶, 可瞬间把奖励速率改至极端值、或通过 recoverRewards() 把奖励储备一次性提走,损害质押者预期(典型中心化 风险向量)。

修复建议: 将这两类敏感操作交由 多签 + 时间锁 治理,给社区留出退出窗口;recoverRewards() 应限定为 仅可回收「误转入的非奖励代币」,不可触及计入分发的奖励储备。

修复情况: 客户把 owner 迁移到 VaultTimelock(48h 延时)+ 多签,并将 recoverRewards() 限制为仅非 奖励代币,复审验证通过。

NS-06 · 份额汇率读取现货储备,可被闪电贷操纵

严重等级: Medium 状态: Resolved 类别: 预言机 / 闪电贷 位置: VaultUUPS.sol:142 · convertToShares()

convertToShares() 以合约 当前现货余额 计算份额单价,攻击者可用闪电贷在单笔交易内拉高/压低余额,操纵 铸造或赎回比例套利。

修复建议: 采用累计型会计(基于内部记账的 totalAssets,而非可被瞬时操纵的现货 balanceOf),并对外部 价格引用使用 TWAP / 预言机。

修复情况: 客户改用内部记账的 totalManagedAssets 计算份额,复审验证通过。

NS-07 · deposit/withdraw 缺少最小输出 / 滑点保护

严重等级: Medium 状态: Resolved 类别: 业务逻辑 位置: VaultUUPS.sol:118-160

存取款未提供 minSharesOut / minAssetsOut 参数,用户在汇率波动或被夹击时可能得到远低于预期的份额或资产。

修复建议: 为存取款加入用户指定的最小输出参数,不满足即 revert。

修复情况: 客户已加入 minSharesOut / minAssetsOut 滑点保护,复审验证通过。

NS-08 · 奖励整数取整向下舍入,小额质押者长期被吞奖励

严重等级: Medium 状态: Resolved 类别: 整数运算 位置: StakingRewards.sol:168 · earned()

earned() 在按 rewardPerToken 计算时直接整除向下取整,且未保留余数尾差。小额质押者在长期、多次结算下可 持续损失本应得到的奖励尾差。

修复建议: 提高 rewardPerToken 的定点精度(如 1e18 缩放),并保留/累计未分配尾差。

修复情况: 客户将累加器精度提升至 1e18 并累计尾差,复审验证通过。

NS-09 · setRewardRate() 缺上限校验,可设置极端值

严重等级: Medium 状态: Resolved 类别: 访问控制 位置: StakingRewards.sol:288 · setRewardRate()

setRewardRate() 未对新速率设上限,配置失误或恶意操作可设至极端值,瞬间耗尽奖励储备或造成溢出风险。

修复建议: 引入 MAX_REWARD_RATE 硬上限并在设置时校验;变更通过时间锁生效。

修复情况: 客户已加入 MAX_REWARD_RATE 校验且经时间锁生效,复审验证通过。

NS-10 · ERC20 转账未检查返回值,非标准代币可静默失败

严重等级: Medium 状态: Resolved 类别: 外部调用 位置: VaultUUPS.sol:240 · _payout()

_payout() 使用裸 transfer 且未检查返回值。对于返回 false 而非 revert 的非标准代币(部分 BEP-20), 转账失败会被静默吞掉,导致账目与实际资产不一致。

修复建议: 统一使用 OpenZeppelin SafeERC20safeTransfer / safeTransferFrom

修复情况: 客户已全量改用 SafeERC20,复审验证通过。

复审结论

客户在复审窗口内修复了全部 10 项发现(2 严重 / 3 高危 / 5 中危)。umibit 对每一项修复做了 回归验证与不变量复测:全部通过。截至本终版报告,残余资金风险敞口为 $0,最终安全评级 低风险, 结论为 「通过,可上线」

本报告编号 UMB-2026-0142 已登记入册,可独立核验。

如何核验本报告

请将链上部署的合约字节码与本页所列受审 commit 比对,确认你正在核验的正是被审计的同一版本代码。

在登记处核验此编号 →

← 返回报告查询