보고서 개요
| 항목 | 설명 |
|---|---|
| 고객 | 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 위의 점검 항목은 주요 항목입니다. 프로젝트의 기능에 따라 감사 과정에서 추가적인 점검 항목을 사용할 수 있습니다. :::



