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 代理) | 7f3c9a2bd6 | 0x4D7c1B2a3E5f60718293A4b5C6d7E8f901234ABC |
| StakingRewards.sol | 奖励分发 | 7f3c9a2bd6 | 0xB1a2C3d4E5F60718293a4B5c6D7e8F90DEAD0001 |
| RewardToken.sol | 奖励代币(BEP-20) | 7f3c9a2bd6 | 0x9F8e7D6c5B4a39281706F5e4D3c2B1A0FEED0002 |
| VaultTimelock.sol | 治理时间锁 | 7f3c9a2bd6 | — |
漏洞汇总
严重 2高 3中 5| ID | 标题 | 严重 | 状态 | 类别 |
|---|---|---|---|---|
| NS-01 | claimRewards() 提现重入可重复领取奖励 | 严重 | Resolved 已修复 | 重入 |
| NS-02 | UUPS 代理 initialize() 未锁定,可被任意账户抢先初始化夺取管理员 | 严重 | Resolved 已修复 | 初始化 |
| NS-03 | 奖励结算顺序错误,新存款可稀释已存款者的应计奖励 | 高 | Resolved 已修复 | 业务逻辑 |
| NS-04 | emergencyWithdraw() 绕过会计,破坏 totalStaked 全局不变量 | 高 | Resolved 已修复 | 业务逻辑 |
| NS-05 | owner 可无时间锁修改奖励速率并提走全部奖励储备 | 高 | Resolved 已修复 | 中心化权限 |
| NS-06 | 份额汇率读取现货储备,可被闪电贷操纵 | 中 | Resolved 已修复 | 预言机 / 闪电贷 |
| NS-07 | deposit/withdraw 缺少最小输出/滑点保护 | 中 | Resolved 已修复 | 业务逻辑 |
| NS-08 | 奖励整数取整向下舍入,小额质押者长期被吞奖励 | 中 | Resolved 已修复 | 整数运算 |
| NS-09 | setRewardRate() 缺上限校验,可设置极端值 | 中 | Resolved 已修复 | 访问控制 |
| NS-10 | ERC20 转账未检查返回值,非标准代币可静默失败 | 中 | 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」这一
核心不变量被打破。后续奖励分摊与汇率计算据此出错,长期可造成会计漂移与坏账。
修复建议: 紧急提现也必须走完整会计:递减 balances 与 totalStaked、结算并清零奖励;并加 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 SafeERC20 的 safeTransfer / safeTransferFrom。
修复情况: 客户已全量改用 SafeERC20,复审验证通过。
复审结论
客户在复审窗口内修复了全部 10 项发现(2 严重 / 3 高危 / 5 中危)。umibit 对每一项修复做了 回归验证与不变量复测:全部通过。截至本终版报告,残余资金风险敞口为 $0,最终安全评级 低风险, 结论为 「通过,可上线」。
本报告编号 UMB-2026-0142 已登记入册,可独立核验。