Bericht Manifest
| Punkt | Beschreibung |
|---|---|
| Kunde | Oinfinance |
| Ziel | NearOinDao |
Versionshistorie
| Version | Datum | Beschreibung |
|---|---|---|
| 1.0 | 04. Dez. 2021 | Erste Veröffentlichung |
1. Einleitung
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 zur Stabilisierung des Stable Coins, d. h. USDO.
| Information | Beschreibung |
|---|---|
| Typ | Smart Contract |
| Sprache | Rust |
| Ansatz | Halbautomatische und manuelle Verifizierung |
Die geprüften Repositories umfassen NearOinDao ^1
Der Prüfungsprozess ist iterativ. Insbesondere werden die Commits weiter geprüft, die die Gründungsfehler beheben. Wenn neue Probleme auftreten, wird dieser Prozess fortgesetzt. Daher werden in diesem Bericht mehrere Commit-SHA-Werte referenziert. Die Commit-SHA-Werte vor und nach der Prüfung sind im Folgenden aufgeführt.
Vor und während der Prüfung

Danach
| Projekt | Commit SHA |
|---|---|
| NearOinDao | 3bd117606c753d3c2f66b6dcddd1ae18ea47a20a |
1.2 Sicherheitsmodell
Zur Risikobewertung folgen wir den Standards oder Vorschlägen, die sowohl von Industrie als auch von der akademischen Welt weitgehend übernommen werden, einschließlich der OWASP Risk Rating Methodology ^2 und 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 außerdem 12 Empfehlungen, wie folgt:
-
Hohes Risiko: 19
-
Mittleres Risiko: 2
-
Niedriges Risiko: 1
-
Empfehlungen: 12
Die Details werden in den folgenden Abschnitten aufgeführt.
| ID | Schweregrad | Beschreibung | Kategorie | Status |
|---|---|---|---|---|
| 1 | Hoch | Logikfehler bei der Änderung von self.liquidation_line | Software-Sicherheit | Bestätigt und behoben |
| 2 | Hoch | Funktion liquidation funktioniert möglicherweise nicht | Software-Sicherheit | Bestätigt und behoben |
| 3 | Hoch | Logikfehler bei der Festlegung des Zeitstempels für die Öffnung des Vertrags | Software-Sicherheit | Bestätigt und behoben |
| 4 | Hoch | Vertragsstatus wird bei fehlgeschlagener externer Vertrags-Transaktion nicht zurückgesetzt | 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 | Oracle prüft nicht das Zeitintervall | DeFi-Sicherheit | Bestätigt und behoben |
| 10 | Hoch | Oracle-Zeitintervall ist zu lang | DeFi-Sicherheit | Bestätigt und behoben |
| 11 | Hoch | Kein Oracle 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ühr zahlen | DeFi-Sicherheit | Bestätigt und behoben |
| 14 | Mittel | Die Multi-Signatur-Anfrage kann mit einer relativ niedrigen Bestätigungsrate bestätigt werden | DeFi-Sicherheit | Bestätigt und behoben |
| 15 | Mittel | Blockzahl pro Jahr ist ungenau | DeFi-Sicherheit | Bestätigt und behoben |
| 16 | Hoch | Verfügbare geprägte Münzen sind nicht richtig | DeFi-Sicherheit | Bestätigt und behoben |
| 17 | Hoch | Zahlung der Stable-Gebühr kann zum Verlust der hinterlegten 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 Grenze ü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 | Die Gesamtbelohnung der Belohnungsmünze kann von Multi-Signatur-Managern geändert werden | DeFi-Sicherheit | Bestätigt und behoben |
| 23 | - | Redundante Behauptung | Empfehlung | Bestätigt und behoben |
| 24 | - | Wiederholte Berücksichtigung der Liquidationsgrenze | 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 | - | Berechnungspräzision kann verbessert werden | Empfehlung | Bestätigt und behoben |
| 31 | - | System erfasst möglicherweise keinen zuvor abgefragten Preis | Empfehlung | Bestätigt und behoben |
| 32 | - | Diskontinuierliche Verteilung von Collateral-Token bei Liquidation | Empfehlung | Bestätigt und behoben |
| 33 | - | Optimierung der Berechnungspräzision ist nicht notwendig | Empfehlung | Bestätigt und behoben |
| 34 | - | Risiko des zentralisierten Designs | Empfehlung | Bestätigt |
2.1 Software-Sicherheit
2.1.1 Potenzielles Problem 1: Zwei verschiedene Attribute für denselben Zweck
| Punkt | 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 denselben Vertragsstatus, nämlich die Liquidationsgrenze 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 Liquidationsgrenze der Benutzer ist in den verschiedenen Funktionen des Vertrags inkonsistent, 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 Liquidationsgrenze des Systems vereinheitlichen.
2.1.2 Potenzielles Problem 2: Ungültige Verteilung der Liquidationsbelohnung
| Punkt | 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 Vertragseigentümers sind möglicherweise nicht registriert (Zeile 193 und 206 von Liste 2.3). In diesem Fall kann die Transaktion, wenn der Sender eine Liquidationsaktion durchführen möchte, aufgrund der ausgelösten Ausnahme, dass 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);
}
}
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 Stellen Sie die Existenz des Kontos des Liquidationssenders und des Vertragseigentümers am Anfang der Funktion liquidation sicher.
2.1.3 Potenzielle Problem 3: Block_timestamp wird beim Öffnen des Systems in closed_time gespeichert
| Punkt | 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
);
}
Listing 2.4: internal_open:esm.rs
Auswirkung Die Öffnungs- und Schließzeit des Vertrags sind vollständig 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, wenn der Vertrag geöffnet wird.
2.1.4 Potenzielle Problem 4: Vertragsstatus wird bei fehlgeschlagenen externen Funktionsaufrufen nicht zurückgesetzt
| Punkt | 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 externer Vertragsaufrufe 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 externen Vertragsaufrufe zurücksetzen.
2.2 DeFi-Sicherheit
2.2.1 Potenzielles Problem 5: inject_reward hat keine Zugriffskontrolle
| Punkt | 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 Guthaben der Belohnung 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"
);
}
...
}
Listing 2.7: inject_reward:pool.rs
Auswirkung Jeder kann beliebige Guthaben auf die Belohnung des Vertrags hinzufügen.
Vorschlag I Diese Funktion sollte als privat behandelt werden, da sie intern nach Erhalt der übertragenen Belohnung aufgerufen wird.
2.2.2 Potenzielles Problem 6: inject_sp_reward hat keine Zugriffskontrolle
| Punkt | 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 Stable-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
);
}
Listing 2.8: inject_sp_reward:stablepool.rs
Auswirkung Jeder kann beliebige Guthaben auf die Stable-Pool-Belohnung des Vertrags hinzufügen.
Vorschlag I Diese Funktion sollte als privat behandelt werden, da sie intern nach Erhalt der übertragenen Stable-Pool-Belohnung aufgerufen wird.
2.2.3 Potenzielles Problem 7: burn_coin hat keine Zugriffskontrolle
| Punkt | 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 Münzen 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 verwenden, um die Münzen eines beliebigen Benutzers zu verbrennen, was zu dem Verlust von Benutzervermögen führt.
Vorschlag I Diese Funktion sollte als privat behandelt werden, da sie intern nach Erhalt der übertragenen Stable-Gebühr zum Verbrennen von Münzen aufgerufen wird.
2.2.4 Potenzielles Problem 8: deposit_token hat keine Zugriffskontrolle
| Punkt | 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);
. . .
}
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 behandelt werden, da sie intern nach Erhalt der eingezahlten Token aufgerufen wird.
2.2.5 Potenzielles Problem 9: Oracle prüft die Zeit nicht
| Punkt | 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 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 Preis-Oracles. Wenn der Token-Preis lange Zeit nicht abgefragt wurde, kann die Behauptung trotzdem erfolgreich sein und die zugehörige Transaktion mit einem veralteten Preis ausgeführt werden.
Vorschlag I Der Vertrag sollte einen gültigen Zeitraum für den abgefragten Preis festlegen.
2.2.6 Potenzielles Problem 10: Unangemessener Oracle-Poke-Intervall
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Konstante POKE_INTERVAL_TIME, die in types.rs definiert ist, bedeutet derzeit 1000 Tage. Dieses Zeitintervall scheint zu lang. 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 abgefragten Preis ist unangemessen.
Vorschlag I Setzen Sie das Intervall für den abgefragten Preis auf einen angemessenen Wert zurück.
2.2.7 Potenzielles Problem 11: Fehlende Überprüfung für Oin_Price
| Punkt | 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 Stable-Gebühr des Benutzers mit 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 Preismanipulationen führen, ohne die Aktualität des vom Oracle abgefragten Preises zu prüfen.
Vorschlag I Fügen Sie eine Assertion self.assert_is_poked() vor der Berechnung der Stable-Gebühr des Benutzers hinzu.
2.2.8 Potenzielles Problem 12: Benutzer können mehr Mining-Belohnungen mit Staking-Token erhalten
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die beanspruchte Belohnung wird nicht korrekt berechnet. Die Funktion internal_get_saved_reward wird verwendet, 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 ist, die durch die Liquidation eines anderen Benutzers hinzugefügt wurde. Die Liquidation kann jedoch jederzeit zwischen t0 und t1 erfolgen. Zum Beispiel hat ein Benutzer am Tag 0 100 Token eingezahlt. Am Tag 999 wurde die Liquidation für einen anderen Benutzer ausgelöst, sodass account_allot.token auf 1000 ansteigen kann.
Wenn der Benutzer am 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 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 Partitionierung 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 Potenzielles Problem 13: Benutzer zahlen möglicherweise weniger Stable-Gebühr
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Angenommen, ein Benutzer prägt am Tag 0 1000 USDOs und der stable_fee_rate beträgt zu diesem Zeitpunkt 0,01 Oin/Münze/Tag. Wenn der Benutzer die 1000 USDOs am Tag 100 zurückgibt und sich der stable_fee_rate 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 stable_fee_rate = 0,005 oin/Münze/Tag am Tag 99 festlegt. Zu diesem Zeitpunkt 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 betragen: (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 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);
}
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 berechnet bekommen.
Vorschlag I Implementieren Sie den Stable-Gebührensystemindex ähnlich der Berechnung der Belohnungsmünze in diesem Vertrag. Und stellen Sie sicher, dass der Stable-Gebührensystemindex immer dann aktualisiert wird, wenn set_stable_fee_rate, liquidation und update_stable_fee von Vertragskunden aufgerufen werden.
2.2.10 Potenzielles Problem 14: Unangemessene Bestätigungsrate für Multi-Signatur-Anfragen
| Punkt | 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 anhand der Anzahl der Multi-Signatur-Manager zum Zeitpunkt der Erstellung der Anfrage berechnet. Die Anzahl der Multi-Signatur-Manager kann sich jedoch später ändern. In diesem Fall kann die Anfrage mit einer niedrigen Bestätigungsrate bestätigt werden, wenn die Anzahl der Manager steigt.
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 Multi-Signatur-Anfragen können mit einer niedrigen Bestätigungsrate bestätigt werden, da der Vertrag nur die Anzahl der Manager berücksichtigt, wenn die Anfrage erstellt wird.
Vorschlag I Erwägen Sie die Verwendung der Anzahl der Multi-Signatur-Benutzer im aktuellen Vertragsstatus zur Berechnung der Bestätigungsrate für Multi-Signatur-Anfragen.
2.2.11 Potenzielles Problem 15: Falsche Blockanzahl pro Jahr
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Da auf dem NEAR-Protokoll-Mainnet jede Sekunde ein Block generiert wird, sollte die generierte Blockanzahl 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 auf 31536000.
2.2.12 Potenzielles Problem 16: Falsche Berechnung der maximalen USDO-Prägung
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. allot_token.0 repräsentiert die zugeteilte Schuld. Bei der Berechnung des verfügbaren Prägebetrags für USDO sollte die zugeteilte Schuld nicht berücksichtigt werden. Andernfalls kann ein Benutzer mit sehr hoher Schuld eine große 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 zugeteilte Schuld repräsentiert, sollte nicht als verfügbarer geprägter USDO gezählt werden.
2.2.13 Potenzielles Problem 17: Falsche Handhabung der Stable-Gebühr des Benutzers
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben (Die zugehörige Logik wurde nun entfernt) |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Wenn Benutzer die Funktion burn_coin aufrufen, wird die Stable-Gebühr mit dem 'OIN'-Token und nicht mit '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 Die Staking-Token der Benutzer können 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 Potenzielles Problem 18: Falsches Systemverhältnis
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor 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 Bedingung if total_coin = 0 in token_usd = 0.
2.2.15 Potenzielles Problem 19: Die Anzahl der Belohnungsmünzen kann größer sein als die Obergrenze
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Wenn es derzeit 20 Belohnungsmünzen gibt, kann die Behauptung in Zeile 131 von Listing 2.24 erfolgreich sein. In diesem Fall kann eine weitere Belohnungsmünze hinzugefügt werden und die Gesamtzahl der Belohnungsmünzen kann 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
)
}
Listing 2.24: internal_add_reward_coin:pool.rs
Auswirkung Die zulässige Anzahl hinzugefügter Belohnungsmünzen widerspricht dem Design des Systems.
Vorschlag I Ändern Sie die Behauptung in self.reward_coins.len() < REWARD_UPPER_BOUND.
2.2.16 Potenzielles Problem 20: Benutzer mit unterschiedlichen Berechtigungen verwenden dieselbe Whitelist
| Punkt | 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 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 Potenzielles Problem 21: burn_coin prüft nicht den Token-Typ
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Funktion burn_coin prüft nicht den Token-Typ. In diesem Fall können Angreifer beliebige Token mit der angegebenen Menge übertragen, um die Stable-Gebühr zu bezahlen.
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 keine Oin-Token bezahlen. Stattdessen können sie die Stable-Gebühr bezahlen, indem sie beliebige Token mit dem erforderlichen Betrag übertragen.
Vorschlag I Prüfen Sie die Adresse des empfangenen Tokens.
2.2.18 Potenzielles Problem 22: Die Gesamtbelohnung der Belohnungsmünze kann von Multi-Signatur-Managern geändert werden
| Punkt | 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 über Multi-Signatur-Anfragen aufrufen und die Gesamtbelohnung beliebig erhöhen, 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 als privat.
2.3 Zusätzliche Empfehlung
2.3.1 Redundante Behauptung
| Punkt | 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 der Belohnungsmünze wird in ft_on_transfer geprüft. In diesem Fall müssen wir den Namen der Belohnungsmünze am Anfang der Funktion inject_reward nicht 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 Prüfung des Liquidationsverhältnisses des Benutzers
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-1 eingeführt. Die Liquidationsgrenze wird bereits in der Funktion internal_avaliable_token berücksichtigt, daher ist es nicht notwendig, später zu prüfen, ob das Benutzerverhältnis die Liquidationsgrenze 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 Behauptung in Zeile 559 von Listing 2.32.
2.3.3 Redundante Whitelist-Prüfung
| Punkt | 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 die Berechtigung zu prüfen. Inzwischen ruft internal_set_reward_speed, das 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
| Punkt | 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. 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
| Punkt | 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));
}
Listing 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
| Punkt | 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 angeben, 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
| Punkt | 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 Stable-Gebühren zu aktualisieren. Stable-Gebühren haben keinen Bezug zu gestakten Token. Daher ist es nicht erforderlich, die Guthaben der Token für Benutzer zu ändern, um die Stable-Gebühren zu 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 Berechnungspräzision kann verbessert werden
| Punkt | Beschreibung |
|---|---|
| Status | Bestätigt und behoben |
Beschreibung Dieses Problem wurde in oder vor Commit-3 eingeführt. Die Funktion internal_user_stable dient zur Berechnung der Stable-Gebühr. Die Berechnungspräzision 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 Zeile 30 durch.
2.3.9 System erfasst möglicherweise keinen zuvor abgefragten Preis
| Punkt | 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 erfasst den abgefragten Preis möglicherweise nicht, da die Gesamtzahl 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
);
}
}
Listing 2.40: poke:oracle.rs
Vorschlag I Die Aufzeichnung des Verhaltens des Abfragens von Token-Preisen sollte nicht von der Anzahl der im Vertrag hinterlegten Token beeinflusst werden.
2.3.10 Diskontinuierliche Verteilung von Collateral-Token bei Liquidation
| Punkt | 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 Liquidationsgebühr zahlen, die 2 % der allot_debt beträgt. Wenn das Staking-Verhältnis des Benutzers jedoch kleiner als 108,5 % ist, muss er keine Liquidationsgebühr zahlen. Dies führt dazu, dass ein Benutzer mit einem größeren Staking-Verhältnis nach der Liquidation möglicherweise weniger Staking-Token dem 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 eine Liquidationsgebühr von (Staking-Verhältnis - 108,5 %) empfohlen.
2.3.11 Optimierung der Berechnungspräzision ist nicht notwendig
| Punkt | 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 von Listing 2.42 erhöht die Berechnungspräzision nicht, 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 eines zentralisierten Designs
Status Bestätigt
Beschreibung Beschreibung Das Projekt hat ein stark zentralisiertes Design. Der Vertragseigentümer hat sehr hohe Privilegien, er kann Multi-Signatur-Manager hinzufügen/entfernen und die Liquidationsgebühr und Belohnung usw. abheben. 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 erzwingt.
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, als würde er die potenziellen wirtschaftlichen Aspekte eines Tokens, eines Token-Verkaufs oder eines anderen Produkts, einer Dienstleistung oder eines anderen Vermögenswerts berücksichtigen oder Einfluss darauf haben. Keine Einheit 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 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 Zusicherungen, dass alle Sicherheitsprobleme der Smart Contracts entdeckt werden, d. h. das Bewertungsergebnis garantiert nicht die Nichtexistenz weiterer Feststellungen von Sicherheitsproblemen. Da eine Prüfung nicht als umfassend angesehen werden kann, empfehlen wir stets, 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, liegen die Sicherheit der Sprache selbst (z. B. der Rust-Sprache), die zugrunde liegende Kompilierungs-Toolchain und die Recheninfrastruktur außerhalb des Geltungsbereichs.
3.2 Prüfverfahren
Wir führen die Prüfung gemäß dem folgenden Verfahren durch.
-
Schwachstellenerkennung Wir scannen zunächst Smart Contracts mit automatischen Code-Analysatoren und überprüfen dann manuell die von ihnen gemeldeten Probleme (ablehnen oder bestätigen).
-
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 Angriffsszenarien mit unabhängigen Gutachtern, um das Ergebnis gegenzuprüfen.
-
Empfehlung Wir geben den Entwicklern nützliche Ratschläge aus der Perspektive guter Programmierpraktiken, einschließlich Gasoptimierung, Code-Stil und so weiter.
Die wichtigsten konkreten Prüfpunkte sind im Folgenden aufgeführt.
3.2.1 Software-Sicherheit
-
Reentrancy
-
DoS
-
Zugriffskontrolle
-
Datenverarbeitung und Datenfluss
-
Fehlerbehandlung
-
Nicht vertrauenswürdige externe Aufrufe und Kontrollfluss
-
Konsistenz der Initialisierung
-
Ereignisbetrieb
-
Fehleranfällige Zufälligkeit
-
Unsachgemäße Verwendung des Proxy-Systems
3.2.2 DeFi-Sicherheit
-
Semantische Konsistenz
-
Funktionale Konsistenz
-
Zugriffskontrolle
-
Geschäftslogik
-
Token-Betrieb
-
Notfallmechanismus
-
Oracle-Sicherheit
-
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 verwenden, je nach Funktionalität des Projekts. :::



