
Einleitung
Um diese Einschränkungen zu umgehen, wurden mehrere Layer-2-Lösungen wie Base entwickelt, die durch Skalierbarkeit und geringere Gas-Kosten gesteuert werden. Obwohl L2s im Vergleich zum Ethereum-Mainnet erhebliche Gebührensenkungen bieten, können Entwickler von Smart Contracts die Gas-Optimierung bei ihrer Entwicklung nicht ignorieren.
Dieses Tutorial soll dazu beitragen, die Benutzererfahrung zu verbessern und wettbewerbsfähigere, dezentrale Anwendungen durch fortgeschrittene Strategien zu entwickeln, die die Gas-Kosten für Smart Contracts senken, die getestet werden sollen. Die Beispiele, die sie geben, sind mit einfachen Verträgen simuliert und berücksichtigen hauptsächlich die Gas-Preise zur Laufzeit, da die Bereitstellungskosten je nach Größe eines Vertrags sehr unterschiedlich sind.
Gasoptimierung ist wichtig für Entwickler, Nutzer und den langfristigen Erfolg von Projekten. Durch die wirtschaftliche Nutzung von Smart Contract Gas können Protokolle kostengünstiger und skalierbarer werden und sind weniger anfällig für Sicherheitsbedrohungen wie Denial-of-Service-Angriffe.
Die praktische Anwendung von Smart Contracts erfordert eine umfassende und systematische Prüfung jedes einzelnen Smart Contracts.
Die Bedeutung der Solidity-Gas-Optimierung
Gasoptimierung sorgt dafür, dass Smart Contracts, Protokolle und Projekte auch bei überlasteten Netzwerken funktionieren und macht sie günstig und effizient. Durch die Verbesserung des Vertragscodes können mögliche Schwachstellen und Protokolle aufgedeckt werden, was für mehr Sicherheit für Nutzer sorgt.
Die Gasoptimierung ist auch ein wichtiger Schwerpunkt der Entwicklung. Sie ist nicht nur ein nettes Extra, sondern entscheidend für den langfristigen Erfolg und die Sicherheit von Smart Contracts. Dass es möglich ist, auf L2s mit vergleichsweise niedrigen Gebühren aufzubauen, heißt nicht, dass die Gasgebühren nicht deutlich höher sein werden als bei der Konkurrenz.
Alle Tests nutzen Foundry und Solidity Version 0.8.13, den lokalen Blockchain-Knoten Anvil, Forge-Testbefehle und 100 Optimierungsläufe.
Professionelle Wirtschaftsprüfungsgesellschaften sollten in umfassende Sicherheitsüberprüfungen einbezogen werden, dieser Leitfaden sollte nur als Hilfsmittel dienen.
Reduzier die On-Chain-Daten
Non-Fungible Tokens wurden 2021 populär und weckten das Interesse an vollständig on-chain NFTs. Im Gegensatz zu traditionellen NFTs, die off-chain Daten wie Metadaten und Bildreferenzen nutzen, speichern on-chain NFTs alle Infos direkt in der Blockchain.
Die Kommunikation mit diesen Token ist bekanntlich teuer, und wenn Nutzer von Gebühren betroffen sind, sind hybride Lösungen die Standardlösung. Egal, ob du NFTs, Spiele oder DeFi-Protokolle entwickelst, du solltest immer überlegen, welche Daten tatsächlich in der Blockchain gespeichert werden müssen und welche Vor- und Nachteile beide Alternativen haben.
**Reduzier den Gasverbrauch drastisch, indem du Infos außerhalb der Blockchain speicherst, sodass weniger Speicherplatz für Variablen gebraucht wird. Eine Möglichkeit ist, Events zu nutzen, um Daten außerhalb der Blockchain zu speichern, statt direkt in der Blockchain.
Die Transaktionsgas-Kosten steigen durch zusätzliche Emit-Funktionen, die durch Ereignisse verursacht werden; aber da die Infos nicht in der Blockchain gespeichert werden, sind die Einsparungen meistens größer als die Kosten.
Stell dir einen Smart Contract vor, bei dem Nutzer mit „Ja“ oder „Nein“ abstimmen können. Der erste speichert die Stimmen der Nutzer in On-Chain-Strukturen. Eine Demo des Abstimmungstests mit Foundry mit über 100 Wiederholungen zeigt bestimmte Gas-Kosten.
Vergleich mit einem Smart Contract, der keine Infos in der Blockchain speichert, sondern ein Ereignis auslöst, wenn die Abstimmungsfunktion aufgerufen wird. Ein Test mit mehr als 100 Foundry-Vorgängen in der neuen Abstimmungsfunktion liefert Ergebnisse.
Durch reduzierte On-Chain-Daten sank der durchschnittliche Gasverbrauch um 90,34 %.
Um auf Off-Chain-Daten in der Blockchain zugreifen zu können, gibt es Chainlink-Funktionen und Support-Lösungen, die sich in die meisten gängigen L2-Netzwerke wie Base integrieren lassen.
Verwende Zuordnungen über Arrays
In Solidity gibt es zwei Hauptdatenstrukturen: Arrays und Mappings.
- Arrays werden verwendet, um Gruppen von Elementen zu speichern, die bestimmten Indizes zugeordnet sind.
- Schlüssel-Wert-Zuordnungen sind Datenstrukturen, die den direkten Zugriff auf Daten mithilfe eindeutiger Schlüssel ermöglichen.
Obwohl Arrays für die Speicherung von Vektoren und ähnlichen Daten nützlich sein können, sind Mappings wegen ihrer Gaseffizienz meistens besser. Sie sind besonders gut für Situationen geeignet, in denen Daten auf Abruf gebraucht werden, z. B. nach Namen, Wallet-Adresse oder Kontostand.
Um den Gasverbrauch bei der Verwendung von Arrays oder Mappings zu verstehen, muss man möglicherweise den Gasverbrauch der zugehörigen EVM-Opcodes konsultieren. Opcodes sind Low-Level-Anweisungen, die die Ethereum Virtual Machine bei der Ausführung von Smart Contracts ausführt, wobei jeder Opcode mit Gas-Kosten verbunden ist.
Um auf die Werte des Arrays zuzugreifen, muss pro Gas-Einheit bezahlt werden, die von den EVM-Opcodes verbraucht wird. Um die Benutzeradressen und die entsprechenden Guthaben im Array zu speichern, musst du alle Elemente im Array durchlaufen, prüfen, ob die userAddress und das angegebene Argument übereinstimmen, und bei einer Übereinstimmung das Guthaben zurückgeben.
Durch den direkten Zugriff auf den spezifischen Kontostand des Benutzers muss nicht mehr das gesamte Array durchlaufen werden. Nachdem die Arrays durch Zuordnungen ersetzt wurden, wurden 89 Prozent weniger Gas für den Zugriff auf Daten verbraucht.
Vergleich des Gasverbrauchs: Arrays vs. Mappings
| Vorgehensweise | Vor der Optimierung | Nach der Optimierung | Benzinersparnis |
|---|---|---|---|
| Datenzugriff | 30.586 | 3.081 | 89 % |
| Hinzufügen von Daten | - | - | 93 % |
Verwende Konstanten und Unveränderliches, um die Gas-Kosten für Smart Contracts zu senken.
Ein weiterer Optimierungstipp wäre, Konstanten und unveränderliche Variablen zu verwenden. Wenn Variablen als unveränderlich oder konstant deklariert werden, werden ihre Werte nur zum Zeitpunkt der Vertragserstellung bereitgestellt und danach nicht mehr.
Im Vergleich zu anderen Variablen nehmen sie keinen Speicherplatz in der EVM ein. Es ist möglich, Werte direkt im Bytecode des Smart Contracts zu kodieren, was bedeutet, dass die Kosten für die Datenspeicherung reduziert werden, da Variablen wie maxSupply und owner nicht über die Schlüsselwörter „constant” oder „immutable” verfügen.
Wenn die Tests 100 Mal durchgeführt werden, betragen die durchschnittlichen Gaskosten 112.222 Einheiten. Da maxSupply und owner bekannte Werte sind, die nicht geändert werden sollen, ist es am besten, die maximale Menge und den Eigentümer anzugeben, die keinen Speicherplatz beanspruchen.
Die Tests mit konstanten oder unveränderlichen Variablen führen zu durchschnittlichen Einsparungen von 35,89 % (von 112.222 auf 71.940 Gaseinheiten).
Optimier ungenutzte Variablen
Es ist ein offensichtlicher Tipp zur Gasoptimierung, Variablen in Smart Contracts zu optimieren. Nicht benötigte Variablen werden jedoch in den meisten Fällen bei der Ausführung des Vertrags beibehalten, was zu unnötigem Gasverbrauch führt.
Das Entfernen einzelner ungenutzter Variablen kann helfen, Gas-Kosten zu sparen, im Durchschnitt 18 %, wenn man Verträge mit ungenutzten Variablen betrachtet, die in Funktionen definiert und bearbeitet werden, aber sonst nirgendwo aufgerufen werden.
Ergebnisse der Optimierung ungenutzter Variablen
| Optimierungsphase | Gasverbrauch | Einsparungen |
|---|---|---|
| Vor der Optimierung | 32.513 | - |
| Nach der Optimierung | 27.429 | 18 % |
Solidity Gas Refund: Löschen nicht genutzter Variablen
Das Entfernen nicht verwendeter Variablen ist ein Versuch, Variablen Standardwerte zuzuweisen, nachdem sie vollständig berechnet wurden, ohne dass Daten im Speicher abgelegt werden. Zur Veranschaulichung: Der Standardwert für Variablen vom Typ uint ist 0.
Wenn du die Variablen der Funktion nach Beendigung der Funktionen nicht zerstörst, betragen die durchschnittlichen Gaskosten 100.300 Einheiten, um Variablen Daten zuzuweisen. Durchschnittlich 19 % Gaseinsparungen durch Verwendung der Löschtaste zum Entfernen von Datenvariablen (um Variablen auf 0 zu setzen).
Verwende Arrays mit fester Größe anstelle von dynamischen Arrays, um die Gas-Kosten für Smart Contracts zu minimieren
Verwende nach Möglichkeit Zuordnungen, um die Gas-Kosten für Smart Contracts zu senken. Wenn jedoch Arrays erforderlich sind, sind Arrays mit fester Größe in Bezug auf Gas wirtschaftlicher als Arrays mit dynamischer Größe, da Arrays mit dynamischer Größe unbegrenzt erweitert werden können, was zu höheren Gas-Kosten führt.
Dynamische Arrays können erweitert werden, daher muss die EVM die Längen überwachen und sie aktualisieren, wenn neue Elemente hinzugefügt werden.
Denk an Code, der Arrays mit dynamischer Größe definiert und sie mit updateArray-Funktionen aktualisiert. Require-Anweisungen sorgen dafür, dass die angegebenen Indizes innerhalb eines Array-Bereichs mit fester Größe liegen. Wenn ein Test 100 Mal wiederholt wird, beträgt der durchschnittliche Gasverbrauch 12.541.
Durch Ändern von Arrays auf eine feste Größe von 5 werden Arrays mit einer festen Größe von 5 Elementen und dem Typ uint256 erstellt. Die EVM weiß, dass fixedArray mit der Statusvariablen Größe 5 über 5 Slots verfügt und die Länge nicht im Speicher abgelegt wird. Durch Ersetzen dynamischer durch feste Arrays lassen sich 17,99 % Gas einsparen.
Optimier deine Smart Contracts noch heute
Fang an, diese Gasoptimierungstechniken zu nutzen, um die Kosten auf Base und anderen L2-Chains um bis zu 90 % zu senken.
Verwendung von uint8 als Ersatz für uint256
Das ist weniger effizient und kann wegen der Funktionsweise der EVM teurer sein als uint256. Die EVM hat 256-Bit-Wortgrößen. Operationen mit 256-Bit-Ganzzahlen (uint256) sind in der Regel am effizientesten, da sie zur EVM-Wortgröße passen, während kleinere Typen (wie uint8) erfordern, dass der Solidity-Compiler zusätzliche Operationen erstellt, um kleinere Typen in einen einzigen 256-Bit-Speicherplatz einzupassen.
Im Allgemeinen können kleine Typen wie uint8 zwar beim Speicherplatz von Vorteil sein (mehrere kleine Typen können in einem Vorgang mit einem 256-Bit-Speicherplatz zusammengefasst werden), aber es hat sich gezeigt, dass kleinere Typen nur beim Speicherplatz helfen. Die Speicherplatzersparnis kann durch die Konvertierung von und nach uint256 für Berechnungen zunichte gemacht werden.
Kombinieren von Variablen, die kleiner als 256 Bit sind
Das Kombinieren von Variablen, die kleiner als 256 Bit sind, ist normalerweise nicht so effizient wie 256-Bit-Variablen. Wenn es aber nicht anders geht, muss man kleinere, weniger leistungsfähige Typen nehmen, z. B. Boolesche Werte, die 1 Byte oder 8 Bit Speicherplatz brauchen.
Bei der Verwendung weniger leistungsfähiger Typen ist es möglich, Statusvariablen unter Berücksichtigung des Speicherplatzes zu deklarieren, und Solidity kann sie zusammenpacken und unter Verwendung desselben Speicherplatzes speichern. Der Vorteil des Variablenpackings wird in der Regel eher bei Speicheroperationen als bei Speicher- oder Stapeloperationen berücksichtigt.
Da die Größe der Zwei-Wege-Kombinationen 16 Bit beträgt, also 240 Bit weniger als die Kapazität zum Speichern eines einzelnen Speicherplatzes, kann Solidity verwendet werden, um Variablen in denselben Speicherplatz zu packen und so den Gasverbrauch beim Deployment zu minimieren, da nicht viele Speicherplätze zum Speichern von Statusvariablen benötigt werden.
Durch das Umordnen von Variablendeklarationen kannst du im Durchschnitt 13 % Gas sparen.
Ergebnisse der Variablenverpackung
| Stage | Gasverbrauch | Einsparungen |
|---|---|---|
| Voroptimierung | 1.678 | - |
| Nach der Optimierung | 1.447 | 13 % |
Externen Sichtbarkeitsmodifikator auswählen
Um das Gas für Smart Contracts zu maximieren, ist es sinnvoll, die richtige Sichtbarkeit der Funktionen auszuwählen. Externe Sichtbarkeitsmodifikatoren können aufgrund der Art und Weise, wie eine öffentliche Funktion ihre Argumente verwaltet und wie Daten an die Funktionen übertragen werden, effizienter im Gasverbrauch sein als öffentliche.
Externe Funktionen können auf calldata zugreifen, einen schreibgeschützten temporären Speicherplatz in der EVM, der die Parameter eines Funktionsaufrufs enthält. Öffentliche Funktionen können aufgerufen werden:
- Extern (wenn sie von einer Transaktion über calldata aufgerufen werden)
- Intern (wenn sie innerhalb eines Vertrags mit calldata aufgerufen werden)
Da Funktionen öffentlich sind, müssen sie Arrays im Speicher empfangen, was bei großen Arrays hohe Gas-Kosten verursachen kann. Durch das Umschalten von Funktionen auf extern kann der Empfang von Arrays in Calldata ermöglicht werden, was bei großen Arrays kostengünstiger ist und durchschnittlich 0,3 Prozent der Gas-Einheiten pro Aufruf einspart.
Speicher zum erneuten Lesen desselben Werts
Eine gute Technik, um Gas zu sparen, ist, denselben Wert im Speicher nicht mehrmals zu lesen. Das Lesen aus dem Speicher ist teurer als das Lesen aus dem Arbeitsspeicher. Das mehrmalige erneute Lesen derselben Variablen im Speicher und das Schreiben in den Speicher, wenn es nicht nötig ist, kann in Smart Contracts eine Verschwendung von Gas sein.
Das kannst du vermeiden, indem du Variablen definierst, die zu Beginn der Funktionen im Speicher sind, und ihnen Werte von Zahlenvariablen gibst.
Das spart 17 % des Gasverbrauchs der sumNumbers-Funktionen, aber je größer das Array, desto größer sind die Iterationszahlen und der Gasverbrauch.
Zwischenspeichern von Variablen-Ergebnissen
| Stage | Gasverbrauch | Einsparungen |
|---|---|---|
| Vor der Optimierung | 3.527 | - |
| Nach der Optimierung | 2.905 | 17 % |
Initialisiere Variablen nicht mit Standardwerten.
Wenn du Variablen des Zustands deklarierst, ohne sie zu initialisieren (d. h. ohne ihnen einen Anfangswert zuzuweisen), werden diese Variablen automatisch mit Standardwerten initialisiert:
- uint: 0
- bool: false
- Adresse: Adresse(0)
Letzteres ist günstiger, als Werte als Standard festzulegen und sie einfach zu aktualisieren, wenn der Benutzer mit dem System interagiert, was nicht gas-effizient ist. Ohne Variableninitialisierung zeigt die Optimierung eine durchschnittliche Gaseinsparung von 4 % an.
Unterstütze die Optimierung des Solidity-Compilers
Solidity hat Compiler mit Einstellungen, die man leicht ändern kann, um den kompilierten Code zu optimieren. Der Optimierungsbuild läuft ein paar hundert Mal, optimiert den Code und wandelt ihn in günstigere Formen um, die weniger Gas zum Ausführen brauchen.
Compiler können angepasst werden, um einen guten Kompromiss zwischen Bereitstellungs- und Laufzeitkosten zu finden. Leg die geschätzte Anzahl der auszuführenden Verträge mit den Run-Befehlen fest:
- Höhere Stufen sind optimal in Bezug auf niedrige Gaspreise bei der Durchsetzung des Vertrags
- Weniger Zahlen sind optimal, um die Gas-Kosten beim Vertragsabschluss zu senken
Optimize flags plus the value of run=200 sagt den Compilern, dass sie den Code optimieren sollen, um Gas zu sparen, wenn die Funktionen von incrementCount 200 Mal ausgeführt werden. Sortiere solche Einstellungen nach den speziellen Anforderungen der Anwendung.
Bonus Solidity Gas-Optimierung: Assembler
Wenn du Smart Contracts in Solidity schreibst, werden sie zu Bytecodes kompiliert, einer Reihe von EVM-Opcodes. Durch Assembler kannst du einfach Code schreiben, der auf Ebenen läuft, die besser mit Opcodes kompatibel sind, und in manchen Fällen ist die Möglichkeit, Opcodes manuell zu optimieren, ein Vorteil gegenüber Solidity-Bytecode.
Auch wenn es nicht ganz einfach ist, Code auf so niedriger Ebene zu schreiben, hat es den Vorteil, dass man Opcodes manuell optimieren kann, und ist daher im Allgemeinen besser als Solidity-Bytecode. Diese Optimierung sorgt für mehr Effizienz und Effektivität bei der Ausführung des Vertrags.
Selbst in einfachen Fällen, in denen die beiden Funktionen zwei Zahlen addieren sollen, gibt es einige Unterschiede zwischen den Versionen in Solidity und Assembly, aber die Assembly-Versionen sind günstiger.
Die Umsetzung von Projekten auf L2s wie Base bedeutet, dass die Nutzer weniger Kosten für die Nutzung von Protokollen haben, aber es ist die Aufgabe der Entwickler, dafür zu sorgen, dass sie die Gasoptimierungstechniken anwenden. Diese Tipps können die Transaktionskosten erheblich senken, die Skalierbarkeit erhöhen und die Effizienz des Vertrags insgesamt verbessern.
Assembly kann verwendet werden, um die Ausführung von Smart Contracts mit Gas zu optimieren. In manchen Fällen kann es aber sein, dass die Assembly-Versionen zu unsicherem Code führen. Es gibt gute Gründe dafür, die Smart-Contract-Sicherheitsexperten in die Überprüfung der Verträge vor deren Einsatz einzubeziehen.


