报告清单
| 项目 | 描述 |
|---|---|
| 客户 | LiNEAR Protocol |
| 目标 | LiNEAR |
版本历史
| 版本 | 日期 | 描述 |
|---|---|---|
| 1.0 | 2022年4月1日 | 首次发布 |
1. 简介
1.1 关于目标合约
| 信息 | 描述 |
|---|---|
| 类型 | 智能合约 |
| 语言 | Rust |
| 方法 | 半自动化和手动验证 |
已审计的仓库包括 LiNEAR ^1。
审计过程是迭代的。具体来说,我们将审计修复已发现问题的提交。如果出现新问题,我们将继续此过程。审计期间的提交 SHA 值如下所示。我们的审计报告负责初始版本(即版本 1),以及用于修复审计报告中问题的后续新代码(在后续版本中)。

1.2 安全模型
为了评估风险,我们遵循行业和学术界广泛采用的标准或建议,包括 OWASP 风险评级方法 ^2 和通用弱点枚举 ^3。风险的总体严重程度由可能性和影响决定。具体来说,可能性用于估计攻击者发现和利用特定漏洞的可能性,而影响则用于衡量成功利用的后果。
在本报告中,可能性和影响均分为两个评级,即高和低,它们的组合显示在表 1.1 中。

因此,本报告中测量的严重程度分为三类:高、中、低。为求完整起见,还使用未确定来涵盖无法很好确定风险的情况。
此外,已发现问题的状态将属于以下四类之一:
-
未确定 尚未收到回复。
-
已确认 客户已收到问题,但尚未确认。
-
已确认 客户已识别出问题,但尚未修复。
-
已修复 问题已由客户确认并修复。
2. 发现
总共,我们在智能合约中发现了 4 个潜在问题。我们还有 4 个建议,如下所示:
-
高风险:0
-
中风险:2
-
低风险:2
-
建议:4
| ID | 严重性 | 描述 | 类别 | 状态 |
|---|---|---|---|---|
| 1 | 中 | 精度损失 | 软件安全 | 已修复 |
| 2 | 低 | 用户可用余额可能暂时被锁定 | DeFi 安全 | 已确认 |
| 3 | 中 | 受益人无限奖励分配 | DeFi 安全 | 已修复 |
| 4 | 低 | 用户的取款请求可能无法及时满足 | DeFi 安全 | 已修复 |
| 7 | - | 由于受益人数量不限,epoch_update_rewards 函数可能无法正常工作 | 建议 | 已修复 |
| 8 | - | 冗余代码 | 建议 | 已确认 |
| 6 | - | 函数 ft_transfer_call 中缺少对预付 gas 的检查 | 建议 | 已修复 |
| 9 | - | 中心化设计的风险 | 建议 | 已确认 |
详细信息将在以下各节中提供。
2.1 软件安全
2.1.1 潜在问题 1:精度损失
| 信息 | 描述 |
|---|---|
| 状态 | 在版本 2 中已修复 |
| 由...引入 | 版本 1 |
描述 在函数 internal_calculate_distribution 的第 125 行,在计算变量 reward_per_session 时,先进行了除法再进行乘法。
fn internal_calculate_distribution(
&self,
farm: &Farm,
total_staked: Balance,
) -> Option<RewardDistribution> {
if farm.start_date > env::block_timestamp() {
// Farm hasn't started.
return None;
}
let mut distribution = farm.last_distribution.clone();
if distribution.undistributed == 0 {
// Farm has ended.
return Some(distribution);
}
distribution.reward_round = (env::block_timestamp() - farm.start_date) / SESSION_INTERVAL;
let reward_per_session =
farm.amount / (farm.end_date - farm.start_date) as u128 * SESSION_INTERVAL as u128;
列表 2.1:contracts/linear/src/farm.rs
影响 在 Rust 语言中,整数除法会截断。在这种情况下,先进行整数除法再进行乘法可能会导致精度损失。
建议 I 修改此计算,先进行乘法再进行除法。
建议 II 在添加农场时预先计算 reward_per_session 的值。这是因为 farm.amount、farm.end_date 和 farm.start_date 在耕种过程中不会改变,除非所有者停止它。
2.2 DeFi 安全
2.2.1 潜在问题 2:用户可用余额可能暂时被锁定
| 信息 | 描述 |
|---|---|
| 状态 | 已确认 |
| 由...引入 | 版本 1 |
描述 用户的已存入 NEAR 将直接添加到用户的未质押余额中。因此,如果用户调用 unstake/unstake_all 函数,该金额的可用 NEAR 将在接下来的 0 到 8 个 epoch 中被锁定。
pub(crate) fn internal_deposit(&mut self, amount: Balance) {
let account_id = env::predecessor_account_id();
let mut account = self.internal_get_account(&account_id);
account.unstaked += amount;
self.internal_save_account(&account_id, &account);
Event::Deposit {
account_id: &account_id,
amount: &U128(amount),
new_unstaked_balance: &U128(account.unstaked),
}
.emit();
}
列表 2.2:contracts/linear/src/internal.rs
影响 如果用户不知道此合约的工作流程并直接与此合约交互,用户的可用余额可能会暂时被锁定。
建议 I 向 Account 结构体添加另一个属性(例如,available_amount)来维护可用的 NEAR。
项目反馈 这是按设计进行的,基本上遵循了质押池的原始接口和设计。为了解决潜在问题,我们可以通过添加一个名为 'unstaking' 的字段来区分 'unstaked',并在我们开始该账户的下一个取款过程之前,将 'unstaking' 移动到 'unstaked' 中。但目前,我们倾向于暂时不做更改,以保持工作流程与质押池一致。作为一种变通方法,当用户从前端取款时,如果他们账户中有 'unstaked' 金额,UI 将提醒用户先取款。
2.2.2 潜在问题 3:受益人无限奖励分配
| 信息 | 描述 |
|---|---|
| 状态 | 在版本 2 中已修复 |
| 由...引入 | 版本 1 |
描述 此合约在设置新受益人时,不会检查所有受益人的总权重。
pub fn set_beneficiary(&mut self, account_id: AccountId, fraction: Fraction) {
self.assert_owner();
fraction.assert_valid();
self.beneficiaries.insert(&account_id, &fraction);
}
列表 2.3:contracts/linear/src/owner.rs
pub fn assert_valid(&self) {
require!(self.denominator != 0, ERR_FRACTION_BAD_DENOMINATOR);
require!(
self.numerator <= self.denominator,
ERR_FRACTION_BAD_NUMERATOR
);
}
列表 2.4:contracts/linear/src/utils.rs
影响 一旦受益人的总权重超过 100%,为受益人铸造的 LiNEAR 可能会在执行 epoch_update_rewards 操作后降低 LiNEAR 的价格。
建议 I 引入一个合理的阈值来限制分配给受益人的总奖励。
2.2.3 潜在问题 4:用户的取款请求可能无法及时满足
| 信息 | 描述 |
|---|---|
| 状态 | 在版本 2 中已修复 |
| 由...引入 | 版本 1 |
描述 描述 从函数 get_num_epoch_to_unstake 返回的 epoch 数量在某些边缘情况下应翻倍。例如,如果验证者质押池中未处于挂起状态的总 NEAR 不足,用户将无法在 4 个 epoch 后取回所有已请求的取款 NEAR。
pub fn get_num_epoch_to_unstake(&self, _amount: u128) -> EpochHeight {
// the num of epoches can be doubled or trippled if not enough stake is available
NUM_EPOCHS_TO_UNLOCK
}
列表 2.5:contracts/linear/src/validator_pool.rs
影响 用户的取款请求可能无法始终及时得到满足。
建议 I 根据验证者质押池的状态,实施一种预测用户取款等待时间的策略。
2.3 附加建议
2.3.1 函数 epoch_update_rewards 可能由于受益人数量不限而无法工作
| 信息 | 描述 |
|---|---|
| 状态 | 在版本 2 中已修复 |
| 由...引入 | 版本 1 |
描述 受益人的数量不受限制。在这种情况下,调用 internal_distribute_staking_rewards 函数的 epoch_update_rewards 函数可能会用完 gas。
pub fn epoch_update_rewards(&mut self, validator_id: AccountId) {
let min_gas = GAS_EPOCH_UPDATE_REWARDS + GAS_EXT_GET_BALANCE + GAS_CB_VALIDATOR_GET_BALANCE;
require!(
env::prepaid_gas() >= min_gas,
format!("{}. require at least {:?}", ERR_NO_ENOUGH_GAS, min_gas)
);
let validator = self
.validator_pool
.get_validator(&validator_id)
.expect(ERR_VALIDATOR_NOT_EXIST);
if validator.staked_amount == 0 && validator.unstaked_amount == 0 {
return;
}
validator
.refresh_total_balance()
.then(ext_self_action_cb::validator_get_balance_callback(
validator.account_id,
env::current_account_id(),
NO_DEPOSIT,
GAS_CB_VALIDATOR_GET_BALANCE,
));
}
列表 2.6:contracts/linear/src/epoch_actions.rs
/// When there are rewards, a part of them will be
/// given to executor, manager or treasury by minting new LiNEAR tokens.
pub(crate) fn internal_distribute_staking_rewards(&mut self, rewards: Balance) {
let hashmap: HashMap<AccountId, Fraction> = self.internal_get_beneficiaries();
for (account_id, fraction) in hashmap.iter() {
let reward_near_amount: Balance = fraction.multiply(rewards);
// mint extra LiNEAR for him
self.internal_mint_beneficiary_rewards(&account_id, reward_near_amount);
}
}
列表 2.7:contract/src/internal.rs
影响 当受益人过多时,由于 gas 有限,epoch_update_rewards 函数可能无法工作。
建议 I 建议添加一个合理的阈值来限制受益人的数量。
2.3.2 冗余代码
| 信息 | 描述 |
|---|---|
| 状态 | 已确认 |
| 由...引入 | 版本 1 |
描述 参数 registration_only 是冗余的,因为函数 storage_deposit 没有为该参数实现任何逻辑。
fn storage_deposit(
&mut self,
account_id: Option<AccountId>,
registration_only: Option<bool>,
) -> StorageBalance {
let amount: Balance = env::attached_deposit();
let account_id = account_id.unwrap_or_else(env::predecessor_account_id);
if let Some(account) = self.accounts.get(&account_id) {
log!("The account is already registered, refunding the deposit");
if amount > 0 {
Promise::new(env::predecessor_account_id()).transfer(amount);
}
} else {
let min_balance = self.storage_balance_bounds().min.0;
if amount < min_balance {
env::panic_str("The attached deposit is less than the minimum storage balance");
}
self.internal_register_account(&account_id);
let refund = amount - min_balance;
if refund > 0 {
Promise::new(env::predecessor_account_id()).transfer(refund);
}
}
self.internal_storage_balance_of(&account_id).unwrap()
}
列表 2.8:contracts/linear/src/fungible_token/storage.rs
建议 I 建议在此函数 storage_deposit 中删除此未使用参数。
项目反馈 这是正确的。NEAR-contract-standards crate 中的标准 FT 实现也是如此。我们将保留未使用的 registration_only 参数,以保持与标准 storage_deposit(account_id, registration_only) 接口的一致性。
2.3.3 函数 ft_transfer_call 中缺少对预付 gas 的检查
| 信息 | 描述 |
|---|---|
| 状态 | 在版本 2 中已修复 |
| 由...引入 | 版本 1 |
描述 应检查预付 gas,以确保其足以用于目标函数,包括 ft_on_transfer 和 ft_resolve_transfer。
#[payable]
fn ft_transfer_call(
&mut self,
receiver_id: AccountId,
amount: U128,
memo: Option<String>,
msg: String,
) -> PromiseOrValue<U128> {
assert_one_yocto();
let sender_id = env::predecessor_account_id();
let amount = amount.into();
self.internal_ft_transfer(&sender_id, &receiver_id, amount, memo);
// Initiating receiver's call and the callback
ext_fungible_token_receiver::ft_on_transfer(
sender_id.clone(),
amount.into(),
msg,
receiver_id.clone(),
NO_DEPOSIT,
env::prepaid_gas() - GAS_FOR_FT_TRANSFER_CALL,
)
.then(ext_ft_self::ft_resolve_transfer(
sender_id,
receiver_id,
amount.into(),
env::current_account_id(),
NO_DEPOSIT,
GAS_FOR_RESOLVE_TRANSFER,
))
.into()
}
列表 2.9:contracts/linear/src/fungible_token/core.rs
建议 I 检查预付 gas。
2.3.4 中心化设计的风险
| 信息 | 描述 |
|---|---|
| 状态 | 已确认 |
| 由...引入 | 版本 1 |
描述 此项目存在潜在的中心化问题。
建议 I 建议在合约中引入去中心化设计,例如多重签名或 DAO。
项目反馈 I 是的。如 github.com/linear-protocol/LiNEAR/issues/60 中所述,这已纳入计划。
建议 II 项目所有者需要确保 OWNER_ROLE/MANAGERS_ROLE 私钥的安全性,并使用多重签名方案来降低单点故障的风险。
项目反馈 II 是的。我们一直在制定安全策略以降低单点故障的风险。
3. 通知和备注
3.1 免责声明
本审计报告不构成投资建议或个人推荐。它不考虑,也不应被解释为考虑或影响代币、代币销售或任何其他产品、服务或资产的潜在经济性。任何实体都不应依赖本报告的任何方面,包括用于做出购买或出售任何代币、产品、服务或其他资产的决定。
本审计报告不代表对任何特定项目或团队的认可,报告也不保证任何特定项目的安全性。本次审计并不保证发现智能合约的所有安全问题,即评估结果不能保证不存在进一步的安全问题。由于一次审计不能被视为全面的,我们始终建议进行独立审计和公开的 Bug Bounty 计划,以确保智能合约的安全性。
本次审计的范围仅限于第 1.1 节中提到的代码。除非另有明确说明,语言本身的安全性(例如 Solidity 语言)、底层编译工具链和计算基础设施均不在审计范围内。
3.2 审计流程
我们按照以下流程进行审计。
-
漏洞检测 我们首先使用自动代码分析器扫描智能合约,然后手动验证(拒绝或确认)它们报告的问题。
-
语义分析 我们研究智能合约的业务逻辑,并使用自动模糊测试工具(由我们的研究团队开发)对潜在漏洞进行进一步调查。我们还由独立审计员手动分析可能的攻击场景,以交叉检查结果。
-
建议 我们从良好的编程实践的角度为开发人员提供一些有用的建议,包括 gas 优化、代码风格等。
我们在下面展示了主要的具体检查点。
3.2.1 软件安全
-
重入攻击
-
拒绝服务 (DoS)
-
访问控制
-
数据处理和数据流
-
异常处理
-
不受信任的外部调用和控制流
-
初始化一致性
-
事件操作
-
易出错的随机性
-
不当使用代理系统
3.2.2 DeFi 安全
-
语义一致性
-
功能一致性
-
访问控制
-
业务逻辑
-
代币操作
-
紧急机制
-
预言机安全
-
白名单和黑名单
-
经济影响
-
批量转账
3.2.3 NFT 安全
-
重复项
-
代币接收者验证
-
链下元数据安全
3.2.4 附加建议
-
Gas 优化
-
代码质量和风格
::: 注意 之前的检查点是主要的。我们可能会根据项目的实际情况在审计过程中使用更多检查点。 :::



