Unterschied zwischen require und assert und der Unterschied zwischen revert und throw

Ich habe mir die Dokumente angesehen und suche nach Erläuterungen zum Unterschied zwischen requireand assertund throwand revert.

assert(boolesche Bedingung): Ausführung abbrechen und Zustandsänderungen rückgängig machen, wenn Bedingung falsch ist (für internen Fehler verwenden)

require(bool condition): Ausführung abbrechen und Zustandsänderungen rückgängig machen, wenn Bedingung falsch ist (für falsch formatierte Eingaben verwenden)

Wie ziehen Sie speziell in Bezug auf assertund requiredie Grenze zwischen fehlerhafter Eingabe und internem Fehler?

Vielleicht möchten Sie auch diesen ausgezeichneten Artikel zum Thema lesen: Revert(), Assert() und Require() in Solidity und der neue REVERT-Opcode in EVM

Antworten (5)

Bei der Wahl zwischen assert()und sind zwei Aspekte zu beachtenrequire()

  1. Gaseffizienz
  2. Bytecode-Analyse

1. Gaseffizienz

assert(false)kompiliert zu 0xfe, was ein ungültiger Opcode ist, verbraucht das gesamte verbleibende Gas und macht alle Änderungen rückgängig.

require(false)kompiliert, zu 0xfddem der REVERTOpcode gehört, was bedeutet, dass das verbleibende Gas zurückerstattet wird. Der Opcode kann auch einen Wert zurückgeben (nützlich zum Debuggen), aber ich glaube nicht, dass dies derzeit in Solidity unterstützt wird. (2017-11-21)

2. Bytecode-Analyse

Aus den Dokumenten (Hervorhebung von mir)

Die require-Funktion sollte verwendet werden, um sicherzustellen, dass gültige Bedingungen wie Eingaben oder Vertragsstatusvariablen erfüllt sind, oder um Rückgabewerte von Aufrufen externer Verträge zu validieren. Bei richtiger Anwendung können Analysetools Ihren Vertrag auswerten, um die Bedingungen und Funktionsaufrufe zu identifizieren, die zu einer fehlgeschlagenen Bestätigung führen. Richtig funktionierender Code sollte niemals eine fehlgeschlagene Assertion-Anweisung erreichen; In diesem Fall liegt ein Fehler in Ihrem Vertrag vor, den Sie beheben sollten.

Der obige Auszug ist ein Verweis auf die noch (Stand 2017-11-21) experimentelle und undokumentierte SMTChecker.

Ich verwende ein paar Heuristiken, um mir bei der Entscheidung zu helfen, welche ich verwenden soll.

Verwenden Sie require()für:

  • Benutzereingaben validieren
  • Validieren Sie die Antwort von einem externen Vertrag,
    dh. verwendenrequire(external.send(amount))
  • Validieren Sie Statusbedingungen, bevor Sie Statusänderungsvorgänge ausführen, beispielsweise in einer ownedVertragssituation
  • Im Allgemeinen sollten Sie requirehäufiger verwenden,
  • Im Allgemeinen wird es am Anfang einer Funktion verwendet.

Verwenden Sie assert()für:

  • auf Überlauf/Unterlauf prüfen
  • Invarianten prüfen
  • Validieren Sie den Vertragsstatus, nachdem Sie Änderungen vorgenommen haben
  • Vermeiden Sie Bedingungen, die niemals möglich sein sollten.
  • Im Allgemeinen sollten Sie es assertseltener verwenden
  • Im Allgemeinen wird es gegen Ende Ihrer Funktion verwendet.

Im Grunde assertist es nur da, um zu verhindern, dass etwas wirklich Schlimmes passiert, aber es sollte nicht möglich sein, dass die Bedingung als falsch ausgewertet wird.

Historischer Hinweis:

Die Funktionen require()und assert()wurden Solidity vor der Byzantium-Fork hinzugefügt, in v0.4.10. Vor Byzanz verhielten sie sich identisch, wurden aber bereits zu unterschiedlichen Opcodes kompiliert. Dies bedeutete, dass sich einige Verträge, die vor Byzanz eingesetzt wurden , nach dem Fork anders verhielten , wobei der Hauptunterschied darin bestand, dass mit der Rückerstattung von nicht verbrauchtem Gas begonnen wurde.

Aber beide assert()und require()Rollback-Änderungen werden in die Blockchain geschrieben, sodass balance[_to] =balance[_from] +_valuesie zurückgesetzt werden, wenn eine assert()Bedingung require()ausgelöst wird, nicht wahr? (Ich spreche nach der Hardfork im letzten Jahr). Oder wird assert()immer wieder etwas geändert?
Beide Rollback-Änderungen, die im Abschnitt Gaseffizienz angegeben sind.
Ich verstehe den Sinn der Verwendung von assert nicht mehr, da ich genau das gleiche Verhalten mit require habe, aber ich habe die Möglichkeit, etwas Gas zurückzuerstatten, was ist der Zweck?
Der Unterschied ist auch semantisch, dh. "Diese Bedingung sollte nicht einmal erreichbar sein. Versuchen Sie, diesen Code mit einer aktuellen Version von zu kompilieren solc: gist.github.com/maurelian/02904ae729fb11213cde20ba05a202e6 Es wird Sie warnen, dass es möglich ist, dass die zweite Assertion-Anweisung für bestimmte Werte wahr ist.

Ich verwende requirefür die Eingabevalidierung, da es etwas effizienter ist als if/throw.

function foo(uint amount) {
    require(amount < totalAmount);
    ...
}

Wo as asserteher zum Abfangen von Laufzeitfehlern verwendet werden sollte:

function foo(uint amount) {
    ...
    __check = myAmount;
        myAmount -= amount;
    assert(myAmount < __check);
    ...
}

revertwird Änderungen rückgängig machen und ungenutztes Gas in einer späteren Version von Ethereum zurückerstatten, aber ATM verhält sich genauso wie Throw.

Könnte ich auch einfach assert(myAmount-amount<myAmount) schreiben und die Variable __check überspringen? was ist besser, warum/warum nicht?
Es ist viel sicherer, keine unnötige Verdoppelung der Logik in der Statusmutation und im assertArgument zu haben. Dies könnte zu einem Debugging-Albtraum führen, bei dem die Operationen für den Zustand verkettet, aber für die Bestätigung vergessen wurden.
Aber beide assert()und require()Rollback-Änderungen werden in die Blockchain geschrieben, sodass balance[_to] =balance[_from] +_valuesie zurückgesetzt werden, wenn eine assert()Bedingung require()ausgelöst wird, nicht wahr? (Ich spreche nach der Hardfork im letzten Jahr). Oder wird assert()immer wieder etwas geändert? Aber beide assert()und require()Rollback-Änderungen werden in die Blockchain geschrieben, sodass balance[_to] =balance[_from] +_valuesie zurückgesetzt werden, wenn eine assert()Bedingung require()ausgelöst wird, nicht wahr? (Ich spreche nach der Hardfork im letzten Jahr). Oder wird assert()immer wieder etwas geändert?

Ich denke, keine der Antworten ist richtig.

assertist für Bedingungen reserviert, mit denen erwartet wird, dass statische Codeanalysetools (möglicherweise Solidity-Compiler in zukünftigen Versionen) die Fehlerwarnung des Entwicklers zur Kompilierzeit erkennen können.

requireist reserviert für Fehlerzustände falscher Eingabedaten für Funktionen (im Vergleich zu erwarteten/gültigen Eingabedaten), die erst zur Ausführungszeit erkannt werden können. Dies entspricht Funktionsvoraussetzungen in der Programmiersprache Argot. Der Compiler kann aufgrund der unendlichen Möglichkeiten der Eingabedaten nicht helfen.

throwwird zugunsten des Zurücksetzens verworfen.

revertist für Fehlerbedingungen reserviert, die sich auf die Geschäftslogik auswirken. Zum Beispiel sendet jemand eine Stimme, wenn die Abstimmung bereits abgeschlossen ist.

requireund revertsind in Bezug auf die interne EVM-Implementierung größtenteils ähnlich, aber Entwickler werden den Unterschied zu schätzen wissen.

Sehr aktuelle Antwort. +1

Asserteignet sich zur Überprüfung von Zuständen, die nicht eintreten sollen, aber eintreten.

Requireeignet sich zur Überprüfung auf unerwünschte Zustände, die auftreten können.

Solidity hat einen SMTChecker , der die Verwendung assertsehr cool macht, weil er beweisen kann, dass Ihre Invarianten wahr sind:

Solidity implementiert einen formalen Verifikationsansatz, der auf SMT (Satisfiability Modulo Theories) und Hornlösung basiert . Das SMTChecker-Modul versucht automatisch zu beweisen, dass der Code die Spezifikation erfüllt, die durch die Anweisungen requireund angegeben wird. assertDas heißt, es betrachtet requireAussagen als Annahmen und versucht zu beweisen, dass die Bedingungen innerhalb assertvon Aussagen immer wahr sind. Wenn ein Behauptungsfehler gefunden wird, kann dem Benutzer ein Gegenbeispiel gegeben werden, das zeigt, wie die Behauptung verletzt werden kann. Wenn der SMTChecker für eine Eigenschaft keine Warnung ausgibt, bedeutet dies, dass die Eigenschaft sicher ist.

Sie sollten verwenden assert, um verletzte Invarianten zu finden.

Sie asserthelfen Ihnen beim Fangen, wenn das Unmögliche passiert.

Das SMTChecker-Tutorial bietet ein gutes Beispiel assertdafür, das zu lang ist, um es hier aufzunehmen: Es wird dringend empfohlen, es zu lesen.

Wenn der SMTChecker Sie dazu bringt, mit der Verwendung von zu beginnen, assertist das eine sehr gute Sache. Aber denken Sie daran, dass sie für Invarianten sind , wie die Solidity-Dokumentation erwähnt:

Assert sollte nur zum Testen auf interne Fehler und zum Überprüfen von Invarianten verwendet werden. Korrekt funktionierender Code sollte niemals eine Panik erzeugen, nicht einmal bei ungültiger externer Eingabe. Wenn dies passiert, dann gibt es einen Fehler in Ihrem Vertrag, den Sie beheben sollten. Sprachanalysetools können Ihren Vertrag auswerten, um die Bedingungen und Funktionsaufrufe zu identifizieren, die eine Panik auslösen.