Smart Contracts verkleinern, um das Größenlimit einzuhalten
Warum gibt es ein Limit?
Am 22. November 2016 (opens in a new tab) führte der Spurious Dragon Hard-Fork EIP-170 (opens in a new tab) ein, was ein Größenlimit für Smart Contracts von 24,576 kb hinzufügte. Für Sie als Solidity-Entwickler bedeutet dies, dass Sie, wenn Sie Ihrem Vertrag immer mehr Funktionen hinzufügen, irgendwann das Limit erreichen und bei der Bereitstellung folgenden Fehler sehen werden:
Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
Dieses Limit wurde eingeführt, um Denial-of-Service-Angriffe (DOS) zu verhindern. Jeder Aufruf eines Vertrags ist in Bezug auf Gas relativ günstig. Die Auswirkungen eines Vertragsaufrufs für Ethereum-Blockchain-Knoten steigen jedoch überproportional in Abhängigkeit von der Größe des aufgerufenen Vertragscodes (Lesen des Codes von der Festplatte, Vorverarbeitung des Codes, Hinzufügen von Daten zum Merkle-Proof). Wann immer Sie eine solche Situation haben, in der der Angreifer nur wenige Ressourcen benötigt, um anderen viel Arbeit zu bereiten, entsteht das Potenzial für DOS-Angriffe.
Ursprünglich war dies weniger ein Problem, da ein natürliches Größenlimit für Verträge das Block-Gaslimit ist. Offensichtlich muss ein Vertrag innerhalb einer Transaktion bereitgestellt werden, die den gesamten Bytecode des Vertrags enthält. Wenn Sie nur diese eine Transaktion in einen Block aufnehmen, können Sie das gesamte Gas aufbrauchen, aber es ist nicht unendlich. Seit dem London-Upgrade kann das Block-Gaslimit je nach Netzwerknachfrage zwischen 15 und 30 Millionen Einheiten variieren.
Im Folgenden werden wir uns einige Methoden ansehen, geordnet nach ihren potenziellen Auswirkungen. Stellen Sie sich das wie beim Abnehmen vor. Die beste Strategie für jemanden, um sein Zielgewicht (in unserem Fall 24 kb) zu erreichen, besteht darin, sich zuerst auf die Methoden mit den größten Auswirkungen zu konzentrieren. In den meisten Fällen reicht es aus, die Ernährung umzustellen, aber manchmal braucht man ein bisschen mehr. Dann könnten Sie etwas Sport (mittlere Auswirkung) oder sogar Nahrungsergänzungsmittel (geringe Auswirkung) hinzufügen.
Große Auswirkungen
Trennen Sie Ihre Verträge
Dies sollte immer Ihr erster Ansatz sein. Wie können Sie den Vertrag in mehrere kleinere aufteilen? Es zwingt Sie im Allgemeinen dazu, sich eine gute Architektur für Ihre Verträge auszudenken. Kleinere Verträge werden aus Sicht der Lesbarkeit des Codes immer bevorzugt. Fragen Sie sich beim Aufteilen von Verträgen:
- Welche Funktionen gehören zusammen? Jede Gruppe von Funktionen ist möglicherweise am besten in einem eigenen Vertrag aufgehoben.
- Welche Funktionen erfordern kein Lesen des Vertragszustands oder nur einer bestimmten Teilmenge des Zustands?
- Können Sie Speicher und Funktionalität trennen?
Bibliotheken
Eine einfache Möglichkeit, Funktionscode vom Speicher zu trennen, ist die Verwendung einer Bibliothek (opens in a new tab). Deklarieren Sie die Bibliotheksfunktionen nicht als intern, da diese während der Kompilierung direkt zum Vertrag hinzugefügt (opens in a new tab) werden. Wenn Sie jedoch öffentliche Funktionen verwenden, befinden sich diese tatsächlich in einem separaten Bibliotheksvertrag. Erwägen Sie die Verwendung von using for (opens in a new tab), um die Nutzung von Bibliotheken komfortabler zu gestalten.
Proxys
Eine fortgeschrittenere Strategie wäre ein Proxy-System. Bibliotheken verwenden im Hintergrund DELEGATECALL, was einfach die Funktion eines anderen Vertrags mit dem Zustand des aufrufenden Vertrags ausführt. Lesen Sie diesen Blogbeitrag (opens in a new tab), um mehr über Proxy-Systeme zu erfahren. Sie bieten Ihnen mehr Funktionalität, z. B. ermöglichen sie die Aktualisierbarkeit, fügen aber auch viel Komplexität hinzu. Ich würde sie nicht nur zur Reduzierung der Vertragsgröße hinzufügen, es sei denn, es ist aus irgendeinem Grund Ihre einzige Option.
Mittlere Auswirkungen
Funktionen entfernen
Dies sollte offensichtlich sein. Funktionen vergrößern einen Vertrag erheblich.
- Extern: Oft fügen wir aus Bequemlichkeitsgründen viele View-Funktionen hinzu. Das ist völlig in Ordnung, bis Sie das Größenlimit erreichen. Dann sollten Sie wirklich darüber nachdenken, alle bis auf die absolut wesentlichen zu entfernen.
- Intern: Sie können auch interne/private Funktionen entfernen und den Code einfach inline einfügen, solange die Funktion nur einmal aufgerufen wird.
Zusätzliche Variablen vermeiden
1function get(uint id) returns (address,address) {2 MyStruct memory myStruct = myStructs[id];3 return (myStruct.addr1, myStruct.addr2);4}1function get(uint id) returns (address,address) {2 return (myStructs[id].addr1, myStructs[id].addr2);3}Eine einfache Änderung wie diese macht einen Unterschied von 0,28 kb. Die Chancen stehen gut, dass Sie viele ähnliche Situationen in Ihren Verträgen finden, und diese können sich wirklich zu erheblichen Mengen summieren.
Fehlermeldungen kürzen
Lange Revert-Nachrichten und insbesondere viele verschiedene Revert-Nachrichten können den Vertrag aufblähen. Verwenden Sie stattdessen kurze Fehlercodes und decodieren Sie diese in Ihrem Vertrag. Eine lange Nachricht könnte viel kürzer werden:
1require(msg.sender == owner, "Only the owner of this contract can call this function");1require(msg.sender == owner, "OW1");Benutzerdefinierte Fehler anstelle von Fehlermeldungen verwenden
Benutzerdefinierte Fehler wurden in Solidity 0.8.4 (opens in a new tab) eingeführt. Sie sind eine großartige Möglichkeit, die Größe Ihrer Verträge zu reduzieren, da sie als Selektoren ABI-codiert sind (genau wie Funktionen).
1error Unauthorized();2
3if (msg.sender != owner) {4 revert Unauthorized();5}Erwägen Sie einen niedrigen Run-Wert im Optimierer
Sie können auch die Optimierer-Einstellungen ändern. Der Standardwert von 200 bedeutet, dass versucht wird, den Bytecode so zu optimieren, als ob eine Funktion 200 Mal aufgerufen wird. Wenn Sie ihn auf 1 ändern, weisen Sie den Optimierer im Grunde an, für den Fall zu optimieren, dass jede Funktion nur einmal ausgeführt wird. Eine für die einmalige Ausführung optimierte Funktion bedeutet, dass sie für die Bereitstellung selbst optimiert ist. Beachten Sie, dass dies die Gaskosten für die Ausführung der Funktionen erhöht, sodass Sie dies möglicherweise nicht tun möchten.
Geringe Auswirkungen
Vermeiden Sie die Übergabe von Structs an Funktionen
Wenn Sie den ABIEncoderV2 (opens in a new tab) verwenden, kann es helfen, keine Structs an eine Funktion zu übergeben. Anstatt den Parameter als Struct zu übergeben, übergeben Sie die erforderlichen Parameter direkt. In diesem Beispiel haben wir weitere 0,1 kb gespart.
1function get(uint id) returns (address,address) {2 return _get(myStruct);3}4
5function _get(MyStruct memory myStruct) private view returns(address,address) {6 return (myStruct.addr1, myStruct.addr2);7}1function get(uint id) returns(address,address) {2 return _get(myStructs[id].addr1, myStructs[id].addr2);3}4
5function _get(address addr1, address addr2) private view returns(address,address) {6 return (addr1, addr2);7}Deklarieren Sie die korrekte Sichtbarkeit für Funktionen und Variablen
- Funktionen oder Variablen, die nur von außen aufgerufen werden? Deklarieren Sie sie als
externalanstatt alspublic. - Funktionen oder Variablen, die nur innerhalb des Vertrags aufgerufen werden? Deklarieren Sie sie als
privateoderinternalanstatt alspublic.
Modifikatoren entfernen
Modifikatoren können, insbesondere wenn sie intensiv genutzt werden, erhebliche Auswirkungen auf die Vertragsgröße haben. Erwägen Sie, sie zu entfernen und stattdessen Funktionen zu verwenden.
1modifier checkStuff() {}2
3function doSomething() checkStuff {}1function checkStuff() private {}2
3function doSomething() { checkStuff(); }Diese Tipps sollten Ihnen helfen, die Vertragsgröße erheblich zu reduzieren. Noch einmal, ich kann es nicht oft genug betonen: Konzentrieren Sie sich immer darauf, Verträge nach Möglichkeit aufzuteilen, um die größten Auswirkungen zu erzielen.
Letzte Aktualisierung der Seite: 3. März 2026