Berichtsprotokoll
| Artikel | Beschreibung |
|---|---|
| Kunde | Oinfinance |
| Ziel | NearOinDao |
Versionsverlauf
| Version | Datum | Beschreibung |
|---|---|---|
| 1.0 | 04. Dez. 2021 | Erste Veröffentlichung |
1. Einführung
1.1 Über Zielverträge
Die Zielverträge enthalten ein Stablecoin-Modul. Darum herum implementiert es auch andere Module, einschließlich Staking und Farming. Diese Module schaffen eine positive Rückkopplungsschleife für die Stabilisierung des Stablecoins, d. h. USDO.
| Informationen | Beschreibung |
|---|---|
| Typ | Smart Contract |
| Sprache | Rust |
| Ansatz | Semi-automatische und manuelle Verifizierung |
Die auditierten Repositories umfassen NearOinDao ^1
Der Auditprozess ist iterativ. Insbesondere werden wir die Commits, die die Gründungsprobleme beheben, weiter auditieren. Wenn es neue Probleme gibt, werden wir diesen Prozess fortsetzen. Daher werden in diesem Bericht mehrere Commit-SHA-Werte referenziert. Die Commit-SHA-Werte vor und nach dem Audit sind wie folgt dargestellt.
Vor und während des Audits

Nachher
| Projekt | Commit SHA |
|---|---|
| NearOinDao | 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a |
1.2 Sicherheitsmodell
Zur Risikobewertung folgen wir den Standards oder Vorschlägen, die sowohl in der Industrie als auch in der Wissenschaft weit verbreitet sind, darunter die OWASP Risk Rating Methodology ^2 und die Common Weakness Enumeration ^3. Entsprechend sind die in diesem Bericht gemessenen Schweregrade in vier Kategorien eingeteilt: Hoch, Mittel, Niedrig und Unbestimmt.
2. Ergebnisse
Insgesamt finden wir 22 potenzielle Probleme im Smart Contract. Wir haben auch 12 Empfehlungen, wie folgt:
-
Hohes Risiko: 19
-
Mittleres Risiko: 2
-
Geringes Risiko: 1
-
Empfehlungen: 12
Die Details sind in den folgenden Abschnitten aufgeführt.
| ID | Schweregrad | Beschreibung | Kategorie | Status |
|---|---|---|---|---|
| 1 | Hoch | Logikfehler bei der Modifikation von self.liquidation_line | Software Sicherheit | Bestätigt und behoben |
| 2 | Hoch | Die Funktion Liquidation funktioniert möglicherweise nicht | Software Sicherheit | Bestätigt und behoben |
| 3 | Hoch | Logikfehler bei der Einstellung des Zeitstempels für die Öffnung des Vertrags | Software Sicherheit | Bestätigt und behoben |
| 4 | Hoch | Der Vertragsstatus wird nicht zurückgesetzt, wenn die plattformübergreifende Transaktion fehlschlägt | Software Sicherheit | Bestätigt und behoben |
| 5 | Hoch | Jeder kann das Guthaben der Belohnung hinzufügen | DeFi Sicherheit | Bestätigt und behoben |
| 6 | Hoch | Jeder kann das Guthaben der Stable-Pool-Belohnung hinzufügen | DeFi Sicherheit | Bestätigt und behoben |
| 7 | Hoch | Jeder kann die Münzen 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 nicht das Zeitintervall | 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 Stable-Gebühren zahlen | DeFi Sicherheit | Bestätigt und behoben |
| 14 | Mittel | Die Multising-Anfrage kann mit einer relativ niedrigen Bestätigungsrate bestätigt werden | DeFi Sicherheit | Bestätigt und behoben |
| 15 | Mittel | Block pro Jahr ist ungenau | DeFi Sicherheit | Bestätigt und behoben |
| 16 | Hoch | Verfügbare geprägte Münzen sind nicht korrekt | DeFi Sicherheit | Bestätigt und behoben |
| 17 | Hoch | Die Zahlung von Stable-Gebühren kann zum Verlust der eingezahlten Token des Benutzers führen | DeFi Sicherheit | Bestätigt und behoben |
| 18 | Hoch | Falsches Staking-Verhältnis | DeFi Sicherheit | Bestätigt und behoben |
| 19 | Niedrig | Belohnungsmünzen können die Beschränkung überschreiten | DeFi Sicherheit | Bestätigt und behoben |
| 20 | Hoch | Gleiche Whitelist für Benutzer mit unterschiedlichen Berechtigungen | DeFi Sicherheit | Bestätigt und behoben |
| 21 | Hoch | Keine Prüfung der Adresse der Stable-Gebühr | DeFi Sicherheit | Bestätigt und behoben |
| 22 | Hoch | Der Gesamtbetrag der Belohnungsmünze kann von Mult Signaturmanagern geändert werden | DeFi Sicherheit | Bestätigt und behoben |
| 23 | - | Redundante Assertion | Empfehlung | Bestätigt und behoben |
| 24 | - | Wiederholte Berücksichtigung der Liquidationslinie | Empfehlung | Bestätigt und behoben |
| 25 | - | Redundante Whitelist-Prüfung | Empfehlung | Bestätigt und behoben |
| 26 | - | Unbenutzte Funktion | Empfehlung | Bestätigt und behoben |
| 27 | - | Redundanter Code | Empfehlung | Bestätigt und behoben |
| 28 | - | Funktionsname und Implementierung widersprechen sich | Empfehlung | Bestätigt und behoben |
| 29 | - | Redundanter Code | Empfehlung | Bestätigt und behoben |
| 30 | - | Die Berechnungsgenauigkeit kann verbessert werden | Empfehlung | Bestätigt und behoben |
| 31 | - | Das System zeichnet möglicherweise keinen zuvor gepokten Preis auf | Empfehlung | Bestätigt und behoben |
| 32 | - | Diskontinuierliche Verteilung von Collateral-Token bei Liquidation | Empfehlung | Bestätigt und behoben |
| 33 | - | Optimierung der Berechnungsgenauigkeit ist nicht notwendig | Empfehlung | Bestätigt und behoben |
| 34 | - | Das Risiko des zentralisierten Designs | Empfehlung | Bestätigt |
2.1 Softwaresicherheit
2.1.1 Mögliches Problem 1: Zwei verschiedene Attribute für die gleiche Verwendung
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Zwei Attribute (d. h. self.cost und self.liquidation_line) stellen denselben Vertragszustand dar, nämlich die Liquidationslinie des Benutzers. Sie werden in verschiedenen Funktionen des Vertrags verwendet (Listing 2.1 und Listing 2.2). self.liquidation_line kann jedoch mit der Funktion set_liquidation_line geändert werden, während self.cost unverändert bleibt. In diesem Fall behält self.cost den ursprünglichen Wert, wenn self.liquidation_line geändert wird. Dies kann die Logik der Funktion assert_user_ratio (Listing 2.1) beeinflussen.
pub(crate) fn assert_user_ratio(&self) {
let user_ratio = self.internal_user_ratio(env::predecessor_account_id());
if user_ratio != 0 {
assert!(user_ratio >= self.cost, "User ratio less than standard.");
}
}
Listing 2.1: assert_user_ratio:lib.rs
// TODO liquidation
#[payable]
pub fn liquidation(&mut self, account: AccountId) {
assert!(self.is_liquidation_paused(), "{}", SYSTEM_PAUSE);
let ratio = self.internal_user_ratio(account.clone());
assert!(ratio > 0, "No current pledge");
assert!(ratio <= self.liquidation_line, "Not at the clearing line");
...
Listing 2.2: internal_can_mint_amount:lib.rs
Auswirkung Die Liquidationslinie der Benutzer ist in den verschiedenen Funktionen des Vertrags nicht konsistent, was die Logik des gesamten Vertrags beeinflusst.
Vorschlag I Wir können die Verwendung dieser beiden Attribute bei der Berechnung des Staking-Verhältnisses des Benutzers und beim Vergleich mit der Liquidationslinie des Systems vereinheitlichen.
2.1.2 Mögliches Problem 2: Ungültige Verteilung der Liquidationsbelohnung
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-4 eingeführt. Das Konto des Liquidationssenders und das Konto des Vertragseigentümers sind möglicherweise nicht registriert (Zeile 193 und 206 von List. 2.3). In diesem Fall kann die Transaktion nicht erfolgreich ausgeführt werden, wenn der Absender eine Liquidationsaktion durchführen möchte, da die Ausnahme, dass Konten nicht registriert sind, ausgelöst wird.
pub(crate) fn personal_liquidation_token(&mut self, send_id: AccountId, account_id: AccountId, liquidation_gas: Balance, surplus_token: Balance, liquidation_fee: Balance) {
//self.owner_id
let coin_id = ST_NEAR.to_string();
let mut sys_reward_coin = self.internal_get_reward_coin(coin_id.clone());
let account_reward_key_o = self.get_staker_reward_key(send_id.clone(), coin_id.clone());
let user_reward_coin_o = self.internal_get_account_reward(send_id.clone(), coin_id.clone());
self.account_reward.insert(
&account_reward_key_o,
&UserReward {
index: user_reward_coin_o.index,
reward: user_reward_coin_o.reward.checked_add(liquidation_gas).expect(ERR_ADD),
},
);
let account_reward_key_t = self.get_staker_reward_key(account_id.clone(), coin_id.clone());
let user_reward_coin_t = self.internal_get_account_reward(account_id.clone(), coin_id.clone());
if surplus_token > 0 {
self.account_reward.insert(
&account_reward_key_t,
&UserReward {
index: user_reward_coin_t.index,
reward: user_reward_coin_t.reward.checked_add(surplus_token).expect(ERR_ADD),
},
);
}
let account_reward_key_s = self.get_staker_reward_key(self.owner_id.clone(), coin_id.clone());
let user_reward_coin_s = self.internal_get_account_reward(self.owner_id.clone(), coin_id.clone());
self.account_reward.insert(
&account_reward_key_s,
&UserReward {
index: user_reward_coin_s.index,
reward: user_reward_coin_s.reward.checked_add(liquidation_fee).expect(ERR_ADD),
},
);
sys_reward_coin.total_reward = sys_reward_coin
.total_reward
.checked_add(liquidation_gas).expect(ERR_ADD)
.checked_add(liquidation_fee).expect(ERR_ADD)
.checked_add(surplus_token).expect(ERR_ADD);
self.reward_coins.insert(&coin_id, &sys_reward_coin);
}
}
Listing 2.3: personal_liquidation_token:reward.rs
Auswirkung Die Funktion Liquidation kann aufgrund der ausgelösten Ausnahme, dass die Konten nicht registriert sind, nicht erfolgreich ausgeführt werden.
Vorschlag I Überprüfen Sie zu Beginn der Funktion Liquidation die Existenz des Kontos des Liquidationssenders und des Kontos des Vertragseigentümers.
2.1.3 Mögliches Problem 3: Block_timestamp wird beim Öffnen des Systems in closed_time gespeichert
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in 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
);
}
Listing 2.4: internal_open:esm.rs
Auswirkung Die Öffnungs- und Schließzeiten des Vertrags sind völlig falsch. Weitere Aktualisierungen, die von Zeitinformationen abhängen, können zu Logikfehlern führen.
Vorschlag I Wir empfehlen die Erstellung eines neuen Vertragszustands namens self.opening_time und die Zuweisung von env::block_timestamp() zu diesem Wert beim Aufruf der Öffnung des Vertrags.
2.1.4 Mögliches Problem 4: Vertragsstatus wird bei fehlgeschlagenen plattformübergreifenden Funktionsaufrufen nicht zurückgesetzt
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Der Prozess von storage_deposit und ft_transfer kann bei plattformübergreifenden Funktionsaufrufen fehlschlagen. Wir können nicht garantieren, dass die Übertragung immer korrekt durchgefü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");
}
}
}
Listing 2.5: storage_deposit_callback:ft.rs
#[private]
pub fn liquidation_transfer_callback(&mut self) {
match env::promise_result(0) {
PromiseResult::NotReady => unreachable!(),
PromiseResult::Successful(_) => {
log!("Transfer success");
}
PromiseResult::Failed => {
log!("Transfer failed");
}
}
}
Listing 2.6: liquidation_transfer_callback:ft.rs
Auswirkung Benutzer können ihre Vermögenswerte verlieren, wenn Transaktionen fehlschlagen, da die Callback-Funktion den Vertragsstatus nicht zurücksetzt.
Vorschlag I Wir müssen den Vertragsstatus (wenn die Übertragung fehlschlägt) in der Callback-Funktion der plattformübergreifenden Funktionsaufrufe zurücksetzen.
2.2 DeFi-Sicherheit
2.2.1 Mögliches Problem 5: inject_reward fehlt Zugriffskontrolle
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion inject_reward ist öffentlich. Jeder kann diese Funktion aufrufen, um das Guthaben der Belohnung im Vertrag hinzuzufügen.
pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
// self.assert_owner();
if reward_coin == String::from("NEAR") {
assert!(
amount.0 == env::attached_deposit(),
"Amount not equal transfer_amount"
);
}
...
}
Listing 2.7: inject_reward:pool.rs
Auswirkung Jeder kann das Guthaben der Belohnung des Vertrags beliebig hinzufügen.
Vorschlag I Diese Funktion sollte als privat gekennzeichnet werden, da sie intern nach Erhalt der übertragenen Belohnung aufgerufen wird.
2.2.2 Mögliches Problem 6: inject_sp_reward fehlt Zugriffskontrolle
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion inject_sp_reward ist öffentlich. Jeder kann diese Funktion aufrufen, um das Guthaben der Stable-Pool-Belohnung im Vertrag hinzuzufügen.
pub fn inject_sp_reward(&mut self, _amount: U128, sender_id: ValidAccountId) {
self.reward_sp = self.reward_sp + u128::from(_amount);
log!(
"{} add sp_reward {} cur amount{}",
sender_id,
u128::from(_amount),
self.reward_sp
);
}
Listing 2.8: inject_sp_reward:stablepool.rs
Auswirkung Jeder kann das Guthaben der Stable-Pool-Belohnung des Vertrags beliebig hinzufügen.
Vorschlag I Diese Funktion sollte als privat gekennzeichnet werden, da sie intern nach Erhalt der übertragenen Stable-Pool-Belohnung aufgerufen wird.
2.2.3 Mögliches Problem 7: burn_coin fehlt Zugriffskontrolle
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion burn_coin ist öffentlich. Jeder kann diese Funktion aufrufen, um die Münze eines beliebigen Benutzers 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());
...
}
Listing 2.9: burn_coin:lib.rs
Auswirkung Jeder kann diese Funktion nutzen, um die Münze eines beliebigen Benutzers zu verbrennen, was zum Verlust von Vermögenswerten der Benutzer führt.
Vorschlag I Diese Funktion sollte als privat gekennzeichnet werden, da sie intern nach Erhalt der übertragenen Stable-Gebühr zum Verbrennen von Münzen aufgerufen wird.
2.2.4 Mögliches Problem 8: deposit_token fehlt Zugriffskontrolle
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion deposit_token ist öffentlich. Jeder kann diese Funktion aufrufen, um das Guthaben seines Kontos hinzuzufügen.
pub fn deposit_token(&mut self, amount: u128, _sender_id: ValidAccountId) {
self.assert_is_poked();
assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
let _amount = u128::from(amount);
let sender_id = AccountId::from(_sender_id);
. . .
}
Listing 2.10: deposit_token:lib.rs
Auswirkung Angreifer können diese Funktion aufrufen, um das Guthaben ihres Kontos zu erhöhen.
Vorschlag I Diese Funktion sollte als privat gekennzeichnet werden, da sie intern nach Erhalt der eingezahlten Token aufgerufen wird.
2.2.5 Mögliches Problem 9: Orakel fehlt die Prüfung der Zeit
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion assert_is_poked in oracle.rs prüft nur, ob der Wert des Token-Preises Null ist. Dies ist nicht sinnvoll, 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.");
}
Listing 2.11: assert_is_poked:oracle.rs
Auswirkung Dieses Problem betrifft Preisorakel. Wenn der Token-Preis längere Zeit nicht gepokt wurde, kann die Assertion trotzdem bestanden werden und die zugehörige Transaktion mit einem veralteten Preis ausgeführt werden.
Vorschlag I Der Vertrag sollte einen gültigen Zeitraum für den gepokten Preis festlegen.
2.2.6 Mögliches Problem 10: Unangemessener Orakel-Poke-Intervallzeit
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Die in types.rs definierte Konstante POKE_INTERVAL_TIME bedeutet jetzt 1000 Tage. Und dieses Zeitintervall scheint zu lang zu sein. Ein angemessener Wert ist erforderlich.
pub const POKE_INTERVAL_TIME: u64 = 86_400_000_000_000_000;
Listing 2.12: types.rs
Auswirkung Das Zeitintervall für den gepokten Preis ist unangemessen.
Vorschlag I Setzen Sie die Intervallzeit für den gepokten Preis mit einem angemessenen Wert zurück.
2.2.7 Mögliches Problem 11: Fehlende Assertion für Oin_Price
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Diese Funktion prüft nicht, ob der Wert des Oin-Token-Preises gepokt wurde, da die Stable-Gebü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)
}
Listing 2.13: internal_user_stable:lib.rs
Auswirkung Der veraltete OIN-Preis kann zu Preismanipulation führen, ohne die Aktualität des vom Orakel gepokten Preises zu prüfen.
Vorschlag I Fügen Sie vor der Berechnung der Stable-Gebühr des Benutzers eine Assertion self.assert_is_poked() hinzu.
2.2.8 Mögliches Problem 12: Benutzer können durch das Staking von Token mehr Mining-Belohnungen erhalten
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die beanspruchte Belohnung wird nicht korrekt 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 zugeteilte Belohnung aus der Liquidation eines anderen Benutzers ist. Die Liquidation kann jedoch jederzeit von t0 bis t1 erfolgen. Zum Beispiel hat ein Benutzer am Tag 0 100 Token eingezahlt. Am Tag 999 wird die Liquidation für einen anderen Benutzer ausgelöst, sodass account_allot.token auf 1000 ansteigen kann.
Wenn der Benutzer an Tag 1000 seine Belohnung beansprucht, sollten die 1000 Token, die aus der Liquidation am Tag 999 resultieren, nur für einen Tag zum Mining gezählt werden. Der Vertrag berechnet jedoch tatsächlich die Mining-Belohnung für die zugeteilte Belohnung vom Tag 0 bis zum 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
}
}
Listing 2.14: internal_get_saved_reward:views.rs
pub fn staker_debt_of(&self, staker: AccountId) -> (u128, u128) {
if let Some(token) = self.account_token.get(&staker) {
let coin = self.account_coin.get(&staker).expect(ERR_NOT_REGISTER);
let allot = self.get_account_allot(staker.clone());
(token + allot.1, coin + allot.0)
} else {
(0, 0)
}
}
Listing 2.15: staker_debt_of:views.rs
Auswirkung Benutzer können zusätzliche Belohnungen erhalten.
Vorschlag I Entfernen Sie die Aufteilung der neu zugewiesenen Sicherheiten bei der Berechnung der Mining-Belohnung. Wir können die Mining-Belohnung nur mit der vom Benutzer eingezahlten Token-Menge verknüpfen.
2.2.9 Mögliches Problem 13: Benutzer können weniger Stable-Gebühren zahlen
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Angenommen, ein Benutzer prägt am Tag 0 1000 USDOs und der Stable-Gebührensatz beträgt zu dieser Zeit 0,01 Oin/Münze/Tag. Wenn der Benutzer am Tag 100 die 1000 USDOs zurückgibt und sich der Stable-Gebührensatz in den letzten 100 Tagen nicht geändert hat, beträgt die zu zahlende Stable-Gebühr 0,01 Oin/Münze/Tag * 1000 Münzen * 100 Tage = 1000 Oin. Wenn jedoch der Eigentümer am Tag 99 den Stable-Gebührensatz = 0,005 Oin/Münze/Tag festlegt. Zu dieser Zeit muss der Benutzer nur 0,005 Oin/Münze/Tag * 1000 Münzen * 100 Tage = 500 Oin zahlen. Tatsächlich sollte die genaue Gebühr sein: (0,01 Oin/Münze/Tag * 1000 Münzen * 99 Tage) + (0,005 Oin/Münze/Tag * 1000 Münzen * 1 Tag) = 990 Oin + 5 Oin = 995 Oin.
In diesem Fall müssen die 495 Oin nicht von den Benutzern gezahlt 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);
}
Listing 2.16: set_stable_fee_rate:dparam.rs
pub fn update_stable_index(&mut self) {
}
Listing 2.17: update_stable_index:stablefee.rs
Auswirkung Vertragskunden können weniger Stable-Gebühren zahlen.
Vorschlag I Implementieren Sie das Systemindex der Stable-Gebühren wie die Berechnung der Belohnungsmünze in diesem Vertrag. Stellen Sie außerdem sicher, dass das Systemindex der Stable-Gebühren aktualisiert wird, wann immer set_stable_fee_rate, liquidation und update_stable_fee von Vertragskunden aufgerufen werden.
2.2.10 Mögliches Problem 14: Unangemessene Bestätigungsrate für Multising-Anfragen
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Bestätigungsrate für Multising-Anfragen wird anhand der Anzahl der Multising-Manager berechnet, als die Anfrage erstellt wurde. Die Anzahl der Multising-Manager kann sich jedoch später ändern. In diesem Fall, wenn die Anzahl der Manager steigt, kann die Anfrage mit einer niedrigen Bestätigungsrate 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
}
Listing 2.18: is_num_enough:multisign.rs
pub fn add_request_only(&mut self, request: MultiSigRequest) -> RequestId {
self.assert_mul_white();
...
let request_added = MultiSigRequestWithSigner {
signer_pk: env::signer_account_pk(),
added_timestamp: env::block_timestamp(),
confirmed_timestamp: 0,
request: request,
is_executed: false,
cool_down: self.request_cooldown,
mul_white_num: self.mul_white_num(),
num_confirm_ratio: self.num_confirm_ratio,
};
self.requests.insert(&self.request_nonce, &request_added);
...
}
Listing 2.19: add_request_only:multisign.rs
Auswirkung Multising-Anfragen können mit einer niedrigen Bestätigungsrate bestätigt werden, da der Vertrag bei der Erstellung der Anfrage nur die Anzahl der Manager berücksichtigt.
Vorschlag I Verwenden Sie die Anzahl der Multising-Benutzer im aktuellen Vertragszustand, um die Bestätigungsrate von Multising-Anfragen zu berechnen.
2.2.11 Mögliches Problem 15: Ungenaue Anzahl von Blöcken pro Jahr
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Da auf dem NEAR-Protokoll-Mainnet jede Sekunde ein Block generiert wird, sollte die Anzahl der generierten Blöcke pro Jahr 31536000 (365 Tage) und nicht 31104000 (360 Tage) betragen.
pub const BLOCK_PER_YEAR: u128 = 31104000;
Listing 2.20: types.rs
Auswirkung Eine ungenaue Konstante für BLOCK_PER_YEAR führt dazu, dass die Ergebnisse von Berechnungen, die die Konstante verwenden, von der Realität abweichen.
Vorschlag I Ändern Sie BLOCK_PER_YEAR in 31536000.
2.2.12 Mögliches Problem 16: Falsche Berechnung des maximalen USDO-Betrags, der geprägt werden kann
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. allot_token.0 stellt die zugewiesene Schuld dar. Bei der Berechnung des verfügbaren Prägebetrags für USDO sollte die zugewiesene Schuld nicht berücksichtigt werden. Andernfalls kann ein Benutzer mit sehr hoher Schuld eine riesige Menge USDOs prägen.
pub(crate) fn internal_can_mint_amount(&self, account: AccountId) -> u128 {
self.assert_is_poked();
let token = self.account_token.get(&account).expect(ERR_NOT_REGISTER);
let guarantee = self.guarantee.get(&account).expect(ERR_NOT_REGISTER);
let allot_token = self.get_account_allot(account.clone());
let max_usdo = (U256::from(token)
.checked_add(U256::from(allot_token.1))
.expect(ERR_ADD))
.checked_mul(U256::from(self.token_price))
.expect(ERR_MUL)
.checked_div(U256::from(self.liquidation_line))
.expect(ERR_DIV)
.checked_div(U256::from(INIT_STABLE_INDEX))
.expect(ERR_DIV)
.checked_add(U256::from(allot_token.0))
.expect(ERR_ADD)
.checked_sub(U256::from(guarantee))
.unwrap_or(U256::from(0))
.as_u128();
...
}
Listing 2.21: internal_can_mint_amount:lib.rs
Auswirkung Benutzer können zusätzliche USDOs prägen, wenn sie die Funktion mint_coin aufrufen.
Vorschlag I allot_token.0, das die zugewiesene Schuld darstellt, sollte nicht als verfügbarer geprägter USDO berücksichtigt werden.
2.2.13 Mögliches Problem 17: Falsche Handhabung der Stable-Gebühr des Benutzers
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben (Die zugehörige Logik wurde jetzt entfernt) |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Wenn Benutzer die Funktion burn_coin aufrufen, wird die Stable-Gebühr in 'OIN'-Token und nicht in 'ST_NEAR' bezahlt. Der Vertrag reduziert jedoch den Saldo des Staking-Tokens des Benutzers, was nicht korrekt 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),
);
...
}
Listing 2.22: burn_coin:lib.rs
Auswirkung Der Staking-Token der Benutzer kann aufgrund der falschen Handhabung der Stable-Gebühr des Benutzers reduziert werden.
Vorschlag I Verwenden Sie den korrekten Token für die Zahlung der Stable-Gebühren.
2.2.14 Mögliches Problem 18: Falsches Systemverhältnis
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Wenn total_coin = 0 ist, sollte das Verhältnis +∞ sein. Es auf 0 zu setzen, ist falsch.
pub(crate) fn internal_sys_ratio(&self) -> u128 {
self.assert_is_poked();
let token_usd = U256::from(self.total_token)
.checked_mul(U256::from(self.token_price))
.expect(ERR_MUL); /* 32 */
let total_coin = self.total_coin + self.total_guarantee;
if total_coin == 0 {
0
} else {
token_usd
.checked_div(U256::from(STAKE_RATIO_BASE))
.expect(ERR_DIV)
.checked_div(U256::from(total_coin))
.expect(ERR_DIV)
.as_u128()
}
}
Listing 2.23: internal_sys_ratio:lib.rs
Auswirkung Das System wird aufgrund des falschen Verhältnisses wahrscheinlich abgeschaltet.
Vorschlag I Ändern Sie die If-Bedingung total_coin = 0 in token_usd = 0.
2.2.15 Mögliches Problem 19: Die Anzahl der Belohnungsmünzen kann größer als die Obergrenze sein
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Wenn derzeit 20 Belohnungsmünzen vorhanden sind, kann die Assertion in Zeile 131 von Listing 2.24 bestanden werden. In diesem Fall kann eine weitere Belohnungsmünze hinzugefügt werden und die Gesamtzahl der Belohnungsmünzen kann größer als REWARD_UPPER_BOUND sein.
pub(crate) fn internal_add_reward_coin(&mut self, coin: RewardCoin) {
assert!(
self.reward_coins.len() <= REWARD_UPPER_BOUND,
"The currency slot has been used up, please modify other currency information as appropriate",
);
match self.reward_coins.get(&coin.token) {
Some(_) => {
env::panic(b"The current currency has been added, please add a new currency.");
}
None => {}
}
self.reward_coins.insert(&coin.token, &coin);
log!(
"{} add the RewardCoin=> {:?}",
env::predecessor_account_id(),
coin
)
}
Listing 2.24: internal_add_reward_coin:pool.rs
Auswirkung Die zulässige Anzahl der hinzugefügten Belohnungsmünzen steht im Widerspruch zum Design des Systems.
Vorschlag I Ändern Sie die Assertion in self.reward_coins.len() < REWARD_UPPER_BOUND.
2.2.16 Mögliches Problem 20: Benutzer mit unterschiedlichen Berechtigungen verwenden die gleiche Whitelist
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktionen assert_param_white, assert_white, assert_esm_white, assert_oracle_white werden für unterschiedliche Berechtigungen verwendet. Sie teilen sich jedoch dieselbe Whitelist.
pub(crate) fn assert_esm_white(&self) {
self.assert_white()
}
Listing 2.25: assert_esm_white:esm.rs
pub(crate) fn assert_param_white(&self) {
self.assert_white();
}
Listing 2.26: assert_param_white:dparam.rs
pub(crate) fn assert_oracle_white(&self) {
self.assert_white();
}
Listing 2.27: assert_oracle_white:oracle.rs
Auswirkung Benutzer mit unterschiedlichen Berechtigungen teilen sich dieselbe Whitelist.
Vorschlag I Implementieren Sie unterschiedliche Whitelists für Benutzer mit unterschiedlichen Berechtigungen.
2.2.17 Mögliches Problem 21: burn_coin prüft den Token-Typ nicht
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion burn_coin prüft den Token-Typ nicht. In diesem Fall können Angreifer beliebige Token mit dem angegebenen Betrag für die Zahlung der Stable-Gebü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);
Listing 2.28: assert_esm_white:esm.rs
Auswirkung Benutzer müssen kein Oin-Token zahlen. Stattdessen können sie die Stable-Gebühr zahlen, indem sie beliebige Token mit dem erforderlichen Betrag übertragen.
Vorschlag I Überprüfen Sie die Adresse des empfangenen Tokens.
2.2.18 Mögliches Problem 22: Der Gesamtbetrag der Belohnungsmünze kann von Mult-Signaturmanagern geändert werden
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Die Funktion inject_reward ist mit #[private] dekoriert. Daher können Multising-Manager diese Funktion über Multising-Anfragen aufrufen und beliebige Beträge zum Gesamtbetrag der Belohnung hinzufügen, ohne Belohnungen einzuspeisen.
#[payable]
#[private]
pub fn inject_reward(&mut self, amount: U128, reward_coin: AccountId) {
// self.assert_owner();
if reward_coin == String::from("NEAR") {
assert!(
amount.0 == env::attached_deposit(),
"Amount not equal transfer_amount"
);
}
if let Some(reward_coin_ins) = self.get_reward_coin(reward_coin.clone()) {
let mut reward_coin_ins = reward_coin_ins;
reward_coin_ins.total_reward = reward_coin_ins
.total_reward
.checked_add(amount.into())
.expect(ERR_SUB);
self.reward_coins.insert(&reward_coin, &reward_coin_ins);
if reward_coin == String::from("NEAR") {
} else {
log!("Transfer is not required for post-processing");
}
} else {
env::panic(b"No the reward coin.");
}
}
Listing 2.29: ainject_reward:pool.rs
Vorschlag I Entfernen Sie den Dekorator #[private] und ändern Sie die Sichtbarkeit der Funktion inject_reward zu privat.
2.3 Zusätzliche Empfehlung
2.3.1 Redundante Assertion
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-2 eingeführt. Die Funktion inject_reward sollte nur intern von ft_on_transfer aufgerufen werden. Die Adresse der Belohnungsmünze wird in ft_on_transfer geprüft. In diesem Fall brauchen wir den Namen der Belohnungsmünze nicht am Anfang der Funktion inject_reward 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"
);
}
...
}
Listing 2.30: inject_reward:pool.rs
pub fn ft_on_transfer(
&mut self,
sender_id: ValidAccountId,
amount: U128,
msg: String, /* token */
) -> PromiseOrValue<U128> {
...
FtOnTransferArgs::InjectReward => {
assert_eq!(sender_id.to_string(), self.owner_id, "ERR_NOT_ALLOWED");
assert!(
self.reward_coins.get(&token_account_id).is_some(),
"Invalid reward coin"
);
self.inject_reward(amount, token_account_id);
amount_return = 0;
}
...
}
Listing 2.31: ft_on_transfer:lib.rs
Vorschlag I Entfernen Sie die Prüfung des Namens der Belohnungsmünze in inject_reward.
2.3.2 Wiederholte Assertion für das Liquidationsverhältnis des Benutzers
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Liquidationslinie wird bereits in der Funktion internal_avaliable_token berücksichtigt, daher ist es nicht notwendig, später 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");
}
}
Listing 2.32: withdraw_token:lib.rs
Vorschlag I Entfernen Sie die redundante Assertion in Zeile 559 von Listing 2.32.
2.3.3 Redundante Whitelist-Prüfung
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion set_reward_speed ruft die Funktion assert_param_white auf, um die Berechtigung zu prüfen. Inzwischen ruft internal_set_reward_speed, die von set_reward_speed aufgerufen wird, assert_white erneut 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);
}
Listing 2.33: set_reward_speed:dparam.rs
pub(crate) fn internal_set_reward_speed(&mut self, reward_coin: AccountId, speed: U128) {
self.assert_white();
self.update_index();
. . .
}
Listing 2.34: internal_set_reward_speed:pool.rs
Vorschlag I Entfernen Sie assert_white innerhalb der Funktion internal_set_reward_speed.
2.3.4 Unbenutzte Funktion
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Die Funktion on_inject_reward wird von keiner anderen Funktion verwendet. Daher kann sie 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);
}
};
}
Listing 2.35: on_inject_reward:pool.rs
Vorschlag I Entfernen Sie die Funktion on_inject_reward.
2.3.5 Redundanter Code
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Die Funktion account_allot.get() wird verwendet, um die zugewiesene Belohnung und Schuld zu erhalten. 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));
}
Listing 2.36: set_account_allot:allot.rs
Vorschlag I Entfernen Sie den Aufruf account_allot.get() in Zeile 42.
2.3.6 Der Funktionsname und die Implementierung sind entgegengesetzt
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in 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 darstellen, ob die Funktion pausiert ist oder nicht. Wenn das spezifische Attribut jedoch aktiv 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
}
Listing 2.37: is_{stake|redeem|claim_reward|liquidation|stable}_paused:esm.rs
Vorschlag I Ändern Sie den Funktionsnamen von is_{stake|redeem|claim_reward|liquidation|stable}_paused in is_{stake|redeem|claim_reward|liquidation|stable}_live.
2.3.7 Redundanter Code
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Die Funktion update_stable_fee dient zur Aktualisierung der erforderlichen Stable-Gebühren. Stable-Gebühren stehen nicht im Zusammenhang mit den gestakten Token. Daher muss die Änderung des Guthabens des Tokens für Benutzer die Stable-Gebühren nicht aktualisieren.
pub(crate) fn deposit_token(&mut self, _amount: u128, _sender_id: ValidAccountId) {
self.assert_is_poked();
assert!(self.is_stake_paused(), "{}", SYSTEM_PAUSE);
let sender_id = AccountId::from(_sender_id);
assert!(_amount > 0, "Deposit token amount must greater than zero.");
if let Some(0) = self.guarantee.get(&sender_id) {
assert!(
_amount >= self._min_amount_token(),
"Deposit token amount must greater the minimum deposit token."
);
}
self.update_personal_token(sender_id.clone());
self.update_stable_fee(sender_id.clone());
self.set_account_allot(sender_id.clone());
. . .
}
Listing 2.38: deposit_token:lib.rs
Vorschlag I Entfernen Sie den Aufruf update_stable_fee in Zeile 344.
2.3.8 Die Berechnungsgenauigkeit kann verbessert werden
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-3 eingeführt. Die Funktion internal_user_stable dient zur Berechnung der Stable-Gebühr. Die Berechnungsgenauigkeit kann durch Multiplikation vor Division verbessert werden.
pub(crate) fn update_stable_fee(&mut self, account: AccountId) {
if let Some(mut user_stable) = self.account_stable.get(&account) {
let allot = self.get_account_allot(account.clone());
let debt = allot.0;
let current_block_number = self.to_nano( env::block_timestamp()) as u128;
let coin = self.account_coin.get(&account).expect(ERR_NOT_REGISTER).checked_add(debt).expect(ERR_ADD);
let delta_block = current_block_number.checked_sub(user_stable.block).expect(ERR_SUB);
if delta_block > 0 && coin > 0 {
let fee = self.stable_fee_rate//16
.checked_mul(delta_block).expect(ERR_MUL)
.checked_mul(coin).expect(ERR_MUL)//8
.checked_div(BLOCK_PER_YEAR).expect(ERR_DIV)
.checked_div(self.oin_price).expect(ERR_DIV)//8
.checked_div(ONE_COIN).expect(ERR_DIV);//8
self.saved_stable = self.saved_stable
.checked_add(fee).expect(ERR_ADD);
user_stable.saved_stable = user_stable.saved_stable
.checked_add(fee).expect(ERR_ADD);
}
user_stable.block = current_block_number;
self.account_stable.insert(&account, &user_stable);
log!("Current stabilization fee: {:?}",self.account_stable.get(&account));
} else {
env::panic(b"Not register")
}
}
Listing 2.39: update_stable_fee:stablefee.rs
Vorschlag I Führen Sie die Multiplikation vor der Division für die Berechnung von Zeile 25 bis 30 durch.
2.3.9 Das System zeichnet möglicherweise keinen zuvor gepokten Preis auf
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-1 eingeführt. Die Funktion ist nicht korrekt implementiert. Das System zeichnet möglicherweise keinen gepokten Preis auf, da die Anzahl der im Vertrag eingezahlten Gesamttokens 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
);
}
}
Listing 2.40: poke:oracle.rs
Vorschlag I Die Aufzeichnung des Verhaltens des Pokings 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
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in 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 kleiner als 108,5 % ist, muss er keine liquidation_fee zahlen. Dies führt dazu, dass ein Benutzer mit einem höheren Staking-Verhältnis nach der Liquidation möglicherweise weniger Staking-Token an den Pool zuweist.
#[payable]
pub fn liquidation(&mut self, account: AccountId) {
...
if ratio >= INIT_NO_LIQUIDATION_FEE_RATE {
liquidation_fee = _allot_debt
.checked_mul(self.liquidation_fee_ratio).expect(ERR_MUL)
.checked_mul(STAKE_RATIO_BASE).expect(ERR_MUL)//16
.checked_div(self.token_price).expect(ERR_DIV);
}else{
allot_ratio = ratio
.checked_sub(self.gas_compensation_ratio).expect(ERR_SUB)
.checked_add(1).expect(ERR_ADD);
}
...
Listing 2.41: liquidation:lib.rs
Vorschlag I Für Benutzer, deren Staking-Verhältnis zwischen 108,5 % und 110,5 % liegt, wird die Liquidationsgebühr als (Staking-Verhältnis - 108,5 %) vorgeschlagen.
2.3.11 Optimierung der Berechnungsgenauigkeit ist nicht notwendig
| Artikel | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde vor oder in Commit-4 eingeführt. Das Hinzufügen von 1 in Zeile 832 von Listing 2.42 kann die Berechnungsgenauigkeit 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);
}
...
Listing 2.42: liquidation:lib.rs
Vorschlag I Entfernen Sie die hinzugefügte "1" in Zeile 831 von Listing 2.42.
2.3.12 Das Risiko des zentralisierten Designs
Status Bestätigt
Beschreibung Beschreibung Das Projekt hat ein stark zentralisiertes Design. Der Vertragseigentümer verfügt über sehr hohe Privilegien, die Multising-Manager hinzufügen/löschen und die Liquidationsgebühr und Belohnungen abheben können usw. Ein solcher Mechanismus ist absolut zentralisiert und hat die vollständige Kontrolle über alle Token. Wir empfehlen dringend, dass der Projekteigentümer Sicherheitsmechanismen zur Sicherung der privaten Schlüssel des Vertragseigentümers zur Verwaltung der Verträge durchsetzt.
3. Hinweise und Bemerkungen
3.1 Haftungsausschluss
Dieser Auditbericht stellt keine Anlageberatung oder persönliche Empfehlung dar. Er berücksichtigt nicht die potenziellen wirtschaftlichen Aspekte eines Tokens, eines Token-Verkaufs oder eines anderen Produkts, einer Dienstleistung oder eines anderen Vermögenswerts und sollte auch nicht als solche interpretiert werden. Keine Stelle sollte sich in irgendeiner Weise auf diesen Bericht verlassen, auch nicht zur Entscheidungsfindung beim Kauf oder Verkauf von Token, Produkten, Dienstleistungen oder anderen Vermögenswerten.
Dieser Auditbericht ist keine Befürwortung eines bestimmten Projekts oder Teams, und der Bericht garantiert nicht die Sicherheit eines bestimmten Projekts. Diese Prüfung gibt keine Gewähr für die Entdeckung aller Sicherheitsprobleme der Smart Contracts, d. h. das Prüfungsergebnis garantiert nicht die Nichtexistenz weiterer Funde von Sicherheitsproblemen. Da eine Prüfung nicht als umfassend angesehen werden kann, empfehlen wir immer, unabhängige Prüfungen und ein öffentliches Bug-Bounty-Programm durchzuführen, um die Sicherheit von Smart Contracts zu gewährleisten.
Der Umfang dieser Prüfung ist auf den in Abschnitt 1.1 genannten Code beschränkt. Sofern nicht ausdrücklich angegeben, sind die Sicherheit der Sprache selbst (z. B. die Rust-Sprache), die zugrundeliegende Kompilierungskette und die Recheninfrastruktur vom Umfang ausgeschlossen.
3.2 Prüfverfahren
Wir führen die Prüfung nach folgendem Verfahren durch.
-
Schwachstellenerkennung Wir scannen Smart Contracts zunächst mit automatischen Code-Analysatoren und verifizieren dann manuell (verwerfen oder bestätigen) die von ihnen gemeldeten Probleme.
-
Semantische Analyse Wir untersuchen die Geschäftslogik von Smart Contracts und führen weitere Untersuchungen zu möglichen Schwachstellen mit einem automatischen Fuzzing-Tool (entwickelt von unserem Forschungsteam) durch. Wir analysieren auch manuell mögliche Angriffszenarien mit unabhängigen Prüfern, um die Ergebnisse zu überprüfen.
-
Empfehlung Wir geben Entwicklern aus der Perspektive guter Programmierpraktiken nützliche Ratschläge, einschließlich Gasoptimierung, Code-Stil usw.
Die wichtigsten konkreten Prüfpunkte zeigen wir im Folgenden.
3.2.1 Softwaresicherheit
-
Reentrancy
-
DoS
-
Zugriffskontrolle
-
Datenverarbeitung und Datenfluss
-
Ausnahmebehandlung
-
Nicht vertrauenswürdiger externer Aufruf und Kontrollfluss
-
Konsistenz der Initialisierung
-
Ereignisoperation
-
Fehleranfällige Zufälligkeit
-
Unsachgemäße Verwendung des Proxy-Systems
3.2.2 DeFi-Sicherheit
-
Semantische Konsistenz
-
Funktionskonsistenz
-
Zugriffskontrolle
-
Geschäftslogik
-
Token-Operation
-
Notfallmechanismus
-
Orakelsicherheit
-
Whitelist und Blacklist
-
Wirtschaftliche Auswirkungen
-
Batch-Übertragung
3.2.3 NFT-Sicherheit
-
Dupliziertes Element
-
Verifizierung des Token-Empfängers
-
Sicherheit von Off-Chain-Metadaten
3.2.4 Zusätzliche Empfehlung
-
Gasoptimierung
-
Codequalität und -stil
::: Hinweis Die vorherigen Prüfpunkte sind die wichtigsten. Wir können während des Prüfprozesses weitere Prüfpunkte gemäß der Funktionalität des Projekts verwenden. :::



