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 farm이 추가될 때 reward_per_session의 값을 미리 계산하십시오. 이는 소유자가 중단하지 않는 한 farm.amount, farm.end_date 및 farm.start_date가 farming 과정에서 변경되지 않기 때문입니다.

2.2 DeFi 보안

2.2.1 잠재적 이슈 2: 사용자의 가용 잔액이 일시적으로 잠길 수 있음

정보 설명
상태 확인됨
도입된 버전 버전 1

설명 사용자가 예치한 NEAR는 사용자의 언스테이크된 잔액에 직접 추가됩니다. 따라서 사용자가 unstake/unstake_all 함수를 호출하면, 해당 가용 NEAR 금액이 다음 0~8 에포크 동안 잠기게 됩니다.

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 가용 NEAR를 유지하기 위해 Account 구조체에 다른 속성(예: available_amount)을 추가하십시오.

프로젝트의 피드백 이것은 의도된 설계로, 기본적으로 스테이킹 풀의 원래 인터페이스와 설계를 따르고 있습니다. 잠재적 이슈를 해결하기 위해, 'unstaked'와 구별하기 위한 'unstaking' 필드를 추가하고, 해당 계정에 대한 다음 언스테이킹 프로세스를 시작하기 전에 'unstaking'을 'unstaked'로 이동시키는 개선을 할 수 있습니다. 그러나 현재로서는 스테이킹 풀과의 워크플로우 일관성을 유지하기 위해 변경을 하지 않는 것을 선호합니다. 임시 해결책으로, 프론트엔드에서 언스테이킹 시 사용자 계정에 'unstaked' 금액이 있는 경우 UI가 먼저 출금하도록 알림을 표시할 것입니다.

2.2.2 잠재적 이슈 3: 수혜자에 대한 무제한 보상 분배

정보 설명
상태 버전 2에서 수정됨
도입된 버전 버전 1

설명 이 컨트랙트는 새 수혜자를 설정할 때 assert_valid 함수에서 모든 수혜자의 총 가중치를 확인하지 않습니다.

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%를 초과하면, epoch_update_rewards 작업 실행 후 수혜자를 위해 발행된 LiNEAR가 LiNEAR의 가격을 하락시킬 수 있습니다.

제안 I 수혜자에게 분배되는 총 보상을 제한하기 위해 합리적인 임계값을 도입하십시오.

2.2.3 잠재적 이슈 4: 사용자의 언스테이킹 요청이 제때 처리되지 않을 수 있음

정보 설명
상태 버전 2에서 수정됨
도입된 버전 버전 1

설명 일부 극단적인 경우에 get_num_epoch_to_unstake 함수에서 반환되는 에포크 수가 두 배가 되어야 합니다. 예를 들어, 대기 중 상태가 아닌 검증자 스테이킹 풀에 스테이킹된 총 NEAR가 충분하지 않은 경우, 사용자는 4 에포크 후에 요청된 모든 언스테이크된 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 함수가 가스를 소진할 수 있습니다.

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 함수가 작동하지 않을 수 있습니다.

제안 I 수혜자 수를 제한하기 위해 합리적인 임계값을 추가하는 것을 권장합니다.

2.3.2 불필요한 코드

정보 설명
상태 확인됨
도입된 버전 버전 1

설명 storage_deposit 함수가 이 매개변수에 대한 어떠한 로직도 구현하지 않으므로 registration_only 매개변수는 불필요합니다.

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 함수에서 이 사용되지 않는 매개변수를 제거하는 것을 권장합니다.

프로젝트의 피드백 맞습니다. 동일한 문제가 nearcontract-standards 크레이트의 표준 FT 구현에도 발생합니다. 표준 storage_deposit(account_id, registration_only) 인터페이스와의 일관성을 유지하기 위해 사용되지 않는 registration_only 매개변수를 유지할 것입니다.

2.3.3 ft_transfer_call 함수에서 prepaid_gas 확인 누락

정보 설명
상태 버전 2에서 수정됨
도입된 버전 버전 1

설명 ft_on_transfer 및 ft_resolve_transfer를 포함한 대상 함수에 충분한 가스가 있는지 확인하기 위해 prepaid_gas를 검사해야 합니다.

#[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 면책 조항

이 감사 보고서는 투자 조언이나 개인적인 추천을 구성하지 않습니다. 토큰, 토큰 판매 또는 기타 제품, 서비스, 자산의 잠재적 경제성을 고려하거나 이에 영향을 미치는 것으로 해석되어서는 안 됩니다. 어떠한 주체도 토큰, 제품, 서비스 또는 기타 자산의 매수 또는 매도 결정을 포함한 어떠한 목적으로도 이 보고서에 의존해서는 안 됩니다.

이 감사 보고서는 특정 프로젝트나 팀에 대한 보증이 아니며, 보고서는 특정 프로젝트의 보안을 보장하지 않습니다. 이 감사는 스마트 컨트랙트의 모든 보안 이슈를 발견한다는 보증을 제공하지 않습니다. 즉, 평가 결과는 추가적인 보안 이슈 발견이 없음을 보장하지 않습니다. 하나의 감사가 포괄적인 것으로 간주될 수 없으므로, 스마트 컨트랙트의 보안을 보장하기 위해 독립적인 감사와 공개 버그 바운티 프로그램을 진행할 것을 항상 권장합니다.

이 감사의 범위는 섹션 1.1에 언급된 코드로 제한됩니다. 명시적으로 지정되지 않는 한, 언어 자체의 보안(예: Solidity 언어), 기반 컴파일 툴체인 및 컴퓨팅 인프라는 범위에 포함되지 않습니다.

3.2 감사 절차

다음 절차에 따라 감사를 수행합니다.

  • 취약점 탐지 자동 코드 분석기로 스마트 컨트랙트를 먼저 스캔한 후, 보고된 이슈를 수동으로 검증(거부 또는 확인)합니다.

  • 의미 분석 스마트 컨트랙트의 비즈니스 로직을 연구하고, 자동 퍼징 도구(연구팀이 개발)를 사용하여 가능한 취약점에 대해 추가 조사를 수행합니다. 또한 독립적인 감사자와 함께 가능한 공격 시나리오를 수동으로 분석하여 결과를 교차 검증합니다.

  • 권고 사항 가스 최적화, 코드 스타일 등 좋은 프로그래밍 관행의 관점에서 개발자에게 유용한 조언을 제공합니다.

주요 구체적인 점검 항목은 다음과 같습니다.

3.2.1 소프트웨어 보안

  • 재진입 공격

  • 서비스 거부(DoS)

  • 접근 제어

  • 데이터 처리 및 데이터 흐름

  • 예외 처리

  • 신뢰할 수 없는 외부 호출 및 제어 흐름

  • 초기화 일관성

  • 이벤트 작업

  • 오류 발생 가능성이 있는 무작위성

  • 프록시 시스템의 부적절한 사용

3.2.2 DeFi 보안

  • 의미 일관성

  • 기능 일관성

  • 접근 제어

  • 비즈니스 로직

  • 토큰 작업

  • 긴급 메커니즘

  • 오라클 보안

  • 화이트리스트 및 블랙리스트

  • 경제적 영향

  • 일괄 전송

3.2.3 NFT 보안

  • 중복 항목

  • 토큰 수신자 검증

  • 오프체인 메타데이터 보안

3.2.4 추가 권고 사항

  • 가스 최적화

  • 코드 품질 및 스타일

::: Note 위의 점검 항목은 주요 항목입니다. 프로젝트의 기능에 따라 감사 과정에서 추가적인 점검 항목을 사용할 수 있습니다. :::

Sign up for the latest updates
~$160만 손실: Moke 토큰, LpdFi 익스플로잇 | BlockSec 위클리
Security Insights

~$160만 손실: Moke 토큰, LpdFi 익스플로잇 | BlockSec 위클리

2026년 8월 3~9일, BNB 체인에서 2건의 보안 사고가 발생해 총 약 160만 달러의 손실이 발생했으며, 모두 가격 조작이 원인이었습니다. LpdFi(약 69.7만 달러)는 PancakeSwap 유동성 풀을 주문 평가와 이자 정산에 동시 사용해 공격자가 원금을 부풀리고 과도한 이자를 탈취했습니다. Moke Token(약 90.6만 달러)은 조작 가능한 현물 가격과 중복 LP 배당 회계를 결합해 MOKE를 부풀려 BNB 배당을 반복 수령했습니다.

COLDCARD 사건: 지갑의 "무작위" 시드가 무작위가 아니었을 때
Security Insights

COLDCARD 사건: 지갑의 "무작위" 시드가 무작위가 아니었을 때

COLDCARD 펌웨어의 빌드 통합 버그로 비트코인 시드 생성이 취약한 소프트웨어 RNG로 라우팅되어 지갑 시드가 오프라인 복구 가능해졌습니다. 시드 자체의 취약점이므로 펌웨어 업데이트로 해결 불가하며, 2026년 8월 7일 기준 확인된 피해는 1,405 BTC(~9,100만 달러), 비공개 추정치는 최대 2,055 BTC입니다.

~$88M 손실: COLDCARD 및 LULA 익스플로잇 | BlockSec 위클리
Security Insights

~$88M 손실: COLDCARD 및 LULA 익스플로잇 | BlockSec 위클리

2026년 7월 27일~8월 2일, 비트코인과 BNB 체인에서 약 8,800만 달러 손실을 유발한 두 건의 보안 사고가 발생했습니다. COLDCARD 사고는 하드웨어 지갑 펌웨어의 엔트로피 오류로, RNG 매크로 활성화 여부 미확인으로 결정론적 폴백이 실행되어 약 1,370 BTC(~8,800만 달러)가 탈취됐습니다. BNB 체인의 LULA 토큰은 비즈니스 로직 취약점으로 `recycle()` 함수가 악용되어 PancakeSwap V2 유동성에서 약 57만 8천 달러가 유출됐습니다.

Best Security Auditor for Web3

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

BlockSec Audit
LiNEAR 보안 감사 보고서