Back to Blog

Sicherheitsaudit-Bericht für NearOinDao

Code Auditing
December 10, 2021
33 min read

Bericht-Manifest

Element Beschreibung
Kunde Oinfinance
Ziel NearOinDao

Versionsverlauf

Version Datum Beschreibung
1.0 04. Dez. 2021 Erste Veröffentlichung

1. Einleitung

1.1 Über die Ziel-Verträge

Die Ziel-Verträge enthalten ein Stablecoin-Modul. Darum herum implementieren sie weitere Module, einschließlich Staking und Farming. Diese Module erzeugen eine positive Rückkopplungsschleife zur Stabilisierung des Stablecoins, d. h. USDO.

Information Beschreibung
Typ Smart Contract
Sprache Rust
Ansatz Halbautomatische und manuelle Überprüfung

Die Repositories, die geprüft wurden, beinhalten NearOinDao ^1

Der Prüfungsprozess ist iterativ. Insbesondere werden wir die Commits, die die gefundenen Probleme beheben, weiter prüfen. Sollten neue Probleme auftreten, werden wir diesen Prozess fortsetzen. Daher werden in diesem Bericht mehrere Commit-SHA-Werte referenziert. Die Commit-SHA-Werte vor und nach der Prüfung sind im Folgenden dargestellt.

Vor und während der Prüfung

Nachher

Projekt Commit SHA
NearOinDao 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a

1.2 Sicherheitsmodell

Zur Risikobewertung folgen wir Standards oder Vorschlägen, die sowohl in der Industrie als auch in der Wissenschaft weit verbreitet sind, einschließlich der OWASP Risk Rating Methodology ^2 und der Common Weakness Enumeration ^3. Dementsprechend ist die in diesem Bericht gemessene Schwere in vier Kategorien unterteilt: Hoch, Mittel, Niedrig und Unbestimmt.

2. Ergebnisse

Insgesamt haben wir 22 potenzielle Probleme im Smart Contract gefunden. Wir haben zudem 12 Empfehlungen wie folgt:

  • Hohes Risiko: 19

  • Mittleres Risiko: 2

  • Niedriges Risiko: 1

  • Empfehlungen: 12

Die Details werden in den folgenden Abschnitten bereitgestellt.

ID Schweregrad Beschreibung Kategorie Status
1 Hoch Logikfehler bei der Änderung von self.liquidation_line Softwaresicherheit Bestätigt und behoben
2 Hoch Funktion liquidation funktioniert möglicherweise nicht Softwaresicherheit Bestätigt und behoben
3 Hoch Logikfehler beim Setzen des Zeitstempels zum Öffnen des Vertrags Softwaresicherheit Bestätigt und behoben
4 Hoch Vertragsstatus nicht zurückgesetzt bei gescheiterter Cross-Contract-Transaktion Softwaresicherheit Bestätigt und behoben
5 Hoch Jeder kann das Belohnungsguthaben hinzufügen DeFi-Sicherheit Bestätigt und behoben
6 Hoch Jeder kann das Belohnungsguthaben des stabilen Pools hinzufügen DeFi-Sicherheit Bestätigt und behoben
7 Hoch Jeder kann die Coins anderer Benutzer verbrennen DeFi-Sicherheit Bestätigt und behoben
8 Hoch Jeder kann das Guthaben seines Kontos hinzufügen DeFi-Sicherheit Bestätigt und behoben
9 Hoch Orakel prüft das Zeitintervall nicht DeFi-Sicherheit Bestätigt und behoben
10 Hoch Orakel-Zeitintervall ist zu lang DeFi-Sicherheit Bestätigt und behoben
11 Hoch Kein Orakel für Oin-Preis DeFi-Sicherheit Bestätigt und behoben
12 Hoch Benutzer können zusätzliche Belohnungen erhalten DeFi-Sicherheit Bestätigt und behoben
13 Hoch Benutzer können weniger Stabilitätsgebühr zahlen DeFi-Sicherheit Bestätigt und behoben
14 Mittel Multi-Sign-Anfrage kann mit relativ niedrigem Bestätigungsverhältnis bestätigt werden DeFi-Sicherheit Bestätigt und behoben
15 Mittel Blocknummer pro Jahr ist ungenau DeFi-Sicherheit Bestätigt und behoben
16 Hoch Verfügbare gemintete Coins sind nicht korrekt DeFi-Sicherheit Bestätigt und behoben
17 Hoch Zahlung der Stabilitätsgebühr kann zum Verlust hinterlegter Token führen DeFi-Sicherheit Bestätigt und behoben
18 Hoch Inkorrektes Staking-Verhältnis DeFi-Sicherheit Bestätigt und behoben
19 Niedrig Belohnungs-Coins können Begrenzung überschreiten DeFi-Sicherheit Bestätigt und behoben
20 Hoch Gleiche Whitelist für Benutzer mit unterschiedlichen Privilegien DeFi-Sicherheit Bestätigt und behoben
21 Hoch Keine Prüfung der Adresse der Stabilitätsgebühr DeFi-Sicherheit Bestätigt und behoben
22 Hoch total_reward der Belohnungs-Coins kann von Multi-Signatur-Managern geändert werden DeFi-Sicherheit Bestätigt und behoben
23 - Überflüssige Assertion Empfehlung Bestätigt und behoben
24 - Wiederholte Berücksichtigung der Liquidationslinie Empfehlung Bestätigt und behoben
25 - Überflüssige Whitelist-Prüfung Empfehlung Bestätigt und behoben
26 - Nicht verwendete Funktion Empfehlung Bestätigt und behoben
27 - Redundanter Code Empfehlung Bestätigt und behoben
28 - Funktionsname und Implementierung stehen im Widerspruch Empfehlung Bestätigt und behoben
29 - Redundanter Code Empfehlung Bestätigt und behoben
30 - Rechenpräzision kann verbessert werden Empfehlung Bestätigt und behoben
31 - System zeichnet möglicherweise zuvor abgefragten Preis nicht auf Empfehlung Bestätigt und behoben
32 - Diskontinuierliche Verteilung von Collateral-Token bei Liquidation Empfehlung Bestätigt und behoben
33 - Optimierung der Rechenpräzision nicht notwendig Empfehlung Bestätigt und behoben
34 - Risiko durch zentralisiertes Design Empfehlung Anerkannt

2.1 Softwaresicherheit

2.1.1 Potenzielles Problem 1: Zwei verschiedene Attribute für die gleiche Verwendung

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Zwei Attribute (d.h. self.cost und self.liquidation_line) repräsentieren den gleichen Vertragsstatus, nämlich die Liquidationslinie des Benutzers. Sie werden in verschiedenen Funktionen des Vertrags verwendet (Auflistung 2.1 und 2.2). Allerdings kann self.liquidation_line mit der Funktion set_liquidation_line geändert werden, während self.cost nicht geändert werden kann. In diesem Fall, wenn self.liquidation_line geändert wird, behält self.cost den ursprünglichen Wert bei. Dies kann die Logik der Funktion assert_user_ratio beeinflussen (Auflistung 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.");
        }
    }

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

Auflistung 2.2: internal_can_mint_amount:lib.rs

Auswirkung Die Liquidationslinie der Benutzer ist in verschiedenen Funktionen des Vertrags inkonsistent, was die Logik des gesamten Vertrags beeinflusst.

Vorschlag I Wir können die Verwendung dieser beiden Attribute vereinheitlichen, wenn das Staking-Verhältnis des Benutzers berechnet und mit der Liquidationslinie des Systems verglichen wird.

2.1.2 Potenzielles Problem 2: Ungültige Verteilung der Liquidationsbelohnung

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-4 eingeführt. Das Konto des Liquidationssenders und das Konto des Vertragsinhabers sind möglicherweise nicht registriert (Zeile 193 und 206 von Auflistung 2.3). In diesem Fall kann die Transaktion, wenn der Sender eine Liquidationsaktion durchführen möchte, aufgrund der ausgelösten Ausnahme, dass die Konten nicht registriert sind, nicht erfolgreich ausgeführt werden.

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

}

Auflistung 2.3: personal_liquidation_token:reward.rs

Auswirkung Die Liquidationsfunktion kann aufgrund der ausgelösten Ausnahme, dass die Konten nicht registriert sind, nicht erfolgreich ausgeführt werden.

Vorschlag I Überprüfen Sie den Bestand des Kontos des Liquidationssenders und des Vertragsinhabers zu Beginn der Liquidationsfunktion.

2.1.3 Potenzielles Problem 3: Block_timestamp wird beim Öffnen des Systems in closed_time gespeichert

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. env::block_time_stamp() sollte nicht in self.closed_time gespeichert werden, wenn die Funktion internal_open aufgerufen wird.

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

Auflistung 2.4: internal_open:esm.rs

Auswirkung Die Öffnungszeit und die Schließzeit des Vertrags sind völlig falsch. Weitere Aktualisierungen, die von Zeitinformationen abhängen, können Logikfehler aufweisen.

Vorschlag I Wir schlagen vor, einen neuen Vertragsstatus namens self.opening_time zu erstellen und env::block_timestamp() diesem Wert zuzuweisen, während das Öffnen des Vertrags aufgerufen wird.

2.1.4 Potenzielles Problem 4: Vertragsstatus wird nicht zurückgesetzt, wenn Cross-Contract-Aufrufe fehlschlagen

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Der Prozess von storage_deposit und ft_transfer kann während der Cross-Contract-Funktionsaufrufe fehlschlagen. Wir können nicht garantieren, dass der Transfer immer korrekt ausgeführt wird. Die Callback-Funktion setzt den Vertragsstatus nicht zurück, wenn der Aufruf fehlschlägt.

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

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

Auflistung 2.6: liquidation_transfer_callback:ft.rs

Auswirkung Benutzer könnten ihre Vermögenswerte verlieren, wenn Transaktionen fehlschlagen, da die Callback-Funktion den Vertragsstatus nicht zurücksetzt.

Vorschlag I Wir müssen den Vertragsstatus (wenn der Transfer fehlschlägt) in der Callback-Funktion der Cross-Contract-Funktionsaufrufe zurücksetzen.

2.2 DeFi-Sicherheit

2.2.1 Potenzielles Problem 5: inject_reward fehlt Zugriffskontrolle

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion inject_reward ist öffentlich. Jeder kann diese Funktion aufrufen, um das Belohnungsguthaben im Vertrag zu erhöhen.

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

Auflistung 2.7: inject_reward:pool.rs

Auswirkung Jeder kann ein beliebiges Guthaben auf die Belohnung des Vertrags hinzufügen.

Vorschlag I Diese Funktion sollte in eine private geändert werden, da sie intern aufgerufen wird, nachdem die übertragene Belohnung empfangen wurde.

2.2.2 Potenzielles Problem 6: inject_sp_reward fehlt Zugriffskontrolle

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion inject_sp_reward ist öffentlich. Jeder kann diese Funktion aufrufen, um das Guthaben der stabilen Pool-Belohnung im Vertrag zu erhöhen.

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

Auflistung 2.8: inject_sp_reward:stablepool.rs

Auswirkung Jeder kann ein beliebiges Guthaben auf die stabile Pool-Belohnung des Vertrags hinzufügen.

Vorschlag I Diese Funktion sollte in eine private geändert werden, da sie intern aufgerufen wird, nachdem die übertragene stabile Pool-Belohnung empfangen wurde.

2.2.3 Potenzielles Problem 7: burn_coin fehlt Zugriffskontrolle

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion burn_coin ist öffentlich. Jeder kann diese Funktion aufrufen, um die Coins von jemand anderem zu verbrennen.

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

Auflistung 2.9: burn_coin:lib.rs

Auswirkung Jeder kann diese Funktion nutzen, um Coins von jemand anderem zu verbrennen, was zum Verlust von Vermögenswerten der Benutzer führt.

Vorschlag I Diese Funktion sollte in eine private geändert werden, da sie intern aufgerufen wird, nachdem die übertragene Stabilitätsgebühr zum Verbrennen von Coins empfangen wurde.

2.2.4 Potenzielles Problem 8: deposit_token fehlt Zugriffskontrolle

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion deposit_token ist öffentlich. Jeder kann diese Funktion aufrufen, um das Guthaben seines Kontos zu erhöhen.

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

Auflistung 2.10: deposit_token:lib.rs

Auswirkung Angreifer können diese Funktion aufrufen, um das Guthaben ihres Kontos zu erhöhen.

Vorschlag I Diese Funktion sollte in eine private geändert werden, da sie intern aufgerufen wird, nachdem die hinterlegten Token empfangen wurden.

2.2.5 Potenzielles Problem 9: Orakel fehlt Zeitprüfung

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion assert_is_poked in oracle.rs prüft nur, ob der Wert des Token-Preises null ist. Das ergibt keinen Sinn, da sich der Token-Preis ständig ändert.

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

Auflistung 2.11: assert_is_poked:oracle.rs

Auswirkung Dieses Problem betrifft Preis-Orakel. Wenn der Token-Preis für eine lange Zeit nicht aktualisiert (poked) wurde, kann die Assertion immer noch bestanden werden, und die zugehörige Transaktion kann mit einem veralteten Preis ausgeführt werden.

Vorschlag I Der Vertrag sollte ein gültiges Zeitfenster für den abgefragten Preis festlegen.

2.2.6 Potenzielles Problem 10: Unangemessenes Orakel-Abfrageintervall

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die in types.rs definierte Konstante POKE_INTERVAL_TIME bedeutet derzeit 1000 Tage. Dieses Zeitintervall scheint zu lang zu sein. Ein angemessener Wert ist erforderlich.

pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;

Auflistung 2.12: types.rs

Auswirkung Das Zeitintervall für abgefragte Preise ist unangemessen.

Vorschlag I Setzen Sie das Intervall für abgefragte Preise auf einen angemessenen Wert zurück.

2.2.7 Potenzielles Problem 11: Fehlende Assertion für Oin_Price

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Diese Funktion prüft nicht, ob der Wert des oin_token Preises abgefragt wurde, da die Stabilitätsgebühr des Benutzers durch self.oin_price berechnet wird.

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

Auflistung 2.13: internal_user_stable:lib.rs

Auswirkung Der veraltete OIN-Preis kann zu Preismanipulation führen, ohne die Aktualität des vom Orakel abgefragten Preises zu prüfen.

Vorschlag I Fügen Sie eine self.assert_is_poked(); Assertion vor der Berechnung der Stabilitätsgebühr des Benutzers hinzu.

2.2.8 Potenzielles Problem 12: Benutzer erhalten möglicherweise mehr Mining-Belohnungen durch Staking von Token

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die beanspruchte Belohnung wird nicht genau berechnet. Die Funktion internal_get_saved_reward wird aufgerufen, um die spezifische Mining-Belohnung des Benutzers von t0 bis t1 mit der folgenden Formel zu berechnen:

Beachten Sie, dass account_allot.token die Sicherheiten-Belohnung ist, die durch die Liquidation eines anderen Benutzers hinzugefügt wurde. Jedoch kann eine Liquidation jederzeit von t0 bis t1 auftreten. Zum Beispiel hat ein Benutzer am Tag 0 100 Token hinterlegt. Am Tag 999 wird eine Liquidation für einen anderen Benutzer ausgelöst, sodass account_allot.token auf 1000 steigen könnte.

Wenn der Benutzer seine Belohnung am Tag 1000 beansprucht, sollten die 1000 Token, die aus der Liquidation am Tag 999 resultieren, nur für einen Tag für das Mining gezählt werden. Der Vertrag berechnet jedoch tatsächlich die Mining-Belohnung für die Sicherheiten-Belohnung von Tag 0 bis Tag 1000.

// 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
        }
    }

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

Auflistung 2.15: staker_debt_of:views.rs

Auswirkung Benutzer können zusätzliche Belohnungen erhalten.

Vorschlag I Entfernen Sie die Partitionierung der neu zugewiesenen Sicherheiten bei der Berechnung der Mining-Belohnung. Wir können die Mining-Belohnung nur auf die Menge der vom Benutzer eingezahlten Token beziehen.

2.2.9 Potenzielles Problem 13: Benutzer zahlen möglicherweise weniger Stabilitätsgebühr

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Angenommen, ein Benutzer mint 1000 USDO am Tag 0, und die stable_fee_rate zu diesem Zeitpunkt beträgt 0,01 oin/coin/tag. Wenn der Benutzer die 1000 USDO am Tag 100 zurückgibt und sich die stable_fee_rate während der letzten 100 Tage nicht ändert, beträgt die zu zahlende Stabilitätsgebühr 0,01 oin/coin/tag * 1000 Coin * 100 Tage = 1000 Oin. Wenn der Eigentümer jedoch am Tag 99 die stable_fee_rate auf 0,005 oin/coin/tag setzt, muss der Benutzer nur 0,005 oin/coin/tag * 1000 Coin * 100 Tage = 500 Oin zahlen. Tatsächlich sollte die korrekte Gebühr sein: (0,01 oin/coin/tag * 1000 Coin * 99 Tage) + (0,005 oin/coin/tag * 1000 Coin * 1 Tag) = 990 Oin + 5 Oin = 995 Oin.

In diesem Fall müssen die 495 Oin nicht von den Benutzern bezahlt werden.

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

Auflistung 2.16: set_stable_fee_rate:dparam.rs

pub fn update_stable_index(&mut self) {
    }

Auflistung 2.17: update_stable_index:stablefee.rs

Auswirkung Vertragsbenutzern wird möglicherweise weniger Stabilitätsgebühr in Rechnung gestellt.

Vorschlag I Implementieren Sie das System-Index der Stabilitätsgebühr wie die Berechnung von reward_coin in diesem Vertrag. Stellen Sie sicher, dass das System-Index der Stabilitätsgebühr aktualisiert wird, wann immer set_stable_fee_rate, Liquidation und update_stable_fee von Vertragsbenutzern abgerufen werden.

2.2.10 Potenzielles Problem 14: Unangemessene Bestätigungsrate für Multi-Signatur-Anfragen

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Bestätigungsrate für Multi-Signatur-Anfragen wird durch die Anzahl der Multi-Signatur-Manager berechnet, als die Anfrage erstellt wurde. Die Anzahl der Multi-Signatur-Manager kann sich jedoch später ändern. Wenn die Anzahl der Manager steigt, kann die Anfrage mit einem niedrigen Bestätigungsverhältnis bestätigt werden.

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
    }

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

Auflistung 2.19: add_request_only:multisign.rs

Auswirkung Multi-Signatur-Anfragen könnten mit einer niedrigen Bestätigungsrate bestätigt werden, da der Vertrag nur die Anzahl der Manager berücksichtigt, als die Anfrage erstellt wurde.

Vorschlag I Erwägen Sie die Verwendung der Anzahl der Multi-Signatur-Benutzer im aktuellen Vertragsstatus, um die Bestätigungsrate für Multi-Signatur-Anfragen zu berechnen.

2.2.11 Potenzielles Problem 15: Inkorrekte Blocknummer pro Jahr

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Da auf dem NEAR-Mainnet jede Sekunde ein Block generiert wird, sollte die generierte Blocknummer pro Jahr 31536000 (365 Tage) betragen, anstatt 31104000 (360 Tage).

pub const BLOCK_PER_YEAR: u128 = 31104000;

Auflistung 2.20: types.rs

Auswirkung Eine ungenaue Konstante für BLOCK_PER_YEAR macht die Ergebnisse von Berechnungen inconsistent mit der Realität.

Vorschlag I Ändern Sie die BLOCK_PER_YEAR auf 31536000.

2.2.12 Potenzielles Problem 16: Inkorrekte Berechnung der maximalen USDO-Mint-Menge

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. allot_token.0 repräsentiert die zugewiesene Schuld. Bei der Berechnung der verfügbaren Mint-Menge für USDO sollte die zugewiesene Schuld nicht mitgezählt werden. Andernfalls könnte ein Benutzer mit sehr hohen Schulden eine riesige Menge an USDO minten.

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

Auflistung 2.21: internal_can_mint_amount:lib.rs

Auswirkung Benutzer können zusätzliche USDOs minten, wenn sie die Funktion mint_coin aufrufen.

Vorschlag I Die allot_token.0, die die zugewiesene Schuld repräsentiert, sollte nicht als verfügbare gemintete USDOs gezählt werden.

2.2.13 Potenzielles Problem 17: Inkorrekter Umgang mit der Stabilitätsgebühr des Benutzers

Element Beschreibung
Status Bestätigt und behoben (die zugehörige Logik wurde entfernt)

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Wenn Benutzer die Funktion burn_coin aufrufen, wird die Stabilitätsgebühr mit dem 'OIN' Token anstelle von 'ST_NEAR' bezahlt. Der Vertrag reduziert jedoch das Guthaben des Staking-Tokens des Benutzers, was nicht genau ist.

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

Auflistung 2.22: burn_coin:lib.rs

Auswirkung Das Staking-Token der Benutzer kann aufgrund des inkorrekten Umgangs mit der Stabilitätsgebühr reduziert werden.

Vorschlag I Verwenden Sie das korrekte Token zur Zahlung der Stabilitätsgebühren.

2.2.14 Potenzielles Problem 18: Inkorrektes Systemverhältnis

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Wenn total_coin = 0, sollte das Verhältnis +∞ sein. Es auf 0 zu setzen, ist inkorrekt.

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

Auflistung 2.23: internal_sys_ratio:lib.rs

Auswirkung Das System wird wahrscheinlich aufgrund des inkorrekten Verhältnisses herunterfahren.

Vorschlag I Ändern Sie die if-Bedingung auf total_coin = 0.

2.2.15 Potenzielles Problem 19: Die Anzahl der Belohnungs-Coins kann den oberen Wert überschreiten

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Wenn es aktuell 20 Belohnungs-Coins gibt, kann die Assertion in Zeile 131 von Auflistung 2.24 bestanden werden. In diesem Fall kann ein weiterer Belohnungs-Coin hinzugefügt werden, und die Gesamtzahl der Belohnungs-Coins kann den REWARD_UPPER_BOUND überschreiten.

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

Auflistung 2.24: internal_add_reward_coin:pool.rs

Auswirkung Die verfügbare Anzahl von Belohnungs-Coins steht im Widerspruch zum Systemdesign.

Vorschlag I Ändern Sie die Assertion in self.reward_coins.len() < REWARD_UPPER_BOUND.

2.2.16 Potenzielles Problem 20: Benutzer mit unterschiedlichen Privilegien verwenden dieselbe Whitelist

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktionen assert_param_white, assert_white, assert_esm_white, assert_oracle_white werden für unterschiedliche Privilegien verwendet. Sie teilen sich jedoch dieselbe Whitelist.

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

Auflistung 2.25: assert_esm_white:esm.rs

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

Auflistung 2.26: assert_param_white:dparam.rs

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

Auflistung 2.27: assert_oracle_white:oracle.rs

Auswirkung Benutzer mit unterschiedlichen Privilegien teilen sich dieselbe Whitelist.

Vorschlag I Implementieren Sie unterschiedliche Whitelists für Benutzer mit unterschiedlichen Privilegien.

2.2.17 Potenzielles Problem 21: burn_coin prüft den Token-Typ nicht

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion burn_coin prüft den Token-Typ nicht. In diesem Fall können Angreifer beliebige Token in spezifizierter Menge zur Zahlung der Stabilitätsgebühr übertragen.

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

Auflistung 2.28: assert_esm_white:esm.rs

Auswirkung Benutzer müssen den Oin-Token nicht verwenden. Stattdessen können sie die Stabilitätsgebühr durch Transferieren eines beliebigen Tokens mit der erforderlichen Menge zahlen.

Vorschlag I Prüfen Sie die Adresse des empfangenen Tokens.

2.2.18 Potenzielles Problem 22: total_reward der Belohnungs-Coins kann von Multi-Signatur-Managern modifiziert werden

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktion inject_reward ist mit #[private] dekoriert. Daher können Multi-Signatur-Manager diese Funktion durch Multi-Signatur-Anfragen aufrufen und einen beliebigen Betrag der Gesamtbelohnung hinzufügen, ohne die Belohnung tatsächlich einzuzahlen.

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

Auflistung 2.29: inject_reward:pool.rs

Vorschlag I Entfernen Sie den Dekorator #[private] und ändern Sie die Sichtbarkeit der Funktion inject_reward in privat.

2.3 Zusätzliche Empfehlung

2.3.1 Überflüssige Assertion

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-2 eingeführt. Die Funktion inject_reward sollte nur intern von ft_on_transfer aufgerufen werden. Die Adresse des Belohnungs-Tokens wird bereits in ft_on_transfer geprüft. In diesem Fall ist es nicht notwendig, den Namen des Belohnungs-Tokens zu Beginn der Funktion inject_reward erneut zu prüfen.

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

    ...
    }

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

Auflistung 2.31: ft_on_transfer:lib.rs

Vorschlag I Entfernen Sie die Prüfung auf den Namen des Belohnungs-Tokens in inject_reward.

2.3.2 Wiederholte Assertion für das Liquidationsverhältnis des Benutzers

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Liquidationslinie wird bereits in der Funktion internal_avaliable_token berücksichtigt, daher ist es nicht nötig, später erneut zu prüfen, ob das user_ratio die Liquidationslinie erreicht hat.

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

Auflistung 2.32: withdraw_token:lib.rs

Vorschlag I Entfernen Sie die überflüssige Assertion in Zeile 559 der Auflistung 2.32.

2.3.3 Überflüssige Whitelist-Prüfung

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion set_reward_speed ruft die Funktion assert_param_white auf, um das Privileg zu prüfen. Unterdessen ruft internal_set_reward_speed, das von set_reward_speed aufgerufen wird, erneut assert_white auf. assert_white hat dieselbe Whitelist wie 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);
    }

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

Auflistung 2.34: internal_set_reward_speed:pool.rs

Vorschlag I Entfernen Sie assert_white innerhalb der Funktion internal_set_reward_speed.

2.3.4 Nicht verwendete Funktion

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktion on_inject_reward wird von keiner anderen Funktion verwendet. Sie kann daher entfernt werden.

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

Auflistung 2.35: on_inject_reward:pool.rs

Vorschlag I Entfernen Sie die Funktion on_inject_reward.

2.3.5 Redundanter Code

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktion account_allot.get() wird verwendet, um die zugewiesene Belohnung und Schuld abzurufen. Innerhalb der Funktion set_account_allot ist der Aufruf dieser Funktion nicht erforderlich.

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

Auflistung 2.36: set_account_allot:allot.rs

Vorschlag I Entfernen Sie den Aufruf account_allot.get() in Zeile 42.

2.3.6 Funktionsname und Implementierung sind gegensätzlich

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktionen is_stake_paused, is_redeem_paused, is_claim_reward_paused, is_liquidation_paused, is_stable_paused sind so definiert, dass sie repräsentieren, ob die Funktion pausiert ist oder nicht. Wenn jedoch das spezifische Attribut live ist, gibt es True zurück.

// 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
    }

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

Vorschlag I Ändern Sie den Funktionsnamen der is_{stake|redeem|claim_reward|liquidation|stable}paused Funktionen in is{stake|redeem|claim_reward|liquidation|stable}_live.

2.3.7 Redundanter Code

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktion update_stable_fee wird verwendet, um die erforderlichen Stabilitätsgebühren zu aktualisieren. Stabilitätsgebühren stehen nicht im Zusammenhang mit den hinterlegten Token. Daher ist es nicht nötig, die Stabilitätsgebühren zu aktualisieren, wenn das Token-Guthaben der Benutzer geändert wird.

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

Auflistung 2.38: deposit_token:lib.rs

Vorschlag I Entfernen Sie den Aufruf update_stable_fee in Zeile 344.

2.3.8 Die Rechenpräzision kann verbessert werden

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktion internal_user_stable zielt darauf ab, die Stabilitätsgebühr zu berechnen. Die Rechenpräzision kann verbessert werden, indem die Multiplikation vor der Division durchgeführt wird.

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

Auflistung 2.39: update_stable_fee:stablefee.rs

Vorschlag I Führen Sie die Multiplikation vor der Division für die Berechnung von Zeile 25 bis 30 durch.

2.3.9 System zeichnet möglicherweise zuvor abgefragten Preis nicht auf

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion ist nicht korrekt implementiert. Das System zeichnet den abgefragten Preis möglicherweise nicht auf, da die Anzahl der im Vertrag hinterlegten Token in den meisten Fällen größer als 0 ist.

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

Auflistung 2.40: poke:oracle.rs

Vorschlag I Die Aufzeichnung des Verhaltens des Abfragens des Token-Preises sollte nicht von der Anzahl der im Vertrag hinterlegten Token beeinflusst werden.

2.3.10 Diskontinuierliche Verteilung von Collateral-Token bei Liquidation

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-4 eingeführt. Wenn das Staking-Verhältnis des Benutzers größer oder gleich 108,5 % ist, müssen Benutzer die liquidation_fee zahlen, die 2 % der allot_debt beträgt. Wenn das Staking-Verhältnis des Benutzers jedoch unter 108,5 % liegt, muss er/sie keine Liquidationsgebühr zahlen. Dies führt dazu, dass Benutzer mit einem höheren Staking-Verhältnis nach der Liquidation weniger Staking-Token im Pool zuteilen können.

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

Auflistung 2.41: liquidation:lib.rs

Vorschlag I Für Benutzer, deren Staking-Verhältnis zwischen 108,5 % und 110,5 % liegt, wird empfohlen, die Liquidationsgebühr als (Staking-Verhältnis - 108,5 %) zu berechnen.

2.3.11 Optimierung der Rechenpräzision nicht notwendig

Element Beschreibung
Status Bestätigt und behoben

Beschreibung Dieses Problem wurde in oder vor Commit-4 eingeführt. Das Hinzufügen von 1 in Zeile 832 in Auflistung 2.42 kann die Rechenpräzision nicht erhöhen, da self.gas_compensation_ratio ziemlich groß ist.

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

Auflistung 2.42: liquidation:lib.rs

Vorschlag I Entfernen Sie die hinzugefügte "1" in Zeile 831 der Auflistung 2.42.

2.3.12 Risiko durch zentralisiertes Design

Status Anerkannt

Beschreibung Das Projekt hat ein hoch zentralisiertes Design. Der Vertragsinhaber hat sehr hohe Privilegien, kann Multi-Signatur-Manager hinzufügen/löschen und kann die Liquidationsgebühr und Belohnung usw. abheben. Ein solcher Mechanismus ist absolut zentralisiert und hat eine vollständige Kontrolle über alle Token. Wir empfehlen dringend, dass der Projekteigentümer Sicherheitsmechanismen durchsetzt, um die privaten Schlüssel des Vertragsinhabers zur Verwaltung der Verträge zu schützen.

3. Hinweise und Anmerkungen

3.1 Haftungsausschluss

Dieser Prüfbericht stellt keine Anlageberatung oder persönliche Empfehlung dar. Er berücksichtigt nicht und sollte nicht so interpretiert werden, dass er die potenzielle Wirtschaftlichkeit eines Tokens, eines Token-Verkaufs oder eines anderen Produkts, einer Dienstleistung oder eines anderen Vermögenswerts berücksichtigt oder irgendeinen Einfluss darauf hat. Jede Einheit sollte sich in keiner Weise auf diesen Bericht verlassen, einschließlich für den Zweck, Entscheidungen zum Kauf oder Verkauf eines Tokens, Produkts, einer Dienstleistung oder eines anderen Vermögenswerts zu treffen.

Dieser Prüfbericht ist keine Billigung eines bestimmten Projekts oder Teams, und der Bericht garantiert nicht die Sicherheit eines bestimmten Projekts. Diese Prüfung gibt keine Garantien, dass alle Sicherheitsprobleme der Smart Contracts entdeckt wurden, d. h. das Bewertungsergebnis garantiert nicht das Nichtvorhandensein weiterer gefundener Sicherheitsprobleme. Da eine Prüfung nicht als umfassend angesehen werden kann, empfehlen wir immer, unabhängige Prüfungen und ein öffentliches Bug-Bounty-Programm fortzusetzen, um die Sicherheit der Smart Contracts zu gewährleisten.

Der Umfang dieser Prüfung ist auf den in Abschnitt 1.1 genannten Code begrenzt. Sofern nicht explizit angegeben, ist die Sicherheit der Sprache selbst (z. B. die Rust-Sprache), die zugrunde liegende Kompilierungs-Toolchain und die IT-Infrastruktur außerhalb des Umfangs.

3.2 Vorgehensweise bei der Prüfung

Wir führen die Prüfung nach folgendem Verfahren durch.

  • Schwachstellenerkennung Wir scannen zuerst Smart Contracts mit automatischen Code-Analysatoren und verifizieren dann manuell (Ablehnung oder Bestätigung) die von ihnen gemeldeten Probleme.

  • Semantische Analyse Wir untersuchen die Geschäftslogik von Smart Contracts und führen weitere Untersuchungen zu möglichen Schwachstellen unter Verwendung eines automatischen Fuzzing-Tools durch (entwickelt von unserem Forschungsteam). Wir analysieren auch manuell mögliche Angriffsszenarien mit unabhängigen Prüfern, um das Ergebnis gegenzuprüfen.

  • Empfehlung Wir geben Entwicklern nützliche Ratschläge aus der Perspektive bewährter Programmierpraktiken, einschließlich Gas-Optimierung, Codestil usw.

Die wichtigsten konkreten Checkpunkte zeigen wir im Folgenden.

3.2.1 Softwaresicherheit

  • Wiedereintritt (Reentrancy)

  • DoS

  • Zugriffskontrolle

  • Datenverarbeitung und Datenfluss

  • Ausnahmebehandlung

  • Nicht vertrauenswürdiger externer Aufruf und Kontrollfluss

  • Initialisierungskonsistenz

  • Ereignis-Operation

  • Fehleranfällige Zufälligkeit

  • Unsachgemäße Verwendung des Proxy-Systems

3.2.2 DeFi-Sicherheit

  • Semantische Konsistenz

  • Funktionale Konsistenz

  • Zugriffskontrolle

  • Geschäftslogik

  • Token-Operation

  • Notfallmechanismus

  • Orakel-Sicherheit

  • Whitelist und Blacklist

  • Wirtschaftliche Auswirkungen

  • Batch-Transfer

3.2.3 NFT-Sicherheit

  • Duplizierte Elemente

  • Verifizierung des Token-Empfängers

  • Off-Chain-Metadaten-Sicherheit

3.2.4 Zusätzliche Empfehlung

  • Gas-Optimierung

  • Codequalität und Stil

Die vorherigen Checkpunkte sind die wichtigsten. Wir können während des Prüfungsprozesses je nach Funktionalität des Projekts weitere Checkpunkte verwenden.

Best Security Auditor for Web3

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

BlockSec Audit