Back to Blog

NearOinDao セキュリティ監査レポート

Code Auditing
December 10, 2021
37 min read

レポートマニフェスト

アイテム 説明
クライアント Oinfinance
ターゲット NearOinDao

バージョン履歴

バージョン 日付 説明
1.0 2021年12月4日 初回リリース

1. はじめに

1.1 ターゲットコントラクトについて

ターゲットコントラクトには、ステーブルコインモジュールが含まれています。その周りには、ステーキングやファーミングなどの他のモジュールも実装されています。これらのモジュールは、USDOというステーブルコインの安定化のための正のフィードバックループを作成します。

情報 説明
タイプ スマートコントラクト
言語 Rust
アプローチ 半自動および手動検証

監査されたリポジトリには、NearOinDao^1 が含まれます。

監査プロセスは反復的です。具体的には、発見された問題に対処するコミットをさらに監査します。新しい問題がある場合は、このプロセスを続行します。したがって、このレポートでは複数のコミットSHA値が参照されています。監査前後のコミットSHA値は以下の通りです。

監査前および監査中

監査後

プロジェクト コミットSHA
NearOinDao 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a

1.2 セキュリティモデル

リスクを評価するために、業界および学術界で広く採用されている標準または提案、OWASPリスク評価方法論^2 および共通脆弱性列挙^3 に従います。したがって、このレポートで測定される深刻度は、未決定 の4つのカテゴリに分類されます。

2. 検出事項

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

  • 高リスク: 19

  • 中リスク: 2

  • 低リスク: 1

  • 推奨事項: 12

詳細は以下のセクションで提供されます。

ID 深刻度 説明 カテゴリ ステータス
1 self.liquidation_line が変更される際のロジックエラー ソフトウェアセキュリティ 確認済みおよび修正済み
2 liquidation 関数が機能しない可能性があります ソフトウェアセキュリティ 確認済みおよび修正済み
3 コントラクトを開く際のタイムスタンプ設定時のロジックエラー ソフトウェアセキュリティ 確認済みおよび修正済み
4 クロスコントラクトトランザクションが失敗した場合、コントラクト状態がロールバックされない ソフトウェアセキュリティ 確認済みおよび修正済み
5 誰でも報酬の残高を追加できます DeFiセキュリティ 確認済みおよび修正済み
6 誰でもステーブルプール報酬の残高を追加できます DeFiセキュリティ 確認済みおよび修正済み
7 誰でも他のユーザーのコインを燃焼できます DeFiセキュリティ 確認済みおよび修正済み
8 誰でも自分のアカウントの残高を追加できます DeFiセキュリティ 確認済みおよび修正済み
9 オラクルは時間間隔をチェックしません DeFiセキュリティ 確認済みおよび修正済み
10 オラクルの時間間隔が長すぎます DeFiセキュリティ 確認済みおよび修正済み
11 Oin価格のオラクルがありません DeFiセキュリティ 確認済みおよび修正済み
12 ユーザーは追加の報酬を得ることができます DeFiセキュリティ 確認済みおよび修正済み
13 ユーザーはより少ないステーブル手数料を支払うことができま DeFiセキュリティ 確認済みおよび修正済み
14 マルチ署名リクエストは比較的低い確認率で確認できます DeFiセキュリティ 確認済みおよび修正済み
15 年間ブロック数が不正確です DeFiセキュリティ 確認済みおよび修正済み
16 利用可能なミントされたコインが正しくありません DeFiセキュリティ 確認済みおよび修正済み
17 ステーブル手数料の支払いがユーザーの預け入れトークンの損失につながる可能性があります DeFiセキュリティ 確認済みおよび修正済み
18 ステーキング比率が不正確です DeFiセキュリティ 確認済みおよび修正済み
19 報酬コインが制限を超える可能性があります DeFiセキュリティ 確認済みおよび修正済み
20 異なる権限を持つユーザーに同じホワイトリスト DeFiセキュリティ 確認済みおよび修正済み
21 ステーブル手数料のアドレスのチェックがありません DeFiセキュリティ 確認済みおよび修正済み
22 報酬コインのtotal_rewardはマルチシグマネージャーによって変更される可能性があります DeFiセキュリティ 確認済みおよび修正済み
23 - 不要なアサーション 推奨事項 確認済みおよび修正済み
24 - 決済ラインの繰り返し検討 推奨事項 確認済みおよび修正済み
25 - 不要なホワイトリストチェック 推奨事項 確認済みおよび修正済み
26 - 未使用の関数 推奨事項 確認済みおよび修正済み
27 - 重複コード 推奨事項 確認済みおよび修正済み
28 - 関数名と実装が矛盾 推奨事項 確認済みおよび修正済み
29 - 重複コード 推奨事項 確認済みおよび修正済み
30 - 計算精度が向上する可能性があります 推奨事項 確認済みおよび修正済み
31 - システムは以前にポークされた価格を記録しない可能性あり 推奨事項 確認済みおよび修正済み
32 - 決済時の担保トークンの不連続な分配 推奨事項 確認済みおよび修正済み
33 - 計算精度の最適化は不要 推奨事項 確認済みおよび修正済み
34 - 中央集権的な設計のリスク 推奨事項 認識済み

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。2つの属性(self.costおよびself.liquidation_line)は、ユーザーの決済ラインという同じコントラクト状態を表します。これらはコントラクトの異なる関数(リスト2.1およびリスト2.2)で使用されます。しかし、self.liquidation_line は set_liquidation_line 関数で変更できますが、self.cost は変更できません。この場合、self.liquidation_line が変更されても、self.cost は元の値を保持します。これは assert_user_ratio 関数(リスト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.");
        }
    }

リスト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");
        ...

リスト2.2: internal_can_mint_amount:lib.rs

影響 ユーザーの決済ラインがコントラクトの異なる関数で一貫していないため、コントラクト全体のロジックに影響します。

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット4以降に導入されました。決済送信者のアカウントとコントラクトオーナーのアカウントが登録されていない可能性があります(リスト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);
    }

}

リスト2.3: personal_liquidation_token:reward.rs

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

提案 I liquidation 関数の先頭で、決済送信者のアカウントとコントラクトオーナーのアカウントの存在をアサートしてください。

2.1.3 潜在的な問題 3: システム起動時にblock_timestampがclosed_timeに保存される

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット3以降に導入されました。env::block_time_stamp() は、internal_open 関数を呼び出す際に 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
        );
    }

リスト2.4: internal_open:esm.rs

影響 コントラクトの起動時間と終了時間が完全に間違っています。時間情報に依存するさらなる更新は、ロジックエラーを引き起こす可能性があります。

提案 I self.opening_time という新しいコントラクト状態を作成し、コントラクトの起動時に env::block_timestamp() をこの値に割り当てることをお勧めします。

2.1.4 潜在的な問題 4: クロス関数呼び出しが失敗した場合にコントラクト状態がロールバックされない

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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");
            }
        }
    }

リスト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");
            }
        }
    }

リスト2.6: liquidation_transfer_callback:ft.rs

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

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

2.2 DeFiセキュリティ

2.2.1 潜在的な問題 5: inject_rewardにアクセス制御がない

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。inject_reward 関数は公開されています。誰でもこの関数を呼び出して、コントラクトの報酬残高を追加できます。

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"
            );
        }
        ...
    }

リスト2.7: inject_reward:pool.rs

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

提案 I この関数は、転送された報酬を受け取った後に内部的に呼び出されるため、プライベート関数に変更する必要があります。

2.2.2 潜在的な問題 6: inject_sp_rewardにアクセス制御がない

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。inject_sp_reward 関数は公開されています。誰でもこの関数を呼び出して、コントラクトのステーブルプール報酬の残高を追加できます。

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
        );
    }

リスト2.8: inject_sp_reward:stablepool.rs

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

提案 I この関数は、転送されたステーブルプール報酬を受け取った後に内部的に呼び出されるため、プライベート関数に変更する必要があります。

2.2.3 潜在的な問題 7: burn_coinにアクセス制御がない

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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);
        self.assert_is_poked();
        self.accured_token(sender_id.clone());
        ...
    }

リスト2.9: burn_coin:lib.rs

影響 誰でもこの関数を使用して誰かのコインを燃焼でき、ユーザーの資産を失うことにつながります。

提案 I この関数は、コインを燃焼するための転送されたステーブル手数料を受け取った後に内部的に呼び出されるため、プライベート関数に変更する必要があります。

2.2.4 潜在的な問題 8: deposit_tokenにアクセス制御がない

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。deposit_token 関数は公開されています。誰でもこの関数を呼び出して、自分のアカウントの残高を追加できます。

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);
        . . .
    }

リスト2.10: deposit_token:lib.rs

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

提案 I この関数は、預け入れられたトークンを受け取った後に内部的に呼び出されるため、プライベート関数に変更する必要があります。

2.2.5 潜在的な問題 9: オラクルに時間のチェックがない

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。oracle.rs の assert_is_poked 関数は、トークン価格がゼロかどうかのみをチェックします。トークン価格は常に変動しているため、これは意味がありません。

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

リスト2.11: assert_is_poked:oracle.rs

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

提案 I コントラクトは、ポークされた価格に対して有効な期間を設定する必要があります。

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット3以降に導入されました。types.rs で定義された定数POKE_INTERVAL_TIMEは、現在1000日を意味します。この時間間隔は長すぎるようです。合理的な値が必要です。

pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;

リスト2.12: types.rs

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

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

2.2.7 潜在的な問題 11: Oin_Priceのアサートの欠如

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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)
    }

リスト2.13: internal_user_stable:lib.rs

影響 オラクルによってポークされた価格の鮮度をチェックせずに、古いOIN価格は価格操作につながる可能性があります。

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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
        }
    }

リスト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)
        }
    }

リスト2.15: staker_debt_of:views.rs

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

提案 I マイニング報酬の計算において、新しく割り当てられた担保の分割を削除してください。マイニング報酬は、ユーザーが預け入れたトークンの金額のみに関連するようにすることができます。

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。あるユーザーが0日目に1000USDOをミントし、その時点でのステーブル手数料率は0.01oin/coin/日であると仮定します。ユーザーが100日目に1000USDOを返却し、過去100日間ステーブル手数料率が変更されなかった場合、支払うべきステーブル手数料は0.01 Oin/coin/day * 1000 Coin * 100 Day = 1000 Oinです。しかし、オーナーが99日目にステーブル手数料率=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);
    }

リスト2.16: set_stable_fee_rate:dparam.rs

pub fn update_stable_index(&mut self) {
    }

リスト2.17: update_stable_index:stablefee.rs

影響 コントラクトユーザーは、ステーブル手数料の支払いが少なくなる可能性があります。

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

2.2.10 潜在的な問題 14: 不合理なマルチ署名リクエスト確認率

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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
    }

リスト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);
        ...
    }

リスト2.19: add_request_only:multisign.rs

影響 コントラクトはリクエスト作成時のマネージャー数のみを考慮するため、マルチ署名リクエストは低い確認率で確認される可能性があります。

提案 I マルチ署名リクエストの確認率を計算する際に、現在のコントラクト状態のマルチ署名ユーザーの数を考慮することを推奨します。

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。NEARプロトコルのメインネットでは1秒ごとにブロックが生成されることを考えると、年間生成ブロック数は31536000(365日)であり、31104000(360日)ではありません。

pub const BLOCK_PER_YEAR: u128 = 31104000;

リスト2.20: types.rs

影響 BLOCK_PER_YEARの定数が不正確であると、定数を使用した計算結果が現実と一致しなくなります。

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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();
        
        ...
    }

リスト2.21: internal_can_mint_amount:lib.rs

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

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

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

アイテム 説明
ステータス 確認済みおよび修正済み (関連ロジックは現在削除されています)

説明 この問題は、コミット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),
            );
        ...
        
    }

リスト2.22: burn_coin:lib.rs

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

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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()
        }
    }

リスト2.23: internal_sys_ratio:lib.rs

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

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。現在、報酬コインが20個ある場合、リスト2.24の131行目のアサートは通過します。この場合、さらに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
        )
    }

リスト2.24: internal_add_reward_coin:pool.rs

影響 追加できる報酬コインの数が、システムの設計と矛盾しています。

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット1以降に導入されました。assert_param_white、assert_white、assert_esm_white、assert_oracle_white 関数は、異なる権限に使用されます。しかし、それらは同じホワイトリストを共有しています。

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

リスト2.25: assert_esm_white:esm.rs

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

リスト2.26: assert_param_white:dparam.rs

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

リスト2.27: assert_oracle_white:oracle.rs

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

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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);

リスト2.28: assert_esm_white:esm.rs

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

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

2.2.18 潜在的な問題 22: 報酬コインのtotal_rewardはマルチシグマネージャーによって変更される可能性がある

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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.");
        }
    }

リスト2.29: ainject_reward:pool.rs

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

2.3 追加の推奨事項

2.3.1 不要なアサーション

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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"
            );
        }

    ...
    }

リスト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;
            }
    ...
    }

リスト2.31: ft_on_transfer:lib.rs

提案 I inject_reward での報酬コイン名のチェックを削除してください。

2.3.2 ユーザーの決済比率に対する繰り返しの断言

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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");
            }
        }

リスト2.32: withdraw_token:lib.rs

提案 I リスト2.32の559行目の不要なアサートを削除してください。

2.3.3 不要なホワイトリストチェック

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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);
    }

リスト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();
        . . .
    }

リスト2.34: internal_set_reward_speed:pool.rs

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

2.3.4 未使用の関数

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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);
            }
        };
    }

リスト2.35: on_inject_reward:pool.rs

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

2.3.5 重複コード

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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));       
    }

リスト2.36: set_account_allot:allot.rs

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

2.3.6 関数名と実装が反対

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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
    }

リスト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 重複コード

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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());
        . . .
    }

リスト2.38: deposit_token:lib.rs

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

2.3.8 計算精度が向上する可能性がある

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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")
        }
    }

リスト2.39: update_stable_fee:stablefee.rs

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

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット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
            );
        }
    }

リスト2.40: poke:oracle.rs

提案 I トークン価格をポークする動作の記録は、コントラクトに預け入れられたトークンの数に影響されないようにする必要があります。

2.3.10 決済時の担保トークンの不連続な分配

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット4以降に導入されました。ユーザーのステーキング比率が108.5%以上の場合、ユーザーはallot_debtの2%であるliquidation_feeを支払う必要があります。しかし、ユーザーのステーキング比率が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);
        }
        ...

リスト2.41: liquidation:lib.rs

提案 I ステーキング比率が108.5%から110.5%の間にあるユーザーの場合、決済手数料は(ステーキング比率 - 108.5%)にすることをお勧めします。

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

アイテム 説明
ステータス 確認済みおよび修正済み

説明 この問題は、コミット4以降に導入されました。リスト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);
        }
        ...

リスト2.42: liquidation:lib.rs

提案 I リスト2.42の831行目の「1」の追加を削除してください。

2.3.12 中央集権的な設計のリスク

ステータス 認識済み

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

3. 通知と注意事項

3.1 免責事項

この監査レポートは、投資アドバイスや個人的な推奨事項を構成するものではありません。トークン、トークンセール、またはその他の製品、サービス、またはその他の資産の潜在的な経済性を考慮しておらず、考慮されるべきでもありません。いかなる法人も、このレポートに、トークン、製品、サービス、またはその他の資産の売買を決定するための目的を含め、いかなる方法でも依存すべきではありません。

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

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

3.2 監査手順

以下の手順に従って監査を行います。

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

  • 意味解析 スマートコントラクトのビジネスロジックを研究し、自動ファジングツール(当社の研究チームによって開発された)を使用して、潜在的な脆弱性についてさらに調査します。また、独立した監査担当者と協力して、可能な攻撃シナリオを手動で分析し、結果を相互照合します。

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

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

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

  • 再入可能性

  • DoS

  • アクセス制御

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

  • 例外処理

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

  • 初期化の一貫性

  • イベント操作

  • エラーが発生しやすいランダム性

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

3.2.2 DeFiセキュリティ

  • 意味の一貫性

  • 機能の一貫性

  • アクセス制御

  • ビジネスロジック

  • トークン操作

  • 緊急メカニズム

  • オラクルセキュリティ

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

  • 経済的影響

  • バッチ転送

3.2.3 NFTセキュリティ

  • 重複アイテム

  • トークン受信者の検証

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

3.2.4 追加の推奨事項

  • ガス最適化

  • コード品質とスタイル

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

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