Von BlockSec Team (@BlockSecTeam)

Letzte Woche gab es im Compound-Protokoll einen Fehler, der dazu führte, dass versehentlich eine große Menge an COMP-Token an Benutzer gesendet wurde. Die Ursache dieses Fehlers (Bug 2 in diesem Blog) liegt in der fehlerhaften Behebung eines anderen, zuvor entdeckten Fehlers (Bug 1 in diesem Blog).
In diesem Blog werden wir die Grundursache des ersten Fehlers und den Grund erläutern, warum die Behebung des ersten Fehlers den zweiten Fehler verursacht hat.
Hintergrund
Das Compound-Protokoll basiert auf dem Compound Whitepaper. Über die cToken-Verträge stellen Konten auf der Blockchain Kapital (Ether oder ERC-20-Token) bereit, um cTokens zu erhalten, oder leihen Vermögenswerte vom Protokoll aus (unter Verwendung anderer Vermögenswerte als Sicherheit). Die Compound cToken-Verträge verfolgen diese Guthaben und legen algorithmisch Zinssätze für Kreditnehmer fest.
Um Benutzer zu incentivieren, erhalten Benutzer, die Liquidität an Compound bereitstellen (Kapital bereitstellen), die Zinsen. Insbesondere stellen Benutzer Vermögenswerte (z. B. Ether oder andere ERC20-Token) an Compound bereit und erhalten die entsprechenden cTokens. Wenn der cToken an Compound zurückgegeben wird, werden die zugrunde liegenden Vermögenswerte (Ether oder ERC20-Token) und die Zinsen an den Benutzer zurückerstattet, sofern der Benutzer keine Schulden bei Compound hat. Wenn ein Benutzer beispielsweise 1000 Ether hat, kann er den Vermögenswert über cEth.mint(1000) in Compound einzahlen, um den cToken zu erhalten.

Der cToken repräsentiert die bei Compound gesperrten zugrunde liegenden Vermögenswerte. Der Benutzer kann den cToken weiter als Sicherheit verwenden, um andere Vermögenswerte zu leihen. Beispielsweise 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 Token der Benutzer in Compound eingezahlt hat, wie viele Token der Benutzer ausgeliehen hat und ob der Benutzer mehr Token ausleihen kann. Zu den in diesem Prozess aufgerufenen Funktionen gehören unter anderem getHypotheticalAccountLiquidityInternal(), borrowAllowed() und mintAllowed().
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.
Bug 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 betrifft CompSpeed, der die Anzahl der COMP-Token darstellt, die Benutzern in jedem Block 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 lautet: mint → mintInternal → mintFresh.

In der Funktion mintFresh wird mintAllowed aufgerufen und dann der Saldo des Benutzers für den cToken aktualisiert.

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

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

In der Struktur CompMarketState wird die Blocknummer (block) dieser Aktualisierung und der Bonusindex (index) aufgezeichnet, der die Anzahl der COMP-Token beeinflusst, die an die Benutzer (die den cToken halten) verteilt werden sollen.
Was ist der Bonusindex (index) für jeden Token? Dies ist der über die Zeit akkumulierte Wert (siehe folgende Formel).

Dies zeigt die Anzahl der COMP, die an Benutzer (für jeden von dem Benutzer gehaltenen cToken) verteilt werden sollen.
distributeSupplierComp
Die andere Funktion distributeSupplierComp ist dafür verantwortlich, die Anzahl der COMP-Token, die dem Benutzer (Lieferanten) in compAccrued[supplier] zugewiesen werden sollen, aufzuzeichnen.

Insbesondere aktualisiert sie den globalen Bonusindex in compSupplyState (in der Funktion updateCompSupplyIndex). Dann zeichnet in der Funktion distributeSupplierComp der supplyIndex den aktuellen Bonusindex auf, und der supplierIndex zeigt den letzten Bonusindex für den Benutzer (Lieferanten) an. Der Delta-Wert ((supplyIndex - supplierIndex)) * das cToken-Guthaben des Benutzers zeigt die Anzahl der COMP-Token an, die dem Benutzer zugewiesen werden sollen.
Die Ursache von Bug 1
Es gibt eine weitere Funktion setCompSpeed, um die supplySpeed des Marktes (compSpeeds[address[cToken]]) anzupassen.

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

Schritt I: Für Märkte, bei denen die Verteilung von COMP-Token in Schritt I deaktiviert wurde (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-Token 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 bei der Wiederaktivierung des deaktivierten Marktes durch Aufruf von setCompSpeed, um CompSpeed[cToken] auf einen Nicht-Null-Wert zu setzen, der Indexwert NICHT auf CompInitialIndex (1e36) zurückgesetzt wird (Zeilen 1090 und 1091 werden nicht ausgeführt).
Die Auswirkung von Bug 1
Wir untersuchen weiter die Funktion distributeSupplierComp, die für die Verteilung der COMP-Token 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.

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

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

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

Die weitere Untersuchung der Transaktion zeigt, dass aufgrund von Bug 2 der deltaIndex 1e36 beträgt und der Benutzer zu diesem Zeitpunkt zufällig den cToken hatte.



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

Lektionen
- Dies ist ein Fehler, der durch die Behebung eines anderen Fehlers verursacht wurde. Wie Codeänderungen für Projekte mit hoher Sichtbarkeit gründlich überprüft werden können, ist nach wie vor eine offene Frage.
- Die DAO kann das Risiko der Zentralisierung eliminieren. Sie 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 Einführung eines effizienten Fuzzing-Systems mit einem kontinuierlichen Testprozess.
Über BlockSec
BlockSec ist ein führendes Unternehmen für 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 fördern. Zu diesem Zweck bietet BlockSec Sicherheitsaudits für Smart Contracts und EVM-Ketten, die Phalcon-Plattform für die sichere Entwicklung und die proaktive Blockierung von Bedrohungen, die MetaSleuth-Plattform für die Nachverfolgung und Untersuchung von Geldern sowie die MetaSuites-Erweiterung für Web3-Entwickler, um effizient in der Krypto-Welt zu navigieren.
Bisher hat das Unternehmen über 300 angesehene Kunden wie MetaMask, Uniswap Foundation, Compound, Forta und PancakeSwap betreut und in zwei Finanzierungsrunden von führenden Investoren, darunter Matrix Partners, Vitalbridge Capital und Fenbushi Capital, zweistellige Millionenbeträge in US-Dollar erhalten.
Offizielle Website: https://blocksec.com/
Offizielles Twitter-Konto: https://twitter.com/BlockSecTeam



