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

これは、2021年12月にNearOinDaoに対して実施したセキュリティ監査レポートです。

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

レポートマニフェスト

項目 説明
クライアント Oinfinance
ターゲット NearOinDao

バージョン履歴

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

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:Oracleが時間のチェックを欠いている

項目 説明
ステータス 確認済み・修正済み

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

リスト2.16: set_stable_fee_rate:dparam.rs

pub fn update_stable_index(&mut self) {
    }

リスト2.17:update_stable_index:stablefee.rs

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

提案I このコントラクトの報酬コインの計算のように、ステーブル手数料のシステムインデックスを実装します。また、コントラクトユーザーによって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]で装飾されているため、マルチシグネスマネージャーはマルチシグネストレクエストを通じてこの関数を呼び出し、報酬を注入せずにtotal_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"
            );
        }

        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