Zum Hauptinhalt springen

ERC-20-Vertrag – Schritt-für-Schritt-Anleitung

Solidity
erc-20
Anfänger
Ori Pomerantz
9. März 2021
27 Minuten Lesezeit

Einführung

Eine der häufigsten Anwendungen für Ethereum ist die Erstellung eines handelbaren Tokens durch eine Gruppe, gewissermaßen ihre eigene Währung. Diese Token folgen typischerweise einem Standard, dem ERC-20. Dieser Standard macht es möglich, Werkzeuge wie Liquiditätspools und Wallets zu schreiben, die mit allen ERC-20-Token funktionieren. In diesem Artikel werden wir die OpenZeppelin Solidity ERC20-Implementierung (opens in a new tab) sowie die Schnittstellendefinition (opens in a new tab) analysieren.

Dies ist ein kommentierter Quellcode. Wenn Sie ERC-20 implementieren möchten, lesen Sie dieses Tutorial (opens in a new tab).

Die Schnittstelle

Der Zweck eines Standards wie ERC-20 ist es, viele Token-Implementierungen zu ermöglichen, die über Anwendungen hinweg interoperabel sind, wie Wallets und dezentralisierte Börsen. Um das zu erreichen, erstellen wir eine Schnittstelle (opens in a new tab). Jeder Code, der den Token-Vertrag nutzen muss, kann dieselben Definitionen in der Schnittstelle verwenden und mit allen Token-Verträgen kompatibel sein, die diese nutzen, sei es ein Wallet wie MetaMask, eine Dapp wie etherscan.io oder ein anderer Vertrag wie ein Liquiditätspool.

Illustration der ERC-20-Schnittstelle

Wenn Sie ein erfahrener Programmierer sind, erinnern Sie sich wahrscheinlich daran, ähnliche Konstrukte in Java (opens in a new tab) oder sogar in C-Header-Dateien (opens in a new tab) gesehen zu haben.

Dies ist eine Definition der ERC-20-Schnittstelle (opens in a new tab) von OpenZeppelin. Es ist eine Übersetzung des menschenlesbaren Standards (opens in a new tab) in Solidity-Code. Natürlich definiert die Schnittstelle selbst nicht, wie etwas zu tun ist. Das wird im Vertragsquellcode weiter unten erklärt.

 

// SPDX-License-Identifier: MIT

Solidity-Dateien sollten eine Lizenzkennung enthalten. Sie können die Liste der Lizenzen hier einsehen (opens in a new tab). Wenn Sie eine andere Lizenz benötigen, erklären Sie dies einfach in den Kommentaren.

 

pragma solidity >=0.6.0 <0.8.0;

Die Sprache Solidity entwickelt sich immer noch schnell weiter, und neue Versionen sind möglicherweise nicht mit altem Code kompatibel (siehe hier (opens in a new tab)). Daher ist es eine gute Idee, nicht nur eine Mindestversion der Sprache anzugeben, sondern auch eine Höchstversion, die neueste, mit der Sie den Code getestet haben.

 

/* *
 * @dev Schnittstelle des ERC20-Standards, wie im EIP definiert. */



Das @dev im Kommentar ist Teil des NatSpec-Formats (opens in a new tab), das verwendet wird, um Dokumentation aus dem Quellcode zu erstellen.

 

interface IERC20 {

Konventionsgemäß beginnen Schnittstellennamen mit I.

 

    /* *
     * @dev Gibt die Menge der existierenden Token zurück. */
    


    function totalSupply() external view returns (uint256);

Diese Funktion ist external, was bedeutet, dass sie nur von außerhalb des Vertrags aufgerufen werden kann (opens in a new tab). Sie gibt das Gesamtangebot an Token im Vertrag zurück. Dieser Wert wird unter Verwendung des häufigsten Typs in Ethereum zurückgegeben, vorzeichenlose 256 Bits (256 Bits ist die native Wortgröße der Ethereum Virtual Machine). Diese Funktion ist auch eine view, was bedeutet, dass sie den Zustand nicht ändert, sodass sie auf einem einzelnen Blockchain-Knoten ausgeführt werden kann, anstatt dass jeder Blockchain-Knoten in der Blockchain sie ausführt. Diese Art von Funktion generiert keine Transaktion und kostet kein Gas.

Hinweis: Theoretisch könnte es so aussehen, als könnte der Ersteller eines Vertrags betrügen, indem er ein kleineres Gesamtangebot als den tatsächlichen Wert zurückgibt, wodurch jeder Token wertvoller erscheint, als er tatsächlich ist. Diese Befürchtung ignoriert jedoch die wahre Natur der Blockchain. Alles, was auf der Blockchain passiert, kann von jedem Blockchain-Knoten verifiziert werden. Um dies zu erreichen, sind der Maschinensprache-Code und der Speicher jedes Vertrags auf jedem Blockchain-Knoten verfügbar. Obwohl Sie nicht verpflichtet sind, den Solidity-Code für Ihren Vertrag zu veröffentlichen, würde Sie niemand ernst nehmen, es sei denn, Sie veröffentlichen den Quellcode und die Version von Solidity, mit der er kompiliert wurde, damit er gegen den von Ihnen bereitgestellten Maschinensprache-Code verifiziert werden kann. Siehe zum Beispiel diesen Vertrag (opens in a new tab).

 

    /* *
     * @dev Gibt die Menge der Token zurück, die `account` gehören. */
    


    function balanceOf(address account) external view returns (uint256);

Wie der Name schon sagt, gibt balanceOf den Kontostand eines Kontos zurück. Ethereum-Konten werden in Solidity mit dem Typ address identifiziert, der 160 Bits umfasst. Sie ist ebenfalls external und view.

 

Die Funktion transfer überträgt Token vom Aufrufer an eine andere Adresse. Dies beinhaltet eine Zustandsänderung, ist also keine view. Wenn ein Benutzer diese Funktion aufruft, erstellt sie eine Transaktion und kostet Gas. Sie gibt auch ein Ereignis, Transfer, aus, um jeden auf der Blockchain über das Ereignis zu informieren.

Die Funktion hat zwei Arten von Ausgaben für zwei verschiedene Arten von Aufrufern:

  • Benutzer, die die Funktion direkt über eine Benutzeroberfläche aufrufen. Typischerweise reicht der Benutzer eine Transaktion ein und wartet nicht auf eine Antwort, was eine unbestimmte Zeit in Anspruch nehmen könnte. Der Benutzer kann sehen, was passiert ist, indem er nach der Transaktionsquittung sucht (die durch den Transaktions-Hash identifiziert wird) oder nach dem Ereignis Transfer sucht.
  • Andere Verträge, die die Funktion als Teil einer Gesamttransaktion aufrufen. Diese Verträge erhalten das Ergebnis sofort, da sie in derselben Transaktion ausgeführt werden, sodass sie den Rückgabewert der Funktion verwenden können.

Die gleiche Art von Ausgabe wird von den anderen Funktionen erstellt, die den Zustand des Vertrags ändern.

 

Freigaben (Allowances) erlauben es einem Konto, einige Token auszugeben, die einem anderen Eigentümer gehören. Dies ist zum Beispiel nützlich für Verträge, die als Verkäufer agieren. Verträge können nicht auf Ereignisse überwachen. Wenn also ein Käufer Token direkt an den Verkäufervertrag übertragen würde, wüsste dieser Vertrag nicht, dass er bezahlt wurde. Stattdessen erlaubt der Käufer dem Verkäufervertrag, einen bestimmten Betrag auszugeben, und der Verkäufer überträgt diesen Betrag. Dies geschieht über eine Funktion, die der Verkäufervertrag aufruft, sodass der Verkäufervertrag wissen kann, ob er erfolgreich war.

Die Funktion allowance lässt jeden abfragen, wie hoch die Freigabe ist, die eine Adresse (owner) einer anderen Adresse (spender) zum Ausgeben gewährt.

 

Die Funktion approve erstellt eine Freigabe. Stellen Sie sicher, dass Sie die Nachricht darüber lesen, wie sie missbraucht werden kann. In Ethereum kontrollieren Sie die Reihenfolge Ihrer eigenen Transaktionen, aber Sie können nicht die Reihenfolge kontrollieren, in der die Transaktionen anderer Personen ausgeführt werden, es sei denn, Sie reichen Ihre eigene Transaktion erst ein, wenn Sie sehen, dass die Transaktion der anderen Seite stattgefunden hat.

 

Schließlich wird transferFrom vom Ausgebenden verwendet, um die Freigabe tatsächlich auszugeben.

 

Diese Ereignisse werden ausgegeben, wenn sich der Zustand des ERC-20-Vertrags ändert.

Der eigentliche Vertrag

Dies ist der eigentliche Vertrag, der den ERC-20-Standard implementiert, von hier entnommen (opens in a new tab). Er ist nicht dazu gedacht, so wie er ist verwendet zu werden, aber Sie können davon erben (opens in a new tab), um ihn zu etwas Brauchbarem zu erweitern.

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;

 

Import-Anweisungen

Zusätzlich zu den obigen Schnittstellendefinitionen importiert die Vertragsdefinition zwei weitere Dateien:


import "../../GSN/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
  • GSN/Context.sol sind die Definitionen, die erforderlich sind, um OpenGSN (opens in a new tab) zu verwenden, ein System, das es Benutzern ohne Ether ermöglicht, die Blockchain zu nutzen. Beachten Sie, dass dies eine alte Version ist. Wenn Sie OpenGSN integrieren möchten, verwenden Sie dieses Tutorial (opens in a new tab).
  • Die SafeMath-Bibliothek (opens in a new tab), die arithmetische Überläufe/Unterläufe für Solidity-Versionen <0.8.0 verhindert. In Solidity ≥0.8.0 werden arithmetische Operationen bei Überlauf/Unterlauf automatisch rückgängig gemacht, was SafeMath überflüssig macht. Dieser Vertrag verwendet SafeMath zur Abwärtskompatibilität mit älteren Compiler-Versionen.

 

Dieser Kommentar erklärt den Zweck des Vertrags.

Vertragsdefinition

contract ERC20 is Context, IERC20 {

Diese Zeile gibt die Vererbung an, in diesem Fall von IERC20 von oben und Context, für OpenGSN.

 


    using SafeMath for uint256;

Diese Zeile hängt die SafeMath-Bibliothek an den Typ uint256 an. Sie können diese Bibliothek hier (opens in a new tab) finden.

Variablendefinitionen

Diese Definitionen spezifizieren die Zustandsvariablen des Vertrags. Diese Variablen sind als private deklariert, aber das bedeutet nur, dass andere Verträge auf der Blockchain sie nicht lesen können. Es gibt keine Geheimnisse auf der Blockchain, die Software auf jedem Blockchain-Knoten hat den Zustand jedes Vertrags bei jedem Block. Konventionsgemäß werden Zustandsvariablen _<etwas> genannt.

Die ersten beiden Variablen sind Mappings (opens in a new tab), was bedeutet, dass sie sich ungefähr so verhalten wie assoziative Arrays (opens in a new tab), außer dass die Schlüssel numerische Werte sind. Speicher wird nur für Einträge zugewiesen, die Werte haben, die vom Standard (Null) abweichen.

    mapping (address => uint256) private _balances;

Das erste Mapping, _balances, sind Adressen und ihre jeweiligen Kontostände dieses Tokens. Um auf den Kontostand zuzugreifen, verwenden Sie diese Syntax: _balances[<Adresse>].

 

    mapping (address => mapping (address => uint256)) private _allowances;

Diese Variable, _allowances, speichert die zuvor erklärten Freigaben. Der erste Index ist der Eigentümer der Token, und der zweite ist der Vertrag mit der Freigabe. Um auf den Betrag zuzugreifen, den Adresse A vom Konto der Adresse B ausgeben kann, verwenden Sie _allowances[B][A].

 

    uint256 private _totalSupply;

Wie der Name schon sagt, verfolgt diese Variable das Gesamtangebot an Token.

 

    string private _name;
    string private _symbol;
    uint8 private _decimals;

Diese drei Variablen werden verwendet, um die Lesbarkeit zu verbessern. Die ersten beiden sind selbsterklärend, aber _decimals ist es nicht.

Einerseits hat Ethereum keine Fließkomma- oder Bruchvariablen. Andererseits mögen es Menschen, Token teilen zu können. Ein Grund, warum sich die Menschen auf Gold als Währung geeinigt haben, war, dass es schwer war, Wechselgeld zu geben, wenn jemand den Gegenwert einer Ente in Kuh kaufen wollte.

Die Lösung besteht darin, Ganzzahlen zu verfolgen, aber anstelle des echten Tokens einen Bruchteil-Token zu zählen, der fast wertlos ist. Im Fall von Ether wird der Bruchteil-Token Wei genannt, und 10^18 Wei entsprechen einem ETH. Zum Zeitpunkt des Schreibens sind 10.000.000.000.000 Wei ungefähr ein US- oder Euro-Cent.

Anwendungen müssen wissen, wie der Token-Kontostand angezeigt werden soll. Wenn ein Benutzer 3.141.000.000.000.000.000 Wei hat, sind das 3,14 ETH? 31,41 ETH? 3.141 ETH? Im Fall von Ether ist definiert, dass 10^18 Wei einem ETH entsprechen, aber für Ihren Token können Sie einen anderen Wert wählen. Wenn das Teilen des Tokens keinen Sinn ergibt, können Sie einen _decimals-Wert von null verwenden. Wenn Sie denselben Standard wie ETH verwenden möchten, verwenden Sie den Wert 18.

Der Konstruktor

Der Konstruktor wird aufgerufen, wenn der Vertrag zum ersten Mal erstellt wird. Konventionsgemäß werden Funktionsparameter <etwas>_ genannt.

Benutzeroberflächenfunktionen

Diese Funktionen, name, symbol und decimals, helfen Benutzeroberflächen, über Ihren Vertrag Bescheid zu wissen, damit sie ihn richtig anzeigen können.

Der Rückgabetyp ist string memory, was bedeutet, dass ein String zurückgegeben wird, der im Speicher (Memory) abgelegt ist. Variablen, wie z. B. Strings, können an drei Orten gespeichert werden:

LebensdauerVertragszugriffGaskosten
MemoryFunktionsaufrufLesen/SchreibenZehner oder Hunderter (höher für höhere Speicherorte)
CalldataFunktionsaufrufNur LesenKann nicht als Rückgabetyp verwendet werden, nur als Parameter
StorageBis zur ÄnderungLesen/SchreibenHoch (800 für Lesen, 20k für Schreiben)

In diesem Fall ist memory die beste Wahl.

Token-Informationen lesen

Dies sind Funktionen, die Informationen über den Token liefern, entweder das Gesamtangebot oder den Kontostand eines Kontos.

    /* *
     * @dev Siehe {IERC20-totalSupply}. */
    


    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

Die Funktion totalSupply gibt das Gesamtangebot an Token zurück.

 

    /* *
     * @dev Siehe {IERC20-balanceOf}. */
    


    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

Lesen Sie den Kontostand eines Kontos. Beachten Sie, dass es jedem erlaubt ist, den Kontostand eines anderen abzurufen. Es hat keinen Sinn zu versuchen, diese Informationen zu verbergen, da sie ohnehin auf jedem Blockchain-Knoten verfügbar sind. Es gibt keine Geheimnisse auf der Blockchain.

Token übertragen

Die Funktion transfer wird aufgerufen, um Token vom Konto des Senders auf ein anderes zu übertragen. Beachten Sie, dass, obwohl sie einen booleschen Wert zurückgibt, dieser Wert immer true ist. Wenn die Übertragung fehlschlägt, macht der Vertrag den Aufruf rückgängig (revert).

 

        _transfer(_msgSender(), recipient, amount);
        return true;
    }

Die Funktion _transfer erledigt die eigentliche Arbeit. Es ist eine private Funktion, die nur von anderen Vertragsfunktionen aufgerufen werden kann. Konventionsgemäß werden private Funktionen _<etwas> genannt, genau wie Zustandsvariablen.

Normalerweise verwenden wir in Solidity msg.sender für den Absender der Nachricht. Das bricht jedoch OpenGSN (opens in a new tab). Wenn wir etherlose Transaktionen mit unserem Token zulassen wollen, müssen wir _msgSender() verwenden. Es gibt msg.sender für normale Transaktionen zurück, aber für etherlose gibt es den ursprünglichen Unterzeichner zurück und nicht den Vertrag, der die Nachricht weitergeleitet hat.

Freigabefunktionen

Dies sind die Funktionen, die die Freigabefunktionalität implementieren: allowance, approve, transferFrom und _approve. Darüber hinaus geht die OpenZeppelin-Implementierung über den grundlegenden Standard hinaus und enthält einige Funktionen, die die Sicherheit verbessern: increaseAllowance und decreaseAllowance.

Die allowance-Funktion

    /* *
     * @dev Siehe {IERC20-allowance}. */
    


    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

Die Funktion allowance ermöglicht es jedem, jede Freigabe zu überprüfen.

Die approve-Funktion

Diese Funktion wird aufgerufen, um eine Freigabe zu erstellen. Sie ist ähnlich wie die obige transfer-Funktion:

  • Die Funktion ruft lediglich eine interne Funktion auf (in diesem Fall _approve), die die eigentliche Arbeit erledigt.
  • Die Funktion gibt entweder true zurück (wenn erfolgreich) oder macht den Aufruf rückgängig (wenn nicht).

 

        _approve(_msgSender(), spender, amount);
        return true;
    }

Wir verwenden interne Funktionen, um die Anzahl der Stellen zu minimieren, an denen Zustandsänderungen auftreten. Jede Funktion, die den Zustand ändert, ist ein potenzielles Sicherheitsrisiko, das auf Sicherheit geprüft werden muss. Auf diese Weise haben wir weniger Chancen, Fehler zu machen.

Die transferFrom-Funktion

Dies ist die Funktion, die ein Ausgebender aufruft, um eine Freigabe auszugeben. Dies erfordert zwei Operationen: den ausgegebenen Betrag übertragen und die Freigabe um diesen Betrag reduzieren.

 

Der Funktionsaufruf a.sub(b, "message") tut zwei Dinge. Erstens berechnet er a-b, was die neue Freigabe ist. Zweitens überprüft er, ob dieses Ergebnis nicht negativ ist. Wenn es negativ ist, wird der Aufruf mit der bereitgestellten Nachricht rückgängig gemacht. Beachten Sie, dass bei einem Revert eines Aufrufs alle zuvor während dieses Aufrufs durchgeführten Verarbeitungen ignoriert werden, sodass wir den _transfer nicht rückgängig machen müssen.

        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,
             "ERC20: transfer amount exceeds allowance"));
        return true;
    }

OpenZeppelin-Sicherheitserweiterungen

Es ist gefährlich, eine Freigabe ungleich Null auf einen anderen Wert ungleich Null zu setzen, da Sie nur die Reihenfolge Ihrer eigenen Transaktionen kontrollieren, nicht die von jemand anderem. Stellen Sie sich vor, Sie haben zwei Benutzer, Alice, die naiv ist, und Bill, der unehrlich ist. Alice möchte eine Dienstleistung von Bill, von der sie glaubt, dass sie fünf Token kostet – also gibt sie Bill eine Freigabe von fünf Token.

Dann ändert sich etwas und Bills Preis steigt auf zehn Token. Alice, die die Dienstleistung immer noch möchte, sendet eine Transaktion, die Bills Freigabe auf zehn setzt. In dem Moment, in dem Bill diese neue Transaktion im Transaktionspool sieht, sendet er eine Transaktion, die Alices fünf Token ausgibt und einen viel höheren Gaspreis hat, damit sie schneller gemint wird. Auf diese Weise kann Bill zuerst fünf Token ausgeben und dann, sobald Alices neue Freigabe gemint ist, zehn weitere für einen Gesamtpreis von fünfzehn Token ausgeben, mehr als Alice autorisieren wollte. Diese Technik wird Front-Running (opens in a new tab) genannt.

Alice-TransaktionAlice-NonceBill-TransaktionBill-NonceBills FreigabeBills Gesamteinkommen von Alice
approve(Bill, 5)1050
transferFrom(Alice, Bill, 5)10,12305
approve(Bill, 10)11105
transferFrom(Alice, Bill, 10)10,124015

Um dieses Problem zu vermeiden, ermöglichen Ihnen diese beiden Funktionen (increaseAllowance und decreaseAllowance), die Freigabe um einen bestimmten Betrag zu ändern. Wenn Bill also bereits fünf Token ausgegeben hätte, könnte er nur noch fünf weitere ausgeben. Abhängig vom Timing gibt es zwei Möglichkeiten, wie dies funktionieren kann, die beide damit enden, dass Bill nur zehn Token erhält:

A:

Alice-TransaktionAlice-NonceBill-TransaktionBill-NonceBills FreigabeBills Gesamteinkommen von Alice
approve(Bill, 5)1050
transferFrom(Alice, Bill, 5)10,12305
increaseAllowance(Bill, 5)110+5 = 55
transferFrom(Alice, Bill, 5)10,124010

B:

Alice-TransaktionAlice-NonceBill-TransaktionBill-NonceBills FreigabeBills Gesamteinkommen von Alice
approve(Bill, 5)1050
increaseAllowance(Bill, 5)115+5 = 100
transferFrom(Alice, Bill, 10)10,124010

Die Funktion a.add(b) ist eine sichere Addition. In dem unwahrscheinlichen Fall, dass a+b>=2^256, kommt es nicht zu einem Überlauf (Wrap-around), wie es bei einer normalen Addition der Fall ist.

Funktionen, die Token-Informationen ändern

Dies sind die vier Funktionen, die die eigentliche Arbeit erledigen: _transfer, _mint, _burn und _approve.

Die _transfer-Funktion

Diese Funktion, _transfer, überträgt Token von einem Konto auf ein anderes. Sie wird sowohl von transfer (für Übertragungen vom eigenen Konto des Senders) als auch von transferFrom (für die Verwendung von Freigaben zur Übertragung vom Konto eines anderen) aufgerufen.

 

        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

Niemand besitzt tatsächlich die Adresse Null in Ethereum (das heißt, niemand kennt einen Private-Key, dessen passender Public-Key in die Null-Adresse umgewandelt wird). Wenn Leute diese Adresse verwenden, handelt es sich normalerweise um einen Softwarefehler – daher schlagen wir fehl, wenn die Null-Adresse als Sender oder Empfänger verwendet wird.

 

        _beforeTokenTransfer(sender, recipient, amount);

Es gibt zwei Möglichkeiten, diesen Vertrag zu nutzen:

  1. Verwenden Sie ihn als Vorlage für Ihren eigenen Code
  2. Erben Sie davon (opens in a new tab) und überschreiben Sie nur die Funktionen, die Sie ändern müssen

Die zweite Methode ist viel besser, da der OpenZeppelin ERC-20-Code bereits geprüft wurde und sich als sicher erwiesen hat. Wenn Sie Vererbung verwenden, ist klar, welche Funktionen Sie ändern, und um Ihrem Vertrag zu vertrauen, müssen die Leute nur diese spezifischen Funktionen prüfen.

Es ist oft nützlich, eine Funktion jedes Mal auszuführen, wenn Token den Besitzer wechseln. _transfer ist jedoch eine sehr wichtige Funktion und es ist möglich, sie unsicher zu schreiben (siehe unten), daher ist es am besten, sie nicht zu überschreiben. Die Lösung ist _beforeTokenTransfer, eine Hook-Funktion (opens in a new tab). Sie können diese Funktion überschreiben, und sie wird bei jeder Übertragung aufgerufen.

 

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);

Dies sind die Zeilen, die die Übertragung tatsächlich durchführen. Beachten Sie, dass sich nichts dazwischen befindet und dass wir den übertragenen Betrag vom Sender abziehen, bevor wir ihn dem Empfänger hinzufügen. Dies ist wichtig, denn wenn es in der Mitte einen Aufruf an einen anderen Vertrag gäbe, hätte dieser verwendet werden können, um diesen Vertrag zu betrügen. Auf diese Weise ist die Übertragung atomar, in der Mitte kann nichts passieren.

 

        emit Transfer(sender, recipient, amount);
    }

Geben Sie schließlich ein Transfer-Ereignis aus. Ereignisse sind für Smart Contracts nicht zugänglich, aber Code, der außerhalb der Blockchain ausgeführt wird, kann auf Ereignisse lauschen und darauf reagieren. Zum Beispiel kann ein Wallet verfolgen, wann der Eigentümer mehr Token erhält.

Die _mint- und _burn-Funktionen

Diese beiden Funktionen (_mint und _burn) ändern das Gesamtangebot an Token. Sie sind intern und es gibt keine Funktion, die sie in diesem Vertrag aufruft, daher sind sie nur nützlich, wenn Sie vom Vertrag erben und Ihre eigene Logik hinzufügen, um zu entscheiden, unter welchen Bedingungen neue Token geprägt (mint) oder bestehende verbrannt (burn) werden sollen.

HINWEIS: Jeder ERC-20-Token hat seine eigene Geschäftslogik, die die Token-Verwaltung vorschreibt. Zum Beispiel könnte ein Vertrag mit festem Angebot nur _mint im Konstruktor aufrufen und niemals _burn aufrufen. Ein Vertrag, der Token verkauft, wird _mint aufrufen, wenn er bezahlt wird, und vermutlich irgendwann _burn aufrufen, um eine unkontrollierte Inflation zu vermeiden.

Stellen Sie sicher, dass Sie _totalSupply aktualisieren, wenn sich die Gesamtzahl der Token ändert.

 

Die Funktion _burn ist fast identisch mit _mint, außer dass sie in die andere Richtung geht.

Die _approve-Funktion

Dies ist die Funktion, die tatsächlich Freigaben spezifiziert. Beachten Sie, dass sie es einem Eigentümer ermöglicht, eine Freigabe anzugeben, die höher ist als der aktuelle Kontostand des Eigentümers. Dies ist in Ordnung, da der Kontostand zum Zeitpunkt der Übertragung überprüft wird, wenn er sich von dem Kontostand bei der Erstellung der Freigabe unterscheiden könnte.

 

Geben Sie ein Approval-Ereignis aus. Je nachdem, wie die Anwendung geschrieben ist, kann der Ausgeber-Vertrag entweder vom Eigentümer oder von einem Server, der auf diese Ereignisse lauscht, über die Genehmigung informiert werden.

        emit Approval(owner, spender, amount);
    }

Die Decimals-Variable ändern

Diese Funktion ändert die Variable _decimals, die verwendet wird, um Benutzeroberflächen mitzuteilen, wie der Betrag zu interpretieren ist. Sie sollten sie aus dem Konstruktor aufrufen. Es wäre unehrlich, sie zu einem späteren Zeitpunkt aufzurufen, und Anwendungen sind nicht darauf ausgelegt, damit umzugehen.

Hooks

Dies ist die Hook-Funktion, die während Übertragungen aufgerufen werden soll. Sie ist hier leer, aber wenn Sie möchten, dass sie etwas tut, überschreiben Sie sie einfach.

Fazit

Zur Wiederholung sind hier einige der wichtigsten Ideen in diesem Vertrag (meiner Meinung nach, Ihre wird wahrscheinlich abweichen):

  • Es gibt keine Geheimnisse auf der Blockchain. Jede Information, auf die ein Smart Contract zugreifen kann, ist für die ganze Welt verfügbar.
  • Sie können die Reihenfolge Ihrer eigenen Transaktionen kontrollieren, aber nicht, wann die Transaktionen anderer Personen stattfinden. Dies ist der Grund, warum das Ändern einer Freigabe gefährlich sein kann, da es dem Ausgebenden ermöglicht, die Summe beider Freigaben auszugeben.
  • Werte vom Typ uint256 laufen über (Wrap-around). Mit anderen Worten, 0-1=2^256-1. Wenn das kein gewünschtes Verhalten ist, müssen Sie dies überprüfen (oder die SafeMath-Bibliothek verwenden, die das für Sie erledigt). Beachten Sie, dass sich dies in Solidity 0.8.0 (opens in a new tab) geändert hat.
  • Führen Sie alle Zustandsänderungen eines bestimmten Typs an einem bestimmten Ort durch, da dies die Prüfung (Auditing) erleichtert. Dies ist der Grund, warum wir zum Beispiel _approve haben, das von approve, transferFrom, increaseAllowance und decreaseAllowance aufgerufen wird.
  • Zustandsänderungen sollten atomar sein, ohne eine andere Aktion in ihrer Mitte (wie Sie in _transfer sehen können). Dies liegt daran, dass Sie während der Zustandsänderung einen inkonsistenten Zustand haben. Zum Beispiel existieren zwischen dem Zeitpunkt, an dem Sie vom Kontostand des Senders abziehen, und dem Zeitpunkt, an dem Sie dem Kontostand des Empfängers hinzufügen, weniger Token, als es geben sollte. Dies könnte potenziell missbraucht werden, wenn es dazwischen Operationen gibt, insbesondere Aufrufe an einen anderen Vertrag.

Jetzt, da Sie gesehen haben, wie der OpenZeppelin ERC-20-Vertrag geschrieben ist und insbesondere, wie er sicherer gemacht wird, gehen Sie und schreiben Sie Ihre eigenen sicheren Verträge und Anwendungen.

Sehen Sie hier für mehr von meiner Arbeit (opens in a new tab).

Letzte Aktualisierung der Seite: 15. April 2026

War dieses Tutorial hilfreich?