Von BlockSec Team (@BlockSecTeam)

In der letzten Woche hatte das Compound-Protokoll einen Fehler, der dazu führte, dass eine große Menge an COMP-Tokens versehentlich an Benutzer gesendet wurde. Ursache dieses Fehlers (Fehler 2 in diesem Blog) ist die fehlerhafte Behebung eines anderen zuvor entdeckten Fehlers (Fehler 1 in diesem Blog).
In diesem Blog erläutern wir die Grundursache des ersten Fehlers und den Grund, warum die Behebung des ersten Fehlers den zweiten Fehler verursacht.
Hintergrund
Das Compound-Protokoll basiert auf dem Compound Whitepaper. Über die cToken-Verträge stellen Konten in der Blockchain Kapital (Ether oder ERC-20-Tokens) zur Verfügung, um cTokens zu erhalten, oder leihen sich Vermögenswerte vom Protokoll (wobei andere Vermögenswerte als Sicherheiten gehalten werden). Die Compound cToken-Verträge verfolgen diese Salden und legen algorithmisch Zinssätze für Kreditnehmer fest.
Um Benutzer zu incentivieren, können Benutzer, die Liquidität für Compound bereitstellen (Kapital bereitstellen), die Zinsen erhalten. Insbesondere stellen Benutzer Vermögenswerte (z. B. Ether oder andere ERC20-Tokens) für Compound bereit und erhalten die entsprechenden cTokens. Wenn der cToken an Compound zurückgegeben wird, werden die zugrunde liegenden Vermögenswerte (Ether oder ERC20-Tokens) und die Zinsen an den Benutzer zurückgegeben, wenn der Benutzer keine Schulden bei Compound hat. Wenn ein Benutzer beispielsweise 1000 Ether hat, kann er/sie den Vermögenswert über cEth.mint(1000) in Compound einzahlen, um den cToken zu erhalten.

Der cToken repräsentiert die in Compound gesperrten zugrunde liegenden Vermögenswerte. Der Benutzer kann den cToken weiter als Sicherheit verwenden, um andere Vermögenswerte zu leihen. Zum Beispiel kann ein Benutzer 1000 Ether über ceth.mint(1000) einzahlen und dann die erhaltenen cTokens verwenden, um x Dai im Wert von 75 Ether zu leihen (Überbesicherung - diese Zahl hängt vom Sicherheitenfaktor ab) über cDai.borrow(x).
Die Kernlogik ist im Comptroller-Vertrag implementiert. Er verwaltet die Zustände eines Benutzers, z. B. wie viele Tokens vom Benutzer in Compound eingezahlt wurden, wie viele Tokens vom Benutzer ausgeliehen wurden und ob der Benutzer mehr Tokens leihen kann. Zu den in diesem Prozess aufgerufenen Funktionen gehören getHypotheticalAccountLiquidityInternal(), borrowAllowed(), mintAllowed() usw.
Compound hat auch den Governance-Token namens COMP. Der COMP-Token kann zur Abstimmung über Vorschläge verwendet werden. Außerdem kann der COMP-Token an Börsen gehandelt werden. Derzeit liegt der Preis von COMP bei etwa 300 US-Dollar.
Fehler 1
Am 31. September 2021 gab es einen neuen Vorschlag (Vorschlag 62) in der Compound DAO, der darauf abzielte, einen Fehler im Comptroller zu beheben.

Der Fehler hängt mit CompSpeed zusammen, das die Anzahl der COMP-Tokens darstellt, die pro Block an Benutzer verteilt werden können.
Der Ablauf der `mint`-Funktion
Im Folgenden beschreiben wir anhand der mint-Funktion die Ursache dieses Fehlers. Die Aufrufkette der mint-Funktion ist: mint → mintInternal → mintFresh.

In der Funktion mintFresh wird mintAllowed aufgerufen und dann der Saldo des Benutzers des cTokens aktualisiert.

In der Funktion mintAllowed werden zuerst updateCompSupplyIndex und dann distributeSupplierComp aufgerufen, um 1) den compSupplyState des Marktes zu aktualisieren und 2) die COMP-Tokens an die Benutzer zu verteilen.
updateCompSupplyIndex

Die Funktion updateCompSupplyIndex aktualisiert den Status jedes Marktes, hauptsächlich den compSupplyState[cToken].

In der CompMarketState-Struktur wird die Blocknummer (block) dieser Aktualisierung und der Bonusindex (index) gespeichert, der die Anzahl der COMP-Tokens beeinflusst, die an die Benutzer (die den cToken halten) verteilt werden sollen (in der folgenden Formel gezeigt).

Dies zeigt die Anzahl der COMP, die an die Benutzer verteilt werden sollen (für jeden cToken, den der Benutzer hält).
distributeSupplierComp
Die andere Funktion distributeSupplierComp ist dafür verantwortlich, die Anzahl der COMP-Tokens zu speichern, die dem Benutzer (Lieferanten) in compAccrued[supplier] zugutekommen sollen.

Genauer gesagt wird der globale Bonusindex in compSupplyState aktualisiert (in der Funktion updateCompSupplyIndex). Dann in der Funktion distributeSupplierComp speichert supplyIndex den aktuellen Bonusindex, und supplierIndex zeigt den letzten Bonusindex für den Benutzer (Lieferanten). Der Delta-Wert (supplyIndex - supplierIndex) * der cToken-Saldo des Benutzers zeigt die Anzahl der COMP-Tokens, die dem Benutzer zugutekommen sollen.
Die Ursache von Fehler 1
Es gibt eine weitere Funktion setCompSpeed, um die supplySpeed des Marktes (compSpeeds[address[cToken]]) anzupassen.

Dies liegt daran, dass, wenn wir die CompSpeed für einen Markt auf Null setzen, dies bedeutet, dass der COMP-Token nicht an Benutzer in diesem Markt verteilt wird. Wenn wir also zuerst die Verteilung von COMP für einen Markt deaktivieren und dann wieder aktivieren möchten, können wir die folgenden Schritte ausführen:
- Schritt I: Setzen Sie
CompSpeed[cToken]auf Null, um die Verteilung von COMP-Tokens zu deaktivieren. - Schritt II: Rufen Sie die Funktion
setCompSpeedauf, umCompSpeed[cToken]auf einen Wert ungleich Null zu setzen.

Schritt I: Für Märkte, die in Schritt I für die Verteilung von COMP-Tokens deaktiviert wurden (supplySpeed == 0), ist der Block nicht Null, da der Block in updateCompSupplyIndex kontinuierlich aktualisiert wird (else if (deltaBlocks > 0)).

Schritt II: Bei der Ausführung der Operation in Schritt II durchläuft die Funktion setCompSpeedInternal die Anweisung else if (compSpeed != 0) (Zeile 1083). Dann gibt es in den Zeilen 1088 bis 1093 eine Prüfung if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0), um den index und block für einen neuen Markt zu initialisieren. Da wir jedoch die Verteilung von COMP-Tokens in einem bestehenden Markt (nicht in einem neuen Markt) wieder aktivieren, werden die Anweisungen in den Zeilen 1090 und 1091 nicht ausgeführt, um den index und block zu initialisieren (da compSupplyState[address(cToken)].block nicht Null ist).
Zusammenfassend lässt sich sagen, dass für einen derzeit deaktivierten Markt der index Null ist. Der block ist jedoch nicht Null. Das bedeutet, dass der Indexwert NICHT auf CompInitialIndex (1e36) zurückgesetzt wird, wenn wir den deaktivierten Markt durch Aufrufen von setCompSpeed zum Setzen von CompSpeed[cToken] auf einen Wert ungleich Null wieder aktivieren (Zeilen 1090 und 1091 werden nicht ausgeführt).
Die Auswirkungen von Fehler 1
Wir untersuchen weiter die Funktion distributeSupplierComp, die für die Verteilung der COMP-Tokens zuständig ist.

Der supplierIndex ist compInitialIndex. Der supplyIndex ist jedoch aufgrund des Fehlers immer noch Null, was zu einem Unterlauf für Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36) führt.

Fehler 2: Eingeführt durch die Behebung von Fehler 1
Um den Fehler zu beheben, ändert der Projektleiter die Code-Logik. Insbesondere wird der index sofort auf compInitialIndex initialisiert, wenn ein neuer Markt initialisiert wird.

Da der globale Bonusindex (index) mit compInitialIndex initialisiert wurde, sollte der Benutzerbonusindex ebenfalls auf diesen Wert initialisiert werden. Schauen wir uns die Funktion distributeSupplierComp an.

Die If-Bedingung in Zeile 1234 kann nicht erfüllt werden, auch wenn supplierIndex == 0 ist, da supplyIndex gleich (nicht größer als) compInitialIndex (1e36) ist. Dies führt dazu, dass der supplierIndex NICHT korrekt auf compInitialIndex (sein Wert ist 0) initialisiert wird. Dann wird der deltaIndex (supplyIndex - supplierIndex) compInitialIndex sein, anstatt Null. Die supplierTokens werden zu einem großen Wert, wenn der Saldo des Benutzers des cTokens nicht Null ist.
Zusammenfassend lässt sich sagen, dass wenn ein Benutzer zufällig die Mint-Operation ausführt, bevor der Fehler 1 behoben wurde, er dann cTokens besitzt und der supplierIndex Null wird (da der COMP-Token verteilt wurde). Dann, nach der Behebung von Fehler 1 (der Fehler 2 einführt), wenn der Benutzer die mint-Funktion erneut aufruft, kann er eine große Menge an COMP-Token erhalten (1e36*ctoken.balanceOf(user)).
Reale Welt
Wir zeigen die betroffenen Märkte in den folgenden:
0xF5DCe57282A584D2746FaF1593d3121Fcac444dC: cSAI
0x12392F67bdf24faE0AF363c24aC620a2f67DAd86: cTUSD
0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b: cMKR
0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7: cSUSHI
0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c: cAAVE
0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946: cYFI
Für den Benutzer (0xa7b95d2a2d10028cc4450e453151181cbcac74fc) hat der Benutzer in dieser Transaktion (0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308) 4.466,542459954989867175 COMP-Tokens erhalten.

Die weitere Untersuchung der Transaktion zeigt, dass aufgrund von Fehler 2 der deltaIndex 1e36 beträgt und der Benutzer zu dieser Zeit zufällig den cToken besaß.



Die Behebung von Fehler 2
Die Behebung von Fehler 2 ist einfach. Sie ändert die If-Bedingung in der Funktion distributeSupplierComp.

Lehren
- Dies ist ein Fehler, der durch die Behebung eines anderen Fehlers verursacht wurde. Wie die Codeänderungen für hochkarätige Projekte gründlich überprüft werden können, ist immer noch eine offene Frage.
- Die DAO kann das Risiko der Zentralisierung eliminieren. Dies macht jedoch auch die Reaktion auf Sicherheitsvorfälle zu einem langsamen Prozess.
- Hochkarätige DeFi-Projekte können gute Sicherheitspraktiken aus traditionellen Programmen übernehmen, z. B. die Bereitstellung eines effizienten Fuzzing-Systems mit einem kontinuierlichen Testprozess.
Über BlockSec
BlockSec ist ein Pionierunternehmen im Bereich Blockchain-Sicherheit, das 2021 von einer Gruppe weltweit herausragender Sicherheitsexperten gegründet wurde. Das Unternehmen engagiert sich für die Verbesserung der Sicherheit und Benutzerfreundlichkeit für die aufstrebende Web3-Welt, um deren Massenadoption zu erleichtern. Zu diesem Zweck bietet BlockSec Sicherheitsaudits für Smart Contracts und EVM-Ketten, die Phalcon-Plattform für die proaktive Sicherheitsentwicklung und Bedrohungsabwehr, die MetaSleuth-Plattform für die Verfolgung von Geldern und Ermittlungen sowie die MetaSuites-Erweiterung für Web3-Entwickler, um effizient in der Krypto-Welt zu navigieren.
Bis heute hat das Unternehmen über 300 angesehene Kunden wie MetaMask, Uniswap Foundation, Compound, Forta und PancakeSwap betreut und in zwei Finanzierungsrunden von namhaften Investoren wie Matrix Partners, Vitalbridge Capital und Fenbushi Capital zweistellige Millionenbeträge US-Dollar erhalten.
Offizielle Website: https://blocksec.com/
Offizielles Twitter-Konto: https://twitter.com/BlockSecTeam
![[Der Schmetterlingseffekt] Der durch eine Fehlerbehebung verursachte Sicherheitsvorfall](/_next/image?url=https%3A%2F%2Fassets.blocksec.com%2Ffrontend%2Fblocksec-strapi-online%2FThe_Butterfly_Effect_The_Compound_Security_Incident_Caused_by_a_Bugfix_4e7b2d04f3.png&w=1920&q=75)


