合约审查报告 · 开发者导向的行为说明 · 不是安全审计 · 主动披露已知边界

LAK 合约审查报告

开发者导向的合约行为说明 · 不等同于安全审计

项目 内容
审查对象 LAK @ 0xb25E69F0D1f07A4e473DAdf93Af5f9322AA5DBF6(Sepolia,chainId 11155111)
源码来源 链上已验证源码,经字节码比对确认与仓库源码一致(见第 2 节)—— ⚠️ 该一致性现仅对 2026-09-17 修复前的提交成立
审查方式 源码逐行精读 + 链上元数据核验;不含动态测试、模糊测试或形式化验证
报告性质 不是安全审计、不是安全认证、不是形式化验证。仅说明"合约做了什么、谁能做什么、资产如何流动"
证据规则 所有断言标注代码位置;名称与注释仅作线索,行为以实现为准
报告日期 2026-09-16(2026-09-17 更新:6.3 修复落地,见 6.3 节)
配套文件 AUDIT-SELF-CHECK.md(工具驱动的自查)、ISSUANCE-STRUCTURE.md(治理事实)、WHITEPAPER.md(对外口径)

⚠️ 源码修订影响(2026-09-17):6.3 的修复已合入仓库源码,但尚未部署。 Sepolia 上的 0xb25E69…DBF6 仍是修复前的字节码,与当前仓库源码不再逐位一致。 第 2 节的一致性核验结论只对修复前的提交成立;下一次部署(含主网)必须重新核验。


1. 结论摘要

维度 结论
合约类型 ERC-20 实用型代币 + 角色权限治理 + 行情联动销毁;非代理、不可升级
继承来源 全部为 OpenZeppelin v5.0.2 标准实现;项目自定义逻辑 5 处(见第 4 节)
资产流向 无原生资产托管;唯一外部调用是 rescueToken(受 DEFAULT_ADMIN_ROLE + nonReentrant 约束)
特权能力 7 类角色,全部可移交多签(当前已全部移交 3 持有人 / 2 签多签)
链上约束 硬顶 / 费率上限 / 销毁率上限 / 指数与灵敏度上限均为常量,超限交易直接回滚
本次新发现 1 项值得注意的风险点(见 6.3:治理参数可导致转账全局回滚),此前自查报告未覆盖 —— 已于 2026-09-17 在仓库源码修复,待下次部署生效

2. 链上一致性核验(本次新增证据)

方法:拉取链上运行时字节码,与仓库编译产物 artifacts/LAK.sol/LAK.json 的 deployedBytecode 逐位比对。

项 结果
字节码长度 链上 10,520 字节 = 本地 10,520 字节
逐位比对 差异仅出现在 immutable 填充位置
差异来源 OZ utils/cryptography/EIP712.sol 的 7 个 immutable(_cachedChainId = 11155111、_cachedThis = 合约地址、_cachedDomainSeparator、_hashedName、_hashedVersion、_name、_version)—— 编译期为占位符、部署期写入,属预期行为
归一化后比对 ✅ 完全一致

链上元数据(Etherscan)

项 值
ContractName LAK
Compiler v0.8.24+commit.e11b9ed9
Optimization 开启,runs = 200
EVM Version paris
Proxy 否
Library 无
源码大小 128,721 字节

✅ 结论:链上运行的代码与仓库源码编译结果一致。 这一核验为后续"以仓库源码为审查依据"提供了依据。


3. 架构与继承

LAK
├── ERC20                    (OZ) 基础代币
├── ERC20Burnable            (OZ) 持有人自毁
├── ERC20Pausable            (OZ) 暂停
├── ERC20Permit              (OZ) EIP-2612 离线授权
├── AccessControl            (OZ) 七类角色
└── ReentrancyGuard          (OZ) 重入防护

重写点:decimals() / _update()
新增点:角色常量、经济参数、风控状态、事件、错误、业务函数

唯一状态变更入口:_update(from, to, value) —— 所有转账、铸造、销毁都经此函数 (contracts/LAK.sol:278),这是理解全部分发逻辑的关键。


4. 自定义逻辑清单(相对 OZ 标准实现的部分)

# 位置 内容
1 constructor (:111) 一次性授予 7 类角色给 admin;设置 feeRecipient;把 admin 与合约自身加入手续费豁免;可选创世铸造
2 _update (:278) 黑名单校验 → 行情销毁 → 手续费 → 净额到账(三段式 _update 调用)
3 currentBurnBps (:229) 销毁率计算:基础值 + 加权指数偏离 × 敏感度,封顶 maxBurnBps
4 batchTransfer (:245) 循环 _transfer,nonReentrant 保护
5 rescueToken (:261) 归集误入的其他 ERC-20;显式禁止操作 LAK 自身

5. 参与者与可执行动作

5.1 任何持币人(无需角色)

动作 入口 说明
转账 transfer / transferFrom 经 _update,可能触发销毁与手续费
自毁代币 burn OZ ERC20Burnable;不计入 totalBurned(见 6.2)
授权 approve / permit permit 为一次签名完成离线授权
批量分发 batchTransfer (:245) 数组长度必须一致,否则 ArrayLengthMismatch

5.2 MINTER_ROLE

动作 约束
mint(to, amount) (:139) to ≠ 0;to 未被冻结;totalSupply() + amount ≤ MAX_SUPPLY(常量 21,000,000)

5.3 BURNER_ROLE

动作 约束
burnFromAccount(account, amount) (:147) account ≠ 0;余额不足由 _burn 回滚;累加 totalBurned
setBurnPolicy(...) (:209) maxBurnBps ≤ 500;baseBurnBps ≤ maxBurnBps;musicWeightBps ≤ 10000

5.4 PAUSER_ROLE

动作 说明
pause() / unpause() (:156、:160) 两个函数共用同一角色,无法拆分"暂停即时、恢复需审批"

5.5 COMPLIANCE_ROLE

动作 说明
setBlocked(account, bool) (:165) 被冻结地址无法转出、转入、被增发(_update 与 mint 双重校验)

5.6 FEE_ROLE

动作 约束
setFeeBps (:173) ≤ 500(5%)
setFeeRecipient (:179) ≠ 0
setFeeExempt(account, bool) (:185) 豁免同时免除销毁与手续费(见 6.4)

5.7 ORACLE_ROLE

动作 约束
setIndustryIndex(ent, music) (:198) 两者均 ≠ 0;无上限(见 6.3)

5.8 DEFAULT_ADMIN_ROLE

动作 说明
grantRole / revokeRole 可授予或撤销任意角色(含自身)—— OZ 标准行为
renounceRole 仅能放弃自己的角色(OZ v5 要求 account == msg.sender)
rescueToken(token, to, amount) (:261) 归集误入资产;禁止 token == address(this);to ≠ 0;nonReentrant

6. 关键实现细节与观察

6.1 _update 的三段式扣减(✅ 确认行为)

// :292-293  计提
if (burnRate > 0) burnAmount = (value * burnRate) / BPS;
if (feeBps > 0)   feeAmount  = ((value - burnAmount) * feeBps) / BPS;

// :296-299  销毁(总供应下降)
super._update(from, address(0), burnAmount);

// :302-306  手续费(进入 feeRecipient)
super._update(from, recipient, feeAmount);

// :308  净额到账
super._update(from, to, value - burnAmount - feeAmount);
观察 说明
计费顺序 手续费按"销毁后金额"计提 —— 与白皮书 4.4 节「先销毁、再计费、最后净额到账」完全一致
扣减守恒 burnAmount + feeAmount + (value − burnAmount − feeAmount) = value ✓
计费条件 仅当 from 与 to 双方都不豁免 且 value > 0 时计费(:290)
gas 代价 三段式意味着最多 3 次 _update;开启销毁与手续费时,单笔转账的 gas 约为普通 ERC-20 的 2–3 倍
铸造/销毁 from == 0 或 to == 0 时 isTransfer = false → 不产生销毁与手续费 ✓

6.2 🟡 totalBurned 的统计口径不完整

销毁路径 是否计入 totalBurned
_update 中的行情销毁(:298) ✅ 计入
burnFromAccount(:150) ✅ 计入
burn()(OZ ERC20Burnable,持有人自毁) ❌ 不计入

影响:totalBurned 是"协议强制销毁量",不等于代币的累计销毁总量。 若对外以 totalBurned 作为"通缩证据",口径会偏低。

建议:对外表述改为「协议销毁量」,或在前端同时展示 MAX_SUPPLY − totalSupply() 作为总销毁量。 (属语义/展示问题,非安全缺陷。)

6.3 ✅ 治理参数可导致转账全局回滚(本次新发现 · 已在源码修复)

修复已落地(2026-09-17,仓库源码;尚未部署)

  • 指数上限:新增常量 MAX_INDUSTRY_INDEX = 10 × INDEX_SCALE(基准 100 → 上限 1000), setIndustryIndex 两个参数均校验
  • 灵敏度上限:新增常量 MAX_BURN_SENSITIVITY = BPS,setBurnPolicy 超限 revert SensitivityTooHigh
  • 计算侧防御:currentBurnBps() 对 devEntertainment / devMusic / weightedDev 按 MAX_DEV_BPS 封顶 —— 即使将来新增写入路径绕过入口校验,也不可能溢出
  • 上限以链上常量强制,与合约既有风格一致(硬顶 / 费率上限 / 销毁率上限同为常量)
  • 回归测试 test/LAK.burn-params.test.js 已从「记录缺陷行为」改为「断言拒绝」,并新增 「把每个参数都推到允许的最大值后 currentBurnBps 不溢出、转账始终可用」一例
  • 全量 54 项测试通过(本文件 12 项);合约体积 14,428 / 24,576 字节(58.7%)

以下为修复前的原始记录,保留以便对照。

currentBurnBps()(:229)在三处做了算术,其中两处没有上限约束:

uint256 devEntertainment = entertainmentIndex > indexTarget ? ((entertainmentIndex - indexTarget) * BPS) / indexTarget : 0;
uint256 weightedDev = (devEntertainment * (BPS - musicWeightBps) + devMusic * musicWeightBps) / BPS;
uint256 total = baseBurnBps + (weightedDev * burnSensitivity) / BPS;   // ← burnSensitivity 无上限

约束缺失点

变量 约束情况
entertainmentIndex / musicIndex 仅校验 ≠ 0,无上限(:202)
burnSensitivity 完全无约束(setBurnPolicy 未校验,:216-218 只校验了另外三个参数)
maxBurnBps ✅ 有上限(≤ 500)

后果(已实证)

以下结论由 test/LAK.burn-params.test.js 在本地 Hardhat 网络复现,非纯推演。

若 autoBurnEnabled = true,且治理方设定了大额 entertainmentIndex 与 burnSensitivity:

weightedDev * burnSensitivity  →  超过 2^256  →  Solidity 0.8 溢出检查 → revert

由于 currentBurnBps() 被 _update(:291)在符合计费条件的每一笔转账中调用:

实测结论 结果
currentBurnBps() 本身 ✅ 确认回滚
符合计费条件的普通转账(双方均未豁免) ✅ 确认一并回滚
pause() + unpause() 之后 ✅ 确认仍然回滚(问题在算术,不在暂停状态)
治理方把参数调回合理区间后 ✅ 确认恢复正常

等价于一次 unpause 无法恢复的链上冻结,必须靠额外治理操作才能恢复可用性。

风险评级:低(需要 ORACLE_ROLE + BURNER_ROLE 同时设置极端参数,而两者当前均由多签持有), 但后果严重,且此前的自查报告(Solhint / Slither / 覆盖率)未覆盖这一类"参数组合导致运行期回滚"的问题 —— 自动化工具检查的是代码缺陷,而这是参数组合造成的可用性故障。

建议

  1. 主网版本为 entertainmentIndex / musicIndex 增加合理上限(如 ≤ 100 × INDEX_SCALE 量级);
  2. 为 burnSensitivity 增加上限校验;
  3. 在 setIndustryIndex / setBurnPolicy 中加入一次"试算"(调用内部计算并确认不溢出);
  4. 运营侧设定硬性规则:修改参数的同一笔治理操作应先在小额转账上验证。

6.4 🟡 手续费豁免同时免除销毁

:290 的条件是「双方都不豁免才计费」,意味着任一豁免地址参与的交易:

影响:若把大量地址(如运营账户、合作方)加入豁免名单,通缩机制的实际作用范围会被显著缩小。

建议:豁免名单应保持最小化并对外公示;白皮书中"零手续费场景"的表述可补充"同时不计入销毁"。

6.5 🟡 rescueToken 的信任边界

IERC20(token).safeTransfer(to, amount);   // :268

token 为调用方指定的任意地址,理论上可以是恶意合约。现有防护:

防护 来源
返回值校验 OZ SafeERC20
重入保护 nonReentrant
禁止操作本币 token == address(this) → RescueNotAllowed

判定:风险可接受。 该函数仅能由 DEFAULT_ADMIN_ROLE(多签)调用,且调用方需主动指定恶意地址。

6.6 🟡 冗余防御分支

:301 feeRecipient == address(0) ? address(0) : feeRecipient —— 由于构造函数与 setFeeRecipient 均拒绝零地址,该分支逻辑不可达(与自查报告 5.3 一致)。保留无害,可精简。

6.7 ✅ 与各文档的一致性核对

检查项 结果
硬顶 21,000,000 ✅ 常量 MAX_SUPPLY(:43),mint 强制校验
费率上限 5% ✅ 常量 MAX_FEE_BPS(:45),setFeeBps 强制
销毁率上限 5% ✅ 常量 MAX_BURN_BPS(:46),setBurnPolicy 强制
七类角色 ✅ 与白皮书 8.1 节、README 角色矩阵一致
结算顺序(先销毁后计费) ✅ 与白皮书 4.4 节一致
indexTarget 为常量 ✅(:66),与自查报告 5.1「已实施」一致
合约注释称「项目主体位于老挝」 🟡 与 ISSUANCE-STRUCTURE.md「主体尚待登记」措辞存在张力。属注释表述,因合约已部署且不可升级,不建议为此重新部署;对外材料以 ISSUANCE-STRUCTURE.md 为准

7. 未覆盖范围与不确定性

本报告未做的事(不代表已完成):

已知不确定性

项 说明
6.3 的影响面 已由 test/LAK.burn-params.test.js 在本地 Hardhat 网络实证复现;未在真实测试网上演练
手续费与销毁的长期经济影响 需建模,不在本报告范围
totalBurned 口径 6.2 建议为语义调整,是否实施由项目方决定

8. 行动建议

优先级 事项 依据
✅ 已完成 为 entertainmentIndex / musicIndex / burnSensitivity 增加上限校验(MAX_INDUSTRY_INDEX / MAX_BURN_SENSITIVITY)+ currentBurnBps 计算侧防御性封顶 6.3
中 对外表述中区分「协议销毁量(totalBurned)」与「总销毁量(MAX_SUPPLY − totalSupply())」 6.2
低 白皮书补充说明:手续费豁免同时免除行情销毁;豁免名单应最小化并公示 6.4
低 豁免名单、feeRecipient、销毁策略等治理动作纳入公开披露流程 6.4
低 精简 :301 的不可达分支(随主网版本一并处理) 6.6
✅ 已完成 字节码与仓库源码一致性核验(⚠️ 仅对 2026-09-17 修复前的提交成立,见文首提示) 第 2 节

⚠️ 上述建议中,凡涉及源码改动的,均须重新编译、重新部署、重新验证并重做多签交接(合约不可升级)。 建议与第三方审计的发现合并处理,一次性进入主网版本。

🔴 本条现已触发:6.3 的修复已于 2026-09-17 改动源码,因此必须走完 重新编译 → 重新部署 → 重新验证 → 重做多签交接。当前 Sepolia 部署仍是修复前版本、 与仓库源码不一致;下一次部署应把本修复与第三方审计的发现合并为同一个版本,避免改两次。


9. 免责声明

本报告为开发者导向的合约行为说明,由项目方使用链上公开数据与自有源码自行整理,不构成第三方安全审计、不构成安全认证、不构成形式化验证,也不构成任何投资建议或法律意见。

报告中的"确认行为"指有代码位置支撑的实现描述;"观察"与"不确定性"指基于阅读的推断,可能不完整或有误。在主网部署前,本合约仍须经独立第三方审计机构的完整审计。


报告日期:2026-09-16 · 审查对象:0xb25E69F0D1f07A4e473DAdf93Af5f9322AA5DBF6(Sepolia) · 源码:contracts/LAK.sol(310 行)

本文为开发者导向的合约行为说明,不构成安全审计、安全认证或投资建议。