Back to Blog

LiNEAR 安全审计报告

Code Auditing
April 8, 2022
11 min read

报告清单

项目 描述
客户 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 函数中缺少对 prepaid_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'。但目前,我们宁愿不进行更改,以保持工作流程与质押池一致。作为一种变通方法,当用户从前端取消质押时,UI 会在用户账户中有 'unstaked' 金额时提醒用户先提取。

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

影响 由于受益人过多,epoch_update_rewards 函数可能因 gas 不足而无法正常工作。

建议 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 函数中缺少对 prepaid_gas 的检查

信息 描述
状态 版本 2 中已修复
由...引入 版本 1

描述 应检查 prepaid_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 检查 prepaid_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 赏金计划,以确保智能合约的安全性。

本次审计范围仅限于第 1.1 节中提到的代码。除非另有明确规定,否则语言本身(例如,solidity 语言)、底层编译工具链和计算基础设施的安全性均不在审计范围之内。

3.2 审计流程

我们根据以下流程进行审计。

  • 漏洞检测 我们首先使用自动代码分析器扫描智能合约,然后手动验证(拒绝或确认)它们报告的问题。

  • 语义分析 我们研究智能合约的业务逻辑,并使用自动模糊测试工具(由我们的研究团队开发)对可能的漏洞进行进一步调查。我们还与独立审计师一起手动分析可能的攻击场景,以交叉检查结果。

  • 建议 我们从良好的编程实践的角度为开发人员提供一些有用的建议,包括 gas 优化、代码风格等。

我们在以下展示主要具体检查点。

3.2.1 软件安全

  • 重入攻击

  • DoS(拒绝服务)

  • 访问控制

  • 数据处理和数据流

  • 异常处理

  • 不受信任的外部调用和控制流

  • 初始化一致性

  • 事件操作

  • 易出错的随机性

  • 代理系统的使用不当

3.2.2 DeFi 安全

  • 语义一致性

  • 功能一致性

  • 访问控制

  • 业务逻辑

  • 代币操作

  • 紧急机制

  • 预言机安全

  • 白名单和黑名单

  • 经济影响

  • 批量转账

3.2.3 NFT 安全

  • 重复项

  • 代币接收者验证

  • 链下元数据安全

3.2.4 附加建议

  • Gas 优化

  • 代码质量和风格

::: 注意 之前的检查点是主要的。在审计过程中,我们可能会根据项目的功能使用更多检查点。 :::

Sign up for the latest updates
Newsletter - April 2026
Security Insights

Newsletter - April 2026

In April 2026, the DeFi ecosystem experienced three major security incidents. KelpDAO lost ~$290M due to an insecure 1-of-1 DVN bridge configuration exploited via RPC infrastructure compromise, Drift Protocol suffered ~$285M from a multisig governance takeover leveraging Solana's durable nonce mechanism, and Rhea Finance incurred ~$18.4M following a business logic flaw in its margin-trading module that allowed circular swap path manipulatio

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly
Security Insights

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly

This BlockSec weekly security report covers eight attack incidents detected between April 20 and April 26, 2026, across Ethereum, Avalanche, Sui, Base, HyperLiquid, and MegaETH, with total estimated losses of approximately $7.04M. The highlighted incident is the $1.3M GiddyDefi exploit, where the attacker did not break any cryptography or use a flash loan but simply replayed an existing on-chain EIP-712 signature with the unsigned `aggregator` and `fromToken` fields swapped out for a malicious contract, demonstrating how partial signature coverage turns any historical signature into a generic permit. Other incidents include a $3.5M Volo Vault operator key compromise on Sui, a $1.5M Purrlend privileged-role takeover, a $413K SingularityFinance oracle misconfiguration, a $142.7K Scallop cross-pool index injection, a $72.35K Kipseli Router decimal mismatch, a $50.7K REVLoans (Juicebox) accounting pollution, and a $64K Custom Rebalancer arbitrary-call exploit.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit