Back to Blog

NearOinDao セキュリティオーディットレポート

Code Auditing
2021年12月10日
37 min read

Report Manifest

Item Description
Client Oinfinance
Target NearOinDao

Version History

Version Date Description
1.0 Dec 04, 2021 First Release

1. はじめに

1.1 対象コントラクトについて

対象コントラクトにはステーブルコインモジュールが含まれています。それを 中心に、Staking や Farming などの他のモジュールも実装されています。 これらのモジュールは、ステーブルコイン(すなわち USDO)の安定化のための 正のフィードバックループを形成しています。

Information Description
Type Smart Contract
Language Rust
Approach Semi-automatic and manual verification

監査対象となったリポジトリには NearOinDao が含まれます ^1

監査プロセスは反復的に行われます。具体的には、発見された問題を修正した コミットについてさらに監査を行います。新たな問題が見つかった場合は、 このプロセスを継続します。そのため、本報告書には複数のコミット SHA 値が 記載されています。監査前後のコミット SHA 値を以下に示します。

監査前および監査中

監査後

Project Commit SHA
NearOinDao 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a

1.2 セキュリティモデル

リスクを評価するために、業界および学術界の両方で広く採用されている 基準や提案、具体的には OWASP Risk Rating Methodology ^2 および Common Weakness Enumeration ^3 に従います。これに基づき、本報告書で 測定される深刻度は、High、Medium、Low、Undetermined の 4つのカテゴリに分類されます。

2. 検出結果

合計で、スマートコントラクト内に 22 件の潜在的な問題を発見しました。 また、以下のように 12 件の推奨事項があります:

  • High Risk: 19

  • Medium Risk: 2

  • Low Risk: 1

  • Recommendations: 12

詳細は以下のセクションに記載されています。

ID Severity Description Category Status
1 High Logic error while self.liquidation_line is modified Software Security Confirmed and fixed
2 High Function liquidation may not work Software Security Confirmed and fixed
3 High Logic error while setting the time stamp for opening the contract Software Security Confirmed and fixed
4 High Contract state is not reverted if the cross con￾tract transaction is failed Software Security Confirmed and fixed
5 High Anyone can add the balance of reward DeFi Security Confirmed and fixed
6 High Anyone can add the balance of stable pool reward DeFi Security Confirmed and fixed
7 High Anyone can burn the other users’ coins DeFi Security Confirmed and fixed
8 High Anyone can add the balance of their account DeFi Security Confirmed and fixed
9 High Oracle does not check the time interval DeFi Security Confirmed and fixed
10 High Oracle time interval is too long DeFi Security Confirmed and fixed
11 High No oracle for Oin price DeFi Security Confirmed and fixed
12 High Users can gain extra reward DeFi Security Confirmed and fixed
13 High Users can pay less stable fee DeFi Security Confirmed and fixed
14 Middle The multi-signed request can be confirmed with a relatively low confirmation ratio DeFi Security Confirmed and fixed
15 Middle Block number per year is inaccurate DeFi Security Confirmed and fixed
16 High Available minted coins is not right DeFi Security Confirmed and fixed
17 High Payment of stable fee can result in the loss of user’s deposited tokens DeFi Security Confirmed and fixed
18 High Incorrect staking ratio DeFi Security Confirmed and fixed
19 Low Reward coins can beyond the limitation DeFi Security Confirmed and fixed
20 High Same whitelist for users in different privileges DeFi Security Confirmed and fixed
21 High No check on the address of stable fee DeFi Security Confirmed and fixed
22 High Reward coin’s total_reward can be modified by multi-Signature managers DeFi Security Confirmed and fixed
23 - Redundant assertion Recommendation Confirmed and fixed
24 - Repeated consideration of the liquidation line Recommendation Confirmed and fixed
25 - Redundant whitelist check Recommendation Confirmed and fixed
26 - Unused function Recommendation Confirmed and fixed
27 - Redundant Code Recommendation Confirmed and fixed
28 - The function name and the implementation is conflict Recommendation Confirmed and fixed
29 - Redundant Code Recommendation Confirmed and fixed
30 - The calculation precision can be enhanced Recommendation Confirmed and fixed
31 - System may not record previously poked price Recommendation Confirmed and fixed
32 - Discontinuous distribution of collateral token in liquidation Recommendation Confirmed and fixed
33 - Optimization of calculation precision is not necessary Recommendation Confirmed and fixed
34 - The risk of centralized design Recommendation Acknowledge

2.1 ソフトウェアセキュリティ

2.1.1 潜在的問題 1: 同じ用途に対する2つの異なる属性

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。2つの属性 (すなわち self.cost と self.liquidation_line)は、ユーザーの清算ライン という同じコントラクト状態を表しています。これらはコントラクトの 異なる関数で使用されています(Listing 2.1 および Listing 2.2)。 しかし、self.liquidation_line は set_liquidation_line 関数で変更可能 ですが、self.cost は変更できません。この場合、self.liquidation_line が 変更されても、self.cost は元の値を保持し続けます。これは assert_user_ratio 関数のロジックに影響を与える可能性があります (Listing 2.1)。

pub(crate) fn assert_user_ratio(&self) {
        let user_ratio = self.internal_user_ratio(env::predecessor_account_id());
        if user_ratio != 0 {
            assert!(user_ratio >= self.cost, "User ratio less than standard.");
        }
    }

Listing 2.1: assert_user_ratio:lib.rs

// TODO liquidation
    #[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        assert!(self.is_liquidation_paused(), "{}", SYSTEM_PAUSE);
        let ratio = self.internal_user_ratio(account.clone());
        assert!(ratio > 0, "No current pledge");
        assert!(ratio <= self.liquidation_line, "Not at the clearing line");
        ...

Listing 2.2: internal_can_mint_amount:lib.rs

影響 ユーザーの清算ラインがコントラクト内の異なる関数間で一致して おらず、コントラクト全体のロジックに影響を及ぼします。

提案 I ユーザーのステーキング比率を計算し、システムの清算ラインと 比較する際に、これら2つの属性の使用方法を統一することができます。

2.1.2 潜在的問題 2: 清算報酬の無効な分配

Item Description
Status Confirmed and fixed

説明 この問題は Commit-4 以前から存在しています。清算の送信者の アカウントとコントラクトオーナーのアカウントが登録されていない可能性が あります(List 2.3 の 193 行目および 206 行目)。この場合、送信者が 清算アクションを実行しようとすると、アカウントが登録されていないという 例外が発生し、トランザクションが正常に実行できません。

pub(crate) fn personal_liquidation_token(&mut self, send_id: AccountId, account_id: AccountId, liquidation_gas: Balance, surplus_token: Balance, liquidation_fee: Balance) {
        //self.owner_id
        let coin_id = ST_NEAR.to_string();
        let mut sys_reward_coin = self.internal_get_reward_coin(coin_id.clone());
        
        let account_reward_key_o = self.get_staker_reward_key(send_id.clone(), coin_id.clone());
        let user_reward_coin_o = self.internal_get_account_reward(send_id.clone(), coin_id.clone());
        
        self.account_reward.insert(
            &account_reward_key_o,
            &UserReward {
                index:  user_reward_coin_o.index,
                reward: user_reward_coin_o.reward.checked_add(liquidation_gas).expect(ERR_ADD),
            },
        );
        
        let account_reward_key_t = self.get_staker_reward_key(account_id.clone(), coin_id.clone());
        let user_reward_coin_t = self.internal_get_account_reward(account_id.clone(), coin_id.clone());

        if surplus_token > 0 {
            self.account_reward.insert(
                &account_reward_key_t,
                &UserReward {
                    index:  user_reward_coin_t.index,
                    reward: user_reward_coin_t.reward.checked_add(surplus_token).expect(ERR_ADD),
                },
            );
        }

        let account_reward_key_s = self.get_staker_reward_key(self.owner_id.clone(), coin_id.clone());
        let user_reward_coin_s = self.internal_get_account_reward(self.owner_id.clone(), coin_id.clone());

        self.account_reward.insert(
            &account_reward_key_s,
            &UserReward {
                index:  user_reward_coin_s.index,
                reward: user_reward_coin_s.reward.checked_add(liquidation_fee).expect(ERR_ADD),
            },
        );
       
        sys_reward_coin.total_reward = sys_reward_coin
            .total_reward
            .checked_add(liquidation_gas).expect(ERR_ADD)
            .checked_add(liquidation_fee).expect(ERR_ADD)
            .checked_add(surplus_token).expect(ERR_ADD);

        self.reward_coins.insert(&coin_id, &sys_reward_coin);
    }

}

Listing 2.3: personal_liquidation_token:reward.rs

影響 アカウントが登録されていないという例外が発生するため、 liquidation 関数が正常に実行されません。

提案 I liquidation 関数の冒頭で、清算送信者のアカウントと コントラクトオーナーのアカウントの存在を assert で確認してください。

2.1.3 潜在的問題 3: システムオープン時に Block_timestamp が closed_time に保存される

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。internal_open 関数を呼び出す際に env::block_time_stamp() を self.closed_time に 保存すべきではありません。

#[private]
    pub fn internal_open(&mut self) {
        self.closed_time = env::block_timestamp();
        self.open_stake();
        self.open_redeem();
        self.open_claim_reward();
        self.open_liquidation();
        self.open_stable();
        log!(
            "{} open sys in {}",
            env::predecessor_account_id(),
            self.closed_time
        );
    }

Listing 2.4: internal_open:esm.rs

影響 コントラクトの開始時刻と終了時刻が完全に誤っています。 時刻情報に依存するその後の更新で、ロジックエラーが発生する可能性が あります。

提案 I self.opening_time という新しいコントラクト状態を作成し、 コントラクトのオープンを呼び出す際に env::block_timestamp() をこの値に 割り当てることを提案します。

2.1.4 潜在的問題 4: クロスファンクション呼び出しが失敗した場合にコントラクト状態がリバートされない

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。storage_deposit と ft_transfer のプロセスは、クロスコントラクトの関数呼び出し中に失敗 する可能性があります。転送が常に正しく実行されることを保証することは できません。コールバック関数は、呼び出しが失敗した場合にコントラクト 状態をリバートしません。

#[private]
    pub fn storage_deposit_callback(&mut self) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {
                log!("Transfer success");
            }
            PromiseResult::Failed => {
                log!("Transfer failed");
            }
        }
    }

Listing 2.5: storage_deposit_callback:ft.rs

#[private]
    pub fn liquidation_transfer_callback(&mut self) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {
                log!("Transfer success");
            }
            PromiseResult::Failed => {
                log!("Transfer failed");
            }
        }
    }

Listing 2.6: liquidation_transfer_callback:ft.rs

影響 コールバック関数がコントラクト状態をリバートしないため、 トランザクションが失敗した場合にユーザーが資産を失う可能性があります。

提案 I クロスコントラクトの関数呼び出しのコールバック関数で、 (転送が失敗した場合に)コントラクト状態をリバートする必要があります。

2.2 DeFi セキュリティ

2.2.1 潜在的問題 5: inject_reward にアクセス制御が欠如している

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。inject_reward 関数は public です。誰でもこの関数を呼び出して、コントラクト内の 報酬の残高を追加することができます。

pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Amount not equal transfer_amount"
            );
        }
        ...
    }

Listing 2.7: inject_reward:pool.rs

影響 誰でもコントラクトの報酬に任意の残高を追加することができます。

提案 I この関数は、転送された報酬を受け取った後に内部的に呼び出される ため、private に変更すべきです。

2.2.2 潜在的問題 6: inject_sp_reward にアクセス制御が欠如している

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。inject_sp_reward 関数は public です。誰でもこの関数を呼び出して、コントラクト内の ステーブルプール報酬の残高を追加することができます。

pub fn inject_sp_reward(&mut self, _amount: U128, sender_id: ValidAccountId) {
        self.reward_sp = self.reward_sp + u128::from(_amount);

        log!(
            "{} add sp_reward  {} cur amount{}",
            sender_id,
            u128::from(_amount),
            self.reward_sp
        );
    }

Listing 2.8: inject_sp_reward:stablepool.rs

影響 誰でもコントラクトのステーブルプール報酬に任意の残高を 追加することができます。

提案 I この関数は、転送されたステーブルプール報酬を受け取った後に 内部的に呼び出されるため、private に変更すべきです。

2.2.3 潜在的問題 7: burn_coin にアクセス制御が欠如している

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。burn_coin 関数は public です。誰でもこの関数を呼び出して、他人のコインを バーンすることができます。

pub fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        assert!(self.is_redeem_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(sender_id);
        self.assert_is_poked();
        self.accured_token(sender_id.clone());
        ...
    }

Listing 2.9: burn_coin:lib.rs

影響 誰でもこの関数を使用して他人のコインをバーンでき、 ユーザーの資産の損失につながります。

提案 I この関数は、コインをバーンするための転送されたステーブル 手数料を受け取った後に内部的に呼び出されるため、private に変更すべきです。

2.2.4 潜在的問題 8: deposit_token にアクセス制御が欠如している

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。deposit_token 関数は public です。誰でもこの関数を呼び出して、自分のアカウントの 残高を追加することができます。

pub fn deposit_token(&mut self, amount: u128, _sender_id: ValidAccountId) {
        self.assert_is_poked();
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let _amount = u128::from(amount);
        let sender_id = AccountId::from(_sender_id);
        . . .
    }

Listing 2.10: deposit_token:lib.rs

影響 攻撃者はこの関数を呼び出して、自分のアカウントの残高を 追加することができます。

提案 I この関数は、デポジットされたトークンを受け取った後に 内部的に呼び出されるため、private に変更すべきです。

2.2.5 潜在的問題 9: Oracle に時間のチェックが欠如している

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。oracle.rs の assert_is_poked 関数は、トークン価格の値がゼロかどうかのみを チェックしています。トークン価格は常に変動しているため、これは 意味をなしません。

pub(crate) fn assert_is_poked(&self) {
        assert!(self.token_price != 0, "Oracle price isn't poked.");
    }

Listing 2.11: assert_is_poked:oracle.rs

影響 この問題は価格オラクルに影響を与えます。トークン価格が 長期間ポークされていない場合でも、assert は通過することができ、 関連するトランザクションは古い価格で実行される可能性があります。

提案 I コントラクトはポークされた価格に対して有効な時間期間を 設定すべきです。

2.2.6 潜在的問題 10: 不適切なオラクルのポーク間隔時間

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。types.rs で 定義された定数 POKE_INTERVAL_TIME は現在 1000 日を意味しています。 この時間間隔は長すぎるように見えます。妥当な値が必要です。

pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;

Listing 2.12: types.rs

影響 ポークされた価格の時間間隔が不適切です。

提案 I ポークされた価格の間隔時間を妥当な値にリセットしてください。

2.2.7 潜在的問題 11: Oin_Price に対する Assert の欠如

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。ユーザーの ステーブル手数料は self.oin_price によって計算されるにもかかわらず、 この関数は oin_token 価格の値がポークされているかどうかを チェックしていません。

pub fn internal_user_stable(&self, account: AccountId) -> u128 {
        let user_stable = self.account_stable.get(&account).expect("error");
        let allot = self.get_account_allot(account.clone()); 
        let coin = self
            .account_coin
            .get(&account)
            .expect("error")
            .checked_add(allot.0)
            .expect(ERR_ADD);
        let current_block_number = env::block_timestamp().checked_div(INIT_BLOCK_TIME).expect(ERR_DIV);
        user_stable
            .saved_stable
            .checked_add(
                self.stable_fee_rate//16
                    .checked_div(BLOCK_PER_YEAR)
                    .expect(ERR_DIV)
                    .checked_mul(current_block_number as u128 - user_stable.block)
                    .expect(ERR_MUL)
                    .checked_mul(coin)//8
                    .expect(ERR_MUL)
                    .checked_div(self.oin_price)//8
                    .expect(ERR_DIV)
                    .checked_div(ONE_COIN)//8
                    .expect(ERR_DIV),
            )
            .expect(ERR_ADD)
    }

Listing 2.13: internal_user_stable:lib.rs

影響 古い OIN 価格は、オラクルがポークした価格の新鮮さを チェックすることなく、価格操作につながる可能性があります。

提案 I ユーザーのステーブル手数料の計算の前に self.assert_is_poked(); のアサーションを追加してください。

2.2.8 潜在的問題 12: ユーザーがステーキングトークンでより多くのマイニング報酬を得る可能性がある

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。請求された報酬は 正確に計算されていません。internal_get_saved_reward 関数は、以下の 式を使用して t0 から t1 までのユーザーの特定のマイニング報酬を 計算するために呼び出されます:

account_allot.token は、他のユーザーの清算によって追加された 担保報酬であることに注意してください。ただし、清算は t0 から t1 の 間のいつでも発生する可能性があります。例えば、あるユーザーが day0 に 100 トークンをデポジットしたとします。day999 に、別のユーザーの 清算がトリガーされ、account_allot.token が 1000 に増加する 可能性があります。

ユーザーが day1000 に報酬を請求する場合、day999 の清算によって生じた 1000 トークンは、1日のみマイニングとしてカウントされるべきです。 しかし、コントラクトは実際には day0 から day1000 までの担保報酬の マイニング報酬を計算しています。

// TODO[OK] Calculation of reward
    pub(crate) fn internal_get_saved_reward(
        &self,
        staker: AccountId,      
        reward_coin: AccountId, 
    ) -> u128 {
        let reward_coin_ins = self.internal_get_reward_coin(reward_coin.clone());
        let (stake_token_num, _) = self.staker_debt_of(staker.clone());

        if let Some(user_reward) = self
            .account_reward
            .get(&self.get_staker_reward_key(staker.clone(), reward_coin.clone()))
        {
            user_reward
                .reward
                .checked_add(
                    U256::from(
                        reward_coin_ins
                            .index
                            .checked_sub(user_reward.index)
                            .expect(ERR_SUB),
                    )
                    .checked_mul(U256::from(stake_token_num))
                    .expect(ERR_MUL)
                    .checked_div(U256::from(reward_coin_ins.double_scale))
                    .expect(ERR_DIV)
                    .as_u128(),
                )
                .expect(ERR_ADD)
        } else {
            0
        }
    }

Listing 2.14: internal_get_saved_reward:views.rs

pub fn staker_debt_of(&self, staker: AccountId) -> (u128, u128) {
        if let Some(token) = self.account_token.get(&staker) {
            let coin = self.account_coin.get(&staker).expect(ERR_NOT_REGISTER);
            let allot = self.get_account_allot(staker.clone());
            (token + allot.1, coin + allot.0)
        } else {
            (0, 0)
        }
    }

Listing 2.15: staker_debt_of:views.rs

影響 ユーザーが追加の報酬を得る可能性があります。

提案 I マイニング報酬を計算する際に、新しく割り当てられた担保の 区分を削除してください。マイニング報酬をユーザーがデポジットした トークンの量のみに関連させることができます。

2.2.9 潜在的問題 13: ユーザーがより少ないステーブル手数料を支払う可能性がある

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。あるユーザーが day0 に 1000 USDO をミントし、その時の stable_fee_rate が 0.01oin/coin/day であると仮定します。ユーザーが day100 に 1000 USDO を 返却し、過去100日間でステーブル手数料率が変わらなかった場合、彼が 支払う必要のあるステーブル手数料は 0.01 Oin/coin/day * 1000 Coin * 100 Day = 1000 Oin です。しかし、オーナーが day99 に stable_fee_rate = 0.005 oin/coin/day を設定した場合、この時、 ユーザーは 0.005 Oin/Coin/Day * 1000 Coin * 100 Day = 500 Oin のみ 支払う必要があります。実際には、正確な手数料は: (0.01 Oin/Coin/Day * 1000 Coin * 99 Day) + (0.005 Oin/Coin/Day * 1000 Coin * 1 Day) = 990 Oin + 5 Oin = 995 Oin であるべきです。

この場合、ユーザーが支払う必要のない 495 Oin が発生します。

// TODO [OK]
    pub fn set_stable_fee_rate(&mut self, fee_rate: U128) {
        self.assert_param_white();
        self.update_stable_index();
        assert!(fee_rate.0 <= INIT_MAX_STABLE_FEE_RATE, "Exceeding the maximum setting");
        self.stable_fee_rate = fee_rate.into();
        log!("Set stable fee rate {}", fee_rate.0);
    }

Listing 2.16: set_stable_fee_rate:dparam.rs

pub fn update_stable_index(&mut self) {
    }

Listing 2.17: update_stable_index:stablefee.rs

影響 コントラクトのユーザーがステーブル手数料を少なく 請求される可能性があります。

提案 I このコントラクト内の reward_coin の計算のように、 ステーブル手数料のシステムインデックスを実装してください。 そして、コントラクトユーザーによって set_stable_fee_rate、liquidation、 update_stable_fee が呼び出されるたびに、ステーブル手数料の システムインデックスが更新されることを確認してください。

2.2.10 潜在的問題 14: 不合理なマルチシグ要求確認率

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。マルチシグ要求の 確認率は、要求が作成された時点のマルチシグ管理者の数によって 計算されます。しかし、マルチシグ管理者の数は後で変更される可能性が あります。この場合、管理者の数が増加すると、要求は低い確認率で 確認される可能性があります。

pub(crate) fn is_num_enough(&self, request_id: RequestId) -> bool {
        let request = self.requests.get(&request_id).unwrap();
        let confirmations = self.confirmations.get(&request_id).unwrap();

        let num_confirmrations = request.num_confirm_ratio * (request.mul_white_num);
        log!(
            "confim num is {} num needed is {} ",
            confirmations.len() as u32 * 100,
            num_confirmrations
        );

        (confirmations.len() as u64) * 100 >= num_confirmrations
    }

Listing 2.18: is_num_enough:multisign.rs

pub fn add_request_only(&mut self, request: MultiSigRequest) -> RequestId {
        self.assert_mul_white();
        ...

        let request_added = MultiSigRequestWithSigner {
            signer_pk: env::signer_account_pk(),
            added_timestamp: env::block_timestamp(),
            confirmed_timestamp: 0,
            request: request,
            is_executed: false,
            cool_down: self.request_cooldown,
            mul_white_num: self.mul_white_num(),
            num_confirm_ratio: self.num_confirm_ratio,
        };

        self.requests.insert(&self.request_nonce, &request_added);
        ...
    }

Listing 2.19: add_request_only:multisign.rs

影響 コントラクトは要求が作成された時点の管理者の数のみを 考慮するため、マルチシグ要求は低い確認率で確認される可能性が あります。

提案 I マルチシグ要求の確認率を計算する際に、現在のコントラクト 状態のマルチシグユーザーの数を使用することを検討してください。

2.2.11 潜在的問題 15: 不正確な年間ブロック数

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。NEAR プロトコルの メインネットでは1秒ごとにブロックが生成されることを考慮すると、 年間に生成されるブロック数は 31104000(360日)ではなく 31536000(365日)であるべきです。

pub const BLOCK_PER_YEAR: u128 = 31104000;

Listing 2.20: types.rs

影響 BLOCK_PER_YEAR の不正確な定数は、この定数を使用した計算結果を 現実と一致しないものにします。

提案 I BLOCK_PER_YEAR を 31536000 に変更してください。

2.2.12 潜在的問題 16: usdo の最大ミント可能量の不正確な計算

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。allot_token.0 は割り当てられた負債を表します。USDO の利用可能なミント量を計算する際、 割り当てられた負債はカウントされるべきではありません。そうでなければ、 非常に高い負債を持つユーザーが大量の USDO をミントできてしまいます。

pub(crate) fn internal_can_mint_amount(&self, account: AccountId) -> u128 {
        self.assert_is_poked();
        let token = self.account_token.get(&account).expect(ERR_NOT_REGISTER);
        let guarantee = self.guarantee.get(&account).expect(ERR_NOT_REGISTER);
        let allot_token = self.get_account_allot(account.clone());

        let max_usdo = (U256::from(token)
            .checked_add(U256::from(allot_token.1))
            .expect(ERR_ADD))
        .checked_mul(U256::from(self.token_price))
        .expect(ERR_MUL)
        .checked_div(U256::from(self.liquidation_line))
        .expect(ERR_DIV)
        .checked_div(U256::from(INIT_STABLE_INDEX))
        .expect(ERR_DIV)
        .checked_add(U256::from(allot_token.0))
        .expect(ERR_ADD)
        .checked_sub(U256::from(guarantee))
        .unwrap_or(U256::from(0))
        .as_u128();
        
        ...
    }

Listing 2.21: internal_can_mint_amount:lib.rs

影響 ユーザーは mint_coin 関数を呼び出す際に、追加の USDO を ミントできてしまいます。

提案 I 割り当てられた負債を表す allot_token.0 は、利用可能な ミント済み USDO としてカウントされるべきではありません。

2.2.13 潜在的問題 17: ユーザーのステーブル手数料の不正確な処理

Item Description
Status Confirmed and fixed (The related logic is removed now)

説明 この問題は Commit-1 以前から存在しています。ユーザーが burn_coin 関数を呼び出す際、ステーブル手数料は 'ST_NEAR' ではなく 'OIN' トークンで支払われます。しかし、コントラクトはユーザーの ステーキングトークンの残高を減少させており、これは正確ではありません。

pub(crate) fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        ...
            assert!(usdo >= amount.into(), "Insufficient amount");
            let token = self.account_token.get(&sender_id.clone()).expect(ERR_NOT_REGISTER);
            self.internal_burn(sender_id.clone(), amount.into());
   
            self.total_token = self.total_token.checked_sub(unpaid_fee.into()).expect(ERR_SUB);
            self.account_token.insert(
                &sender_id.clone(),
                &token.checked_sub(unpaid_fee.into()).expect(ERR_SUB),
            );
        ...
        
    }

Listing 2.22: burn_coin:lib.rs

影響 ユーザーのステーブル手数料の不正確な処理により、ユーザーの ステーキングトークンが減少する可能性があります。

提案 I ステーブル手数料の支払いには正しいトークンを使用してください。

2.2.14 潜在的問題 18: 不正確なシステム比率

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。total_coin = 0 の 場合、比率は +∞ であるべきです。0 に設定するのは不正確です。

pub(crate) fn internal_sys_ratio(&self) -> u128 {
        self.assert_is_poked();
        let token_usd = U256::from(self.total_token)
            .checked_mul(U256::from(self.token_price))
            .expect(ERR_MUL); /* 32 */
        let total_coin = self.total_coin + self.total_guarantee;
        if total_coin == 0 {
            0
        } else {
            token_usd
                .checked_div(U256::from(STAKE_RATIO_BASE))
                .expect(ERR_DIV)
                .checked_div(U256::from(total_coin))
                .expect(ERR_DIV)
                .as_u128()
        }
    }

Listing 2.23: internal_sys_ratio:lib.rs

影響 不正確な比率により、システムがシャットダウンする可能性が 高くなります。

提案 I if 条件の total_coin = 0 を token_usd = 0 に変更して ください。

2.2.15 潜在的問題 19: 報酬コインの数が上限を超える可能性がある

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。現在20個の 報酬コインがある場合、Listing 2.24 の 131 行目の assert は通過 することができます。この場合、報酬コインをもう1つ追加することができ、 報酬コインの総数が REWARD_UPPER_BOUND を超える可能性があります。

pub(crate) fn internal_add_reward_coin(&mut self, coin: RewardCoin) {
        assert!(
            self.reward_coins.len() <= REWARD_UPPER_BOUND,
            "The currency slot has been used up, please modify other currency information as appropriate",
        );

        match self.reward_coins.get(&coin.token) {
            Some(_) => {
                env::panic(b"The current currency has been added, please add a new currency.");
            }
            None => {}
        }
        self.reward_coins.insert(&coin.token, &coin);

        log!(
            "{} add the RewardCoin=> {:?}",
            env::predecessor_account_id(),
            coin
        )
    }

Listing 2.24: internal_add_reward_coin:pool.rs

影響 追加可能な報酬コインの数が、システムの設計と矛盾します。

提案 I assert を self.reward_coins.len() < REWARD_UPPER_BOUND に 変更してください。

2.2.16 潜在的問題 20: 異なる権限のユーザーが同じホワイトリストを使用している

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。 assert_param_white、assert_white、assert_esm_white、assert_oracle_white 関数は、異なる権限に対して使用されます。しかし、これらは同じ ホワイトリストを共有しています。

pub(crate) fn assert_esm_white(&self) {
        self.assert_white()
    }

Listing 2.25: assert_esm_white:esm.rs

pub(crate) fn assert_param_white(&self) {
        self.assert_white();
    }

Listing 2.26: assert_param_white:dparam.rs

pub(crate) fn assert_oracle_white(&self) {
        self.assert_white();
    }

Listing 2.27: assert_oracle_white:oracle.rs

影響 異なる権限のユーザーが同じホワイトリストを共有しています。

提案 I 異なる権限のユーザーに対して異なるホワイトリストを 実装してください。

2.2.17 潜在的問題 21: burn_coin がトークンタイプをチェックしていない

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。burn_coin 関数は トークンタイプをチェックしていません。この場合、攻撃者はステーブル 手数料を支払うために、指定された量の任意のトークンを転送することが できます。

pub fn burn_coin(&mut self, amount: U128, fee: Balance, sender_id: ValidAccountId) -> Balance{
        assert!(self.is_redeem_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(sender_id);

Listing 2.28: assert_esm_white:esm.rs

影響 ユーザーは Oin トークンを支払う必要がなく、代わりに 必要な量の任意のトークンを転送することでステーブル手数料を 支払うことができます。

提案 I 受け取ったトークンのアドレスをチェックしてください。

2.2.18 潜在的問題 22: 報酬コインの total_reward がマルチシグ管理者によって変更される可能性がある

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。inject_reward 関数は #[private] で装飾されています。そのため、マルチシグ管理者は マルチシグ要求を通じてこの関数を呼び出すことができ、報酬を注入する ことなく、総報酬に任意の量を追加することができます。

#[payable]
    #[private]
    pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Amount not equal transfer_amount"
            );
        }

        if let Some(reward_coin_ins) = self.get_reward_coin(reward_coin.clone()) {
            let mut reward_coin_ins = reward_coin_ins;
            reward_coin_ins.total_reward = reward_coin_ins
                .total_reward
                .checked_add(amount.into())
                .expect(ERR_SUB);
            self.reward_coins.insert(&reward_coin, &reward_coin_ins);

            if reward_coin == String::from("NEAR") {
            
            } else {
                log!("Transfer is not required for post-processing");
            }
        } else {
            env::panic(b"No the reward coin.");
        }
    }

Listing 2.29: ainject_reward:pool.rs

提案 I デコレータ #[private] を削除し、inject_reward 関数の 可視性を private に変更してください。

2.3 追加の推奨事項

2.3.1 冗長なアサーション

Item Description
Status Confirmed and fixed

説明 この問題は Commit-2 以前から存在しています。inject_reward 関数は内部的に ft_on_transfer からのみ呼び出されるべきです。報酬コインの アドレスは ft_on_transfer でチェックされています。この場合、 inject_reward 関数の冒頭で報酬コインの名前をチェックする必要は ありません。

#[payable]
    #[private]
    pub  fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
        // self.assert_owner();

        if reward_coin == String::from("NEAR") {
            assert!(
                amount.0 == env::attached_deposit(),
                "Amount not equal transfer_amount"
            );
        }

    ...
    }

Listing 2.30: inject_reward:pool.rs


    pub fn ft_on_transfer(
        &mut self,
        sender_id: ValidAccountId,
        amount: U128,
        msg: String, /* token */
    ) -> PromiseOrValue<U128> {
    ...
            FtOnTransferArgs::InjectReward => {
                assert_eq!(sender_id.to_string(), self.owner_id, "ERR_NOT_ALLOWED");

                assert!(
                    self.reward_coins.get(&token_account_id).is_some(),
                    "Invalid reward coin"
                );

                self.inject_reward(amount, token_account_id);
                amount_return = 0;
            }
    ...
    }

Listing 2.31: ft_on_transfer:lib.rs

提案 I inject_reward 内の報酬コインの名前に対するチェックを 削除してください。

2.3.2 ユーザーの清算比率に対する重複したアサーション

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。清算ラインは すでに internal_avaliable_token 関数で考慮されているため、後で user_ratio が清算ラインに達しているかどうかをチェックする必要は ありません。

#[payable]
    pub fn withdraw_token(&mut self, amount: U128) {
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let mut amount = amount.0;

        let token = self.internal_avaliable_token(env::predecessor_account_id());
        let debt = self.get_dept(env::predecessor_account_id());

        log!("token :{} amount: {}", token, amount);
        assert!(token >= amount, "Insufficient avaliable token.");
        if debt.0 - debt.2 == 0 {
            if token - amount < self._min_amount_token() {
                amount = token;
            }
        } else {
            self.assert_user_ratio();
            if token - amount < self._min_amount_token() {
                env::panic(b"Please return all coins first");
            }
        }

Listing 2.32: withdraw_token:lib.rs

提案 I Listing 2.32 の 559 行目にある冗長なアサーションを 削除してください。

2.3.3 冗長なホワイトリストチェック

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。set_reward_speed 関数は assert_param_white 関数を呼び出して権限をチェックします。 一方、set_reward_speed によって呼び出される internal_set_reward_speed は 再度 assert_white を呼び出します。assert_white は assert_param_white と同じホワイトリストを持っています。

pub fn set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
        self.assert_param_white();
        self.internal_set_reward_speed(reward_coin, speed);
    }

Listing 2.33: set_reward_speed:dparam.rs

pub(crate) fn internal_set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
        self.assert_white();
        self.update_index();
        . . .
    }

Listing 2.34: internal_set_reward_speed:pool.rs

提案 I internal_set_reward_speed 関数内の assert_white を 削除してください。

2.3.4 未使用の関数

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。on_inject_reward 関数は他のどの関数からも使用されていません。したがって、削除する ことができます。

#[private]
    pub fn on_inject_reward(&mut self, reward_coin: AccountId, amount: U128) {
        match env::promise_result(0) {
            PromiseResult::NotReady => unreachable!(),
            PromiseResult::Successful(_) => {}
            PromiseResult::Failed => {
                let mut reward_coin_ins = self.internal_get_reward_coin(reward_coin.clone());
                reward_coin_ins.total_reward = reward_coin_ins
                    .total_reward
                    .checked_sub(amount.into())
                    .expect(ERR_ADD);
                self.reward_coins.insert(&reward_coin, &reward_coin_ins);
            }
        };
    }

Listing 2.35: on_inject_reward:pool.rs

提案 I on_inject_reward 関数を削除してください。

2.3.5 冗長なコード

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。 account_allot.get() 関数は、割り当てられた報酬と負債を取得するために 使用されます。set_account_allot 関数内では、この関数の呼び出しは 必要ありません。

pub(crate) fn set_account_allot(&mut self,account_id: AccountId){
        //Update [personally assigned debt, personally assigned pledge] to system value
        let (allot_debt, allot_token) = self.get_account_allot(account_id.clone());
        let token = self.account_token.get(&account_id).expect(ERR_NOT_REGISTER);
        let coin = self.account_coin.get(&account_id).expect(ERR_NOT_REGISTER);

        self.account_allot.get(&account_id);

        self.account_allot.insert(
            &account_id, 
            &AccountAllot{
                account_allot_debt: self.sys_allot_debt,
                account_allot_token: self.sys_allot_token,
            }
        );
        self.account_coin.insert(&account_id, &coin.checked_add(allot_debt).expect(ERR_ADD));
        self.account_token.insert(&account_id, &token.checked_add(allot_token).expect(ERR_ADD));       
    }

Listing 2.36: set_account_allot:allot.rs

提案 I 42 行目の account_allot.get() の呼び出しを削除して ください。

2.3.6 関数名と実装が逆になっている

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。is_stake_paused、 is_redeem_paused、is_claim_reward_paused、is_liquidation_paused、 is_stable_paused 関数は、その機能が一時停止されているかどうかを 表すために定義されています。しかし、特定の属性がライブ状態の場合、 True を返します。

// TODO [OK]
    pub(crate) fn is_stake_paused(&self) -> bool {
        self.stake_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_redeem_paused(&self) -> bool {
        self.redeem_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_claim_reward_paused(&self) -> bool {
        self.claim_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_liquidation_paused(&self) -> bool {
        self.liquidation_live == 1
    }

    // TODO [OK]
    pub(crate) fn is_stable_paused(&self) -> bool {
        self.stable_live == 1
    }

Listing 2.37: is_{stake|redeem|claim_reward|liquidation|stable}_paused:esm.rs

提案 I is_{stake|redeem|claim_reward|liquidation|stable}_paused の 関数名を is_{stake|redeem|claim_reward|liquidation|stable}_live に 変更してください。

2.3.7 冗長なコード

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。 update_stable_fee 関数は、必要なステーブル手数料を更新するために 使用されます。ステーブル手数料はステーキングされたトークンとは 関係ありません。したがって、ユーザーのトークンの残高を変更しても、 ステーブル手数料を更新する必要はありません。

pub(crate) fn deposit_token(&mut self, _amount: u128, _sender_id: ValidAccountId) {
        self.assert_is_poked();
        assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
        let sender_id = AccountId::from(_sender_id);
        assert!(_amount > 0, "Deposit token amount must greater than zero.");

        if let Some(0) = self.guarantee.get(&sender_id) {
            assert!(
                _amount >= self._min_amount_token(),
                "Deposit token amount must greater the minimum deposit token."
            );
        }
        self.update_personal_token(sender_id.clone());
        self.update_stable_fee(sender_id.clone());
        self.set_account_allot(sender_id.clone());
        . . .
    }

Listing 2.38: deposit_token:lib.rs

提案 I 344 行目の update_stable_fee の呼び出しを削除して ください。

2.3.8 計算精度を向上させることができる

Item Description
Status Confirmed and fixed

説明 この問題は Commit-3 以前から存在しています。 internal_user_stable 関数はステーブル手数料を計算することを 目的としています。乗算を除算の前に行うことで、計算精度を 向上させることができます。

pub(crate) fn update_stable_fee(&mut self, account: AccountId) {
        if let Some(mut user_stable) = self.account_stable.get(&account) {
            let allot = self.get_account_allot(account.clone());
            let debt = allot.0;
            let current_block_number = self.to_nano( env::block_timestamp()) as u128;

            let coin = self.account_coin.get(&account).expect(ERR_NOT_REGISTER).checked_add(debt).expect(ERR_ADD);
            let delta_block = current_block_number.checked_sub(user_stable.block).expect(ERR_SUB);
            if delta_block > 0 && coin > 0 {
                let fee = self.stable_fee_rate//16
                        .checked_mul(delta_block).expect(ERR_MUL)
                        .checked_mul(coin).expect(ERR_MUL)//8
                        .checked_div(BLOCK_PER_YEAR).expect(ERR_DIV)
                        .checked_div(self.oin_price).expect(ERR_DIV)//8
                        .checked_div(ONE_COIN).expect(ERR_DIV);//8
                        
                self.saved_stable = self.saved_stable
                        .checked_add(fee).expect(ERR_ADD);

                user_stable.saved_stable = user_stable.saved_stable
                        .checked_add(fee).expect(ERR_ADD); 
            }
            
            user_stable.block = current_block_number;
            self.account_stable.insert(&account, &user_stable);    
            log!("Current stabilization fee: {:?}",self.account_stable.get(&account));
        } else {
            env::panic(b"Not register")
        }
    }

Listing 2.39: update_stable_fee:stablefee.rs

提案 I 25 行目から 30 行目までの計算で、乗算を除算の前に 行ってください。

2.3.9 システムが以前にポークされた価格を記録しない可能性がある

Item Description
Status Confirmed and fixed

説明 この問題は Commit-1 以前から存在しています。この関数は 正しく実装されていません。ほとんどの場合、コントラクトに デポジットされたトークンの総数が 0 より大きいため、システムは ポークされた価格を記録しない可能性があります。

pub fn poke(&mut self, token_price: U128) {
    ...
       if self.total_token > 0 {
           if self.internal_sys_ratio() <= INIT_MIN_RATIO_LINE {
                self.internal_shutdown();
           }
       }else {
            log!(
                "{} poke price {} successfully.",
                env::predecessor_account_id(),
                token_price.0
            );
        }
    }

Listing 2.40: poke:oracle.rs

提案 I トークン価格のポーク動作の記録は、コントラクトに デポジットされたトークンの数に影響されるべきではありません。

2.3.10 清算における担保トークンの不連続な分配

Item Description
Status Confirmed and fixed

説明 この問題は Commit-4 以前から存在しています。ユーザーの ステーキング比率が 108.5% 以上の場合、ユーザーは liquidation_fee を 支払う必要があり、これは allot_debt の 2% を占めます。しかし、 ユーザーのステーキング比率が 108.5% 未満の場合、清算手数料を 支払う必要はありません。これにより、ステーキング比率が大きい ユーザーは、清算後にプールに割り当てるステーキングトークンが 少なくなるという結果になります。

#[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        ...
        if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
            liquidation_fee = _allot_debt
                            .checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
                            .checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
                            .checked_div(self.token_price).expect(ERR_DIV);
        }else{
            allot_ratio = ratio
                .checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
                .checked_add(1).expect(ERR_ADD);
        }
        ...

Listing 2.41: liquidation:lib.rs

提案 I ステーキング比率が 108.5% から 110.5% の間のユーザーに 対しては、清算手数料を(ステーキング比率 - 108.5%)とすることを 提案します。

2.3.11 計算精度の最適化は不要

Item Description
Status Confirmed and fixed

説明 この問題は Commit-4 以前から存在しています。Listing 2.42 の 832 行目で 1 を加算しても、self.gas_compensation_ratio がかなり 大きいため、計算精度を向上させることはできません。

#[payable]
    pub fn liquidation(&mut self, account: AccountId) {
        ...
        if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
            liquidation_fee = _allot_debt
                            .checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
                            .checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
                            .checked_div(self.token_price).expect(ERR_DIV);
        }else{
            allot_ratio = ratio
                .checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
                .checked_add(1).expect(ERR_ADD);
        }
        ...

Listing 2.42: liquidation:lib.rs

提案 I Listing 2.42 の 831 行目に追加された "1" を削除して ください。

2.3.12 集中型設計のリスク

Status Acknowledged

説明 説明 このプロジェクトは高度に集中化された設計を持って います。コントラクトオーナーは非常に高い権限を持っており、 マルチシグ管理者の追加/削除、清算手数料や報酬の引き出しなどが できます。 このような仕組みは完全に集中化されており、すべての トークンに対して完全な支配力を持っています。プロジェクトオーナーは コントラクトを管理するために、コントラクトオーナーの秘密鍵を 保護するセキュリティメカニズムを強化することを強く推奨します。

3. 注意事項と所見

3.1 免責事項

本監査報告書は、投資アドバイスや個人的な推奨事項を構成するものでは ありません。トークン、トークンセール、またはその他の製品、サービス、 その他の資産の潜在的な経済性を考慮したものではなく、そのように 解釈されるべきではありません。いかなる主体も、トークン、製品、 サービス、その他の資産の売買に関する決定を行う目的を含め、いかなる 方法でも本報告書に依拠すべきではありません。

本監査報告書は、特定のプロジェクトやチームを推奨するものではなく、 本報告書はいかなる特定のプロジェクトのセキュリティも保証するもの ではありません。本監査は、スマートコントラクトのすべてのセキュリティ 問題を発見することについていかなる保証も行いません。すなわち、 評価結果は、セキュリティ問題のさらなる発見が存在しないことを 保証するものではありません。1回の監査は包括的であるとは 見なされないため、スマートコントラクトのセキュリティを確保するために、 常に独立した監査と公開のバグバウンティプログラムを実施することを 推奨します。

本監査の範囲は、セクション 1.1 で言及されたコードに限定されます。 明示的に指定されていない限り、言語自体(例えば Rust 言語)、 基盤となるコンパイルツールチェーン、およびコンピューティング インフラストラクチャのセキュリティは対象外です。

3.2 監査手順

私たちは以下の手順に従って監査を実施しています。

  • 脆弱性検出 まず、自動コードアナライザーでスマートコントラクトを スキャンし、その後、報告された問題を手動で検証(却下または確認) します。

  • 意味解析 スマートコントラクトのビジネスロジックを研究し、 自動ファジングツール(私たちの研究チームによって開発)を使用して 可能性のある脆弱性についてさらに調査を行います。また、独立した 監査人と共に、起こりうる攻撃シナリオを手動で分析し、結果を 相互チェックします。

  • 推奨事項 優れたプログラミングプラクティスの観点から、 ガス最適化、コードスタイルなどを含む、開発者にとって有用な アドバイスを提供します。

以下に主な具体的なチェックポイントを示します。

3.2.1 ソフトウェアセキュリティ

  • 再入可能性(Reentrancy)

  • サービス拒否(DoS)

  • アクセス制御

  • データ処理とデータフロー

  • 例外処理

  • 信頼できない外部呼び出しと制御フロー

  • 初期化の一貫性

  • イベント操作

  • エラーを起こしやすい乱数生成

  • プロキシシステムの不適切な使用

3.2.2 DeFi セキュリティ

  • 意味的一貫性

  • 機能的一貫性

  • アクセス制御

  • ビジネスロジック

  • トークン操作

  • 緊急メカニズム

  • オラクルセキュリティ

  • ホワイトリストとブラックリスト

  • 経済的影響

  • 一括転送

3.2.3 NFT セキュリティ

  • 重複アイテム

  • トークン受信者の検証

  • オフチェーンメタデータのセキュリティ

3.2.4 追加の推奨事項

  • ガス最適化

  • コード品質とスタイル

前述のチェックポイントが主なものです。プロジェクトの機能性に応じて、 監査プロセス中にさらに多くのチェックポイントを使用する場合があります。

Sign up for the latest updates
約4.18億ドル損失:Bitget、NEAR Intentsのエクスプロイト | BlockSec Weekly
Security Insights

約4.18億ドル損失:Bitget、NEAR Intentsのエクスプロイト | BlockSec Weekly

本隔週セキュリティレポートは、2026年9月21日から10月4日までの8件のインシデントを記録し、Ethereum、BNB Chain、Solana、Bitcoin、TRON、XRP Ledger、Zcash、Avalanche、NEARなどで約4億1840万ドルの損失が発生。詳細分析では、Bitgetの3.875億ドルのサードパーティ製品脆弱性と、NEAR Intentsの387万ドルの返金検証欠陥および状態ロールバック欠落を取り上げる。

BitgetのオフチェーンでのUSD 3億8,750万ドル流出:鍵とコントラクトを超えて
Security Insights

BitgetのオフチェーンでのUSD 3億8,750万ドル流出:鍵とコントラクトを超えて

2026年9月24日、攻撃者がサードパーティ製セキュリティ製品の脆弱性を悪用し内部認証情報を取得、出金命令を偽造。Ethereum、他のEVMネットワーク、XRP Ledger、Zcash、TRON上のBitget運用ウォレットから約3億8750万ドルが流出。秘密鍵とコールドウォレットは無事。本稿は攻撃経路と資金フロー、即時換金とエコシステムの回復対応を整理し、組織向けの体系的な多層防御策を提案、認可済みブロックチェーン侵入テストによる層間検証手法を解説する。

約1,130万ドル(11.3M)の損失:Multicall Router、Nostra | BlockSec Weekly
Security Insights

約1,130万ドル(11.3M)の損失:Multicall Router、Nostra | BlockSec Weekly

2026/09/14-20のレポート。Ethereum・Starknetで計約1130万ドル被害の2件を分析。1件目はマルチコールルーターが自身を宛先と誤認し、Safeのゲートウェイがルーターの権限で約2,900 aEthrsETHを攻撃者のUniswap v4プールへ流出。2件目はNostraのオラクルが最小1ソースで集約可能なため、3つ中2ソースの平均値がNSTRを約49.52ドルと過大評価し、約350万ドルの借入を許した。

Best Security Auditor for Web3

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

BlockSec Audit