Erweiterbare Smart Contracts

Wenn der Vertragsaussteller eine Möglichkeit haben möchte, den Vertragscode zu aktualisieren, damit Kontodaten und andere Dinge übertragen werden, kann Ethereum dies bereitstellen? Geht das auch ohne Änderung der Vertragsadresse oder muss man immer einen neuen Vertrag aufsetzen?

Gibt es „Annex“-Mechanismen, um einem Vertrag einige neue Funktionen hinzuzufügen, ohne ihn komplett neu schreiben zu müssen?

Dies könnte für die potenziellen Leser dieser interessanten Frage interessant sein: OpenZeppelin hat gerade eine Aktualisierungsfunktion in seinem Smart Contract Wizard hinzugefügt , die es einfach macht, erweiterbare ERC721-, ERC20- und ERC1155-Token mit dem UUPS- oder Transparent-Muster zu generieren.
Wenn jemand wie ich eher ein visueller Lerner ist, war dieses kurze Video hilfreich, als ich meinen Ethereum-Vertrag aktualisierte - youtube.com/watch?v=sQJ-XQBzEuc

Antworten (13)

Ja. Es gibt eine Reihe von Ansätzen, mit denen Sie a auf aktualisieren können Contract1, Contract2wobei der Status (Daten und Kontostand) mit derselben Adresse wie zuvor beibehalten wird.

Wie funktioniert das? Eine Möglichkeit besteht darin, einen Proxy-Vertrag mit einer fallbackFunktion zu verwenden, bei der jeder Methodenaufruf/trx an den Implementierungsvertrag (der die gesamte Logik enthält) delegiert wird.Geben Sie hier die Bildbeschreibung ein

Ein Delegiertenaufruf ähnelt einem regulären Aufruf, außer dass der gesamte Code im Kontext des Aufrufers (Proxy) und nicht des Aufgerufenen (Implementierung) ausgeführt wird. Aus diesem Grund überträgt eine Übertragung im Code des Implementierungsvertrags das Guthaben des Proxys, und alle Lese- oder Schreibvorgänge in den Vertragsspeicher lesen oder schreiben aus dem Speicher des Proxys.

Bei diesem Ansatz interagieren Benutzer nur mit dem Proxy-Vertrag und wir können den Implementierungsvertrag ändern, während wir denselben Proxy-Vertrag beibehalten.

Geben Sie hier die Bildbeschreibung ein

Die fallbackFunktion wird bei jeder Anfrage ausgeführt, leitet die Anfrage an die Implementierung um und gibt den resultierenden Wert zurück (unter Verwendung von Opcodes).

Dies war eine grundlegende Erklärung, die uns ausreicht, um mit erweiterbaren Verträgen zu arbeiten. Falls Sie sich eingehend mit dem Proxy-Vertragscode und verschiedenen Proxy-Mustern befassen möchten, lesen Sie diese Beiträge.

Wie kann ich aktualisierbare Smart Contracts schreiben?

OpenZeppelin bietet großartige CLI-Tools und JS-Bibliotheken , die sich um alle oben genannten komplexen proxyVerträge kümmern, sie mit dem Implementierungsvertrag (Logik) verknüpfen und alle Verträge verwalten, die Sie mithilfe der CLI bereitstellen, um sofort einsatzbereit zu sein.

Das Einzige, was Sie tun müssen, ist, Ihre Verträge zu schreiben und die OpenZeppelin CLI oder Bibliotheken zu verwenden, um die Verträge bereitzustellen.

HINWEIS: Es gibt einige Einschränkungen , die Sie beachten sollten, in Bezug darauf, wie Sie Ihre Verträge schreiben und wie Sie sie aktualisieren sollten. Es gibt auch eine Reihe von Problemumgehungen für diese Einschränkungen in diesem Beitrag .

Was hält in diesem Muster einen böswilligen Köder und Schalter auf? Das heißt, Sie kaufen Ihre Token und vertrauen auf das Guthaben, dann wechselt der Entwickler später zu einem neuen Vertrag, bei dem Ihnen die Token weggenommen werden.
Kaufen Sie die Token erst, wenn Sie dem Entwickler mit dem einzelnen Upgrade das Richtige anvertrauen. Bei einem guten DAO-Projekt werden die Upgrade-Proxy-Schlüssel von einem Multisig oder einer direkten DAO-Abstimmung gesteuert. Aber wenn Sie sich Sorgen über böswillige Köder und Schalter machen, sollten Sie dem Projekt wahrscheinlich gar nicht erst vertrauen, da es Tausende anderer Möglichkeiten gibt, Sie zu vermasseln.
Ich möchte auch klarstellen, dass Sie die Upgrade-Proxy-Verträge einfach selbst bereitstellen können – ein Beispiel finden Sie hier: github.com/Dawn-Protocol/dawn-erc20-erc777
Existieren der Proxy-Vertrag und der Implementierungsvertrag beide in der Kette mit separaten Adressen? Wenn ein Vertrag aktualisiert wird, können Benutzer den älteren Vertrag immer noch direkt unter Umgehung des Proxys anrufen?

Sobald sich ein Vertrag in der Blockchain befindet, ist er endgültig und kann nicht mehr geändert werden. Bestimmte Parameter können natürlich geändert werden, wenn sie über den Originalcode geändert werden dürfen.

Eine Methode zum Aktualisieren von Verträgen ist die Verwendung eines Versionierungssystems. Beispielsweise könnten Sie einen Entryway-Vertrag haben, der alle Anrufe einfach an die neueste Version des Vertrags weiterleitet, wie durch einen aktualisierbaren Adressparameter definiert. Sie könnten auch eine Namensregistrierung verwenden und diese so aktualisieren, dass sie auf die neueste Vertragsversion verweist.

Eine andere Methode besteht darin, Ihren Logikcode in eine Bibliothek zu stellen und dann die CALLCODE-Funktion über Bibliotheken in Solidity zu verwenden, um den Code aufzurufen, der sich an einer bestimmten, aktualisierbaren Adresse befindet. Auf diese Weise bleiben Benutzerdaten zwischen Versionen bestehen. Dies hat die Einschränkung, dass der ABI des Logikvertrags über die Zeit gleich bleiben muss.

Hier ist ein alter Kern, den ich vor einiger Zeit verwendet habe, um die Trennung von Daten und Code zu demonstrieren.

Gehöft bearbeiten:

Beginnend mit der Homestead-Version gibt es jetzt einen DELEGATECALLOpcode. Auf diese Weise können Sie Anrufe im Wesentlichen an einen separaten Vertrag weiterleiten, während Sie die msg.sendergesamte Speicherung beibehalten.

Sie könnten beispielsweise einen Vertrag haben, der dieselbe Adresse und denselben Speicher beibehält, aber alle Anrufe an eine in einer Variablen gespeicherte Adresse weiterleitet:

contract Relay {
    address public currentVersion;
    address public owner;

    function Relay(address initAddr){
        currentVersion = initAddr;
        owner = msg.sender;
    }

    function update(address newAddress){
        if(msg.sender != owner) throw;
        currentVersion = newAddress;
    }

    function(){
        if(!currentVersion.delegatecall(msg.data)) throw;
    }
}
Es könnte auch erwähnenswert sein, Namensauflöser zu erwähnen.
Hier ist ein großartiges Beispiel für diese vollständig ausgearbeitete Idee: gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f
Das ist so schlau und eine Form der Abhängigkeitsinjektion: Der Eingangskontakt ist von der Adresse der aktuellen Version abhängig.
Hallo Tjaden Hess, Jossie Calderon! Ich bin etwas verwirrt darüber, wie ich dieses Konzept tatsächlich umsetzen soll. Ich habe ein einfaches Beispiel mit einer einzigen Vertragsspende erstellt, die aktualisiert werden muss. Wäre jemand so freundlich, einen Blick darauf zu werfen und mir zu sagen, ob ich das Konzept völlig falsch verstehe? gist.github.com/fabdarice/d513d620d9355312d085c7a68e6c6118 Schätzen Sie das sehr, danke!
Hey Tjaden Hess schönes Beispiel, wie kann ich mit diesem Vertrag über web3 umgehen, Angenommen, ich habe einen Vertrag mit dem Namen Vertrag DemoVersion1.sol, der den folgenden Code enthält, contract DemoVersion1 { function checkVersion() returns (uint){ return 1; } }und ich muss den Vertrag auf Version zwei aktualisieren, die den folgenden Code enthält, contract DemoVersion2 { function checkVersion() returns (uint){ return 2; } }wie ich mit dem Aufrufen der Methoden umgehen kann des Vertrages, kann das jemand erklären oder auf das passende Beispiel hinweisen.
@fabdarice Hast du es herausgefunden? Kannst du noch einen funktionierenden Gist posten?
@fabdarice Also, die Größe der Daten, die eine Funktion im Voraus kennen sollte? Was ist, wenn ich ein dynamisches Array zurückgebe?
Das ist richtig, Sie müssen die Größe der Daten kennen. Meines Wissens wird es in Solidity nicht empfohlen, ein dynamisches Array => Getter mit Indexparametern zurückzugeben
Glauben Sie, dass es bei „Proxy“-Verträgen ein Vertrauensproblem gibt? Es ist schön für Entwickler, dass der eigene Smart Contract „aktualisierbar“ ist, aber das bedeutet wirklich, dass es ein unbeschriebenes Blatt gibt, was die Änderungen betrifft, die diese Entwickler am bestehenden Smart Contract vornehmen können, oder? Gibt es nicht einen Vertrauensverlust, den der Vertrag aufgrund seiner Unveränderlichkeit erleiden könnte?
Ja, dies ist ein Problem, das pro Anwendungsfall angegangen werden muss. Häufig ist es eine gute Idee, einen „Opt-in“-Mechanismus bereitzustellen, der es Benutzern ermöglicht, nach eigenem Ermessen zum neuen Vertrag überzugehen. Eine andere Möglichkeit besteht darin, einfach einen Namensauflöser und ein TOFU-Modell zu verwenden
Frage, für die ich im Moment zu faul bin, mich selbst zu testen. Macht die Verwendung eines Relais den Modifikator "Ansicht" nicht nutzlos? Es fallen keine Gaskosten an, es sei denn, es wird von anderen Verträgen verlangt, und das ist es. Oder liege ich falsch?
Ich sehe im Kern nichts als Delegiertenruf: s
Da jetzt bessere öffentliche Informationen zur Aufrüstbarkeit verfügbar sind, werde ich das rechte Antwortkreuz auf eine ausführlichere Antwort verschieben
Können wir dem aktualisierten Vertrag neue Funktionen hinzufügen? @Tjaden Hess♦
In Ihrem Hauptcode habe ich das veröffentlicht und Speicher und Logik in zwei verschiedenen Verträgen getrennt. Gibt es bei jedem Anruf vom Logikvertrag zum Vertrag mit Speichergebühren zusätzliche 2.000 Gas, gibt es eine Möglichkeit, diese zusätzlichen Gaskosten zu vermeiden? @TjadenHess
Dieser Kern ist ziemlich veraltet, Sie sollten jetzt Delegiertenrufe über Bibliotheksverträge verwenden. Aber im Allgemeinen wird es aufgrund der zusätzlichen Indirektionsebene immer einen Overhead geben. Das Wechseln von Kontexten ist etwas teuer, da Knoten möglicherweise jeden Vertragsstatus von der Festplatte abrufen müssen

Eine Methode besteht darin, ein System von Verträgen zu verwenden, wie unten beschrieben:

  1. Vertrag "Registrieren" - enthält Paare "Name - Adresse" für alle Verträge Ihres Systems;
  2. Vertrag Backend;
  3. Vertrag Frontendmit Backend;
  4. Bereitstellen Registerund Adresse abrufen;
  5. Implementieren Backendund registrieren Sie die Adresse von Backendin bereits implementiert Register;
  6. Hardcodieren Sie die Adresse von Registerin die Quelle von Backend. Bevor Sie anrufen, sollten Sie Ihre anrufen und die tatsächliche Adresse von Backenderhalten .FrontendRegisterBackend

Dann können Sie Ihren BackendVertrag jederzeit aktualisieren – stellen Sie einfach den neuen bereit und registrieren Sie ihn erneut in der Register.

Externer Vertrag aufrufen: solidity.readthedocs.org...

Siehe auch Forumsdiskussion: forum.ethereum.org...


UPD: Gleicher, aber effizienterer Weg (vielleicht)

Erste Bereitstellung:

  1. Schreiben Sie einen Vertrag Register, der andere Verträge mit seiner eigenen Adresse als Konstruktorargument bereitstellen kann;
  2. Schreiben Sie alle anderen Verträge - "aktualisierbare" Verträge mit Konstrukteuren, die Registerdie Adresse von benötigen;
    • Vielleicht sollten diese Verträge deaktiviert werden oder eine Subside-Methode haben
  3. Stellen Sie bereit Registerund geben Sie die Daten des Konstrukteurs an - alle anderen Verträge aus Schritt 2.

Aktualisierung:

  1. Stellen Sie eine neue Version des „aktualisierbaren“ Vertrags mit derselben Adresse von Register;
    • Oder vielleicht, wenn Sie Registerandere Verträge einsetzen können - geben Sie es ihm
  2. (optional) alte Version des „aktualisierbaren“ Vertrags deaktivieren/beenden;
  3. Registrieren Sie die Adresse der neuen Version des "aktualisierbaren" Vertrags in der Register.
Groß! Wenn jemand bereits codierte Beispiele hat, fügen Sie sie bitte hier hinzu :)
Ich verschiebe das richtige Antwortzeichen, weil die andere Antwort aktualisierte Informationen darüber enthält, wie man einen Staffelvertrag auf Homestead abschließt.
Frage: Wäre es im Fall mehrerer Verträge nicht effizienter, zuerst alle Verträge zu registrieren, alle ihre Adressen zu speichern und dann den Register-Vertrag bereitzustellen (wobei alle gespeicherten Adressen als Konstruktorargumente übergeben werden)? Könnte so eine Menge Funktionsaufrufe und entsprechendes Benzin sparen?
@BharatMellapur, ja natürlich

Der Vertragscode ist unveränderlich, der Speicher ist veränderlich, aber Sie können den im Speicher abgelegten Code zumindest vorerst nicht ausführen.

Fehlerbehebungen bei Verträgen

Bei Bugfixes besteht das übliche Muster darin, Proxy- oder Lookup-Verträge zu haben, die ein Tor zum echten sind, der im Falle einer Änderung oder eines Bugfixes ersetzt würde. Ein Austausch bedeutet auch den Verlust des alten Speicherinhalts.

Lagerung halten

Wenn Sie die Möglichkeit haben möchten, Code zu aktualisieren und gleichzeitig Speicher zu behalten, könnten Sie daran denken, Speicher und Logik zu trennen. Haben Sie einen dedizierten Speichervertrag, der Schreibaufrufe von vertrauenswürdigen Adressen akzeptiert (z. B. die Logikverträge). Alle wichtigen Speicher sollten diesem zugeordnet sein.

Zugriff auf Speicher nach Selbstzerstörung

Stand heute gibt es auch im Fall der Selbstzerstörung keine wirkliche Beschneidung, aber das sollte definitiv in Zukunft kommen. Es gibt mehrere EIPs, die dies diskutieren.

Selbst wenn das Pruning implementiert ist, sollte es nicht sofort passieren und Sie sollten in der Lage sein, den Speicher aus dem letzten Zustand zu lesen. Es ist auch geplant, Archivknoten zu haben, um Zustände auf unbestimmte Zeit zu speichern – nicht sicher, ob dies ohne Einschränkungen machbar ist, wenn man nur das Wachstum der Blockchain beurteilt.

Neubereitstellung unter derselben Adresse

Kurzum: Praktisch ist das nicht möglich. Die Vertragsadressen errechnen sich aus Absender und Nonce. Die Nonce ist sequentiell, es darf keine Lücken und keine Duplikate geben.

Theoretisch ist es möglich, denselben Hash mit einer anderen Kombination aus Nonce und Adresse zu erhalten, aber die Wahrscheinlichkeit ist gering.

Auf einer Blockchain bereitgestellte Verträge sind unveränderlich, das bedeutet also:

  • Adresse und Code eines bereitgestellten Vertrags können nicht geändert werden
  • Durch die Bereitstellung eines neueren (oder sogar identischen) Vertrags wird eine neue Adresse erstellt
  • Code kann einem bereitgestellten Vertrag nicht hinzugefügt werden

Wenn die Vertragsfragen eine Möglichkeit haben möchten, den Vertragscode zu aktualisieren, damit Kontodaten und andere Dinge übertragen werden, welche Mittel stellt Ethereum dafür bereit?

Eine einfache Möglichkeit, einen Vertrag C1 zu erweitern, besteht darin, sicherzustellen, dass C1 über Funktionen / Zugriffsmethoden verfügt, die alle Daten zurückgeben, die es hat. Ein neuer Vertrag C2 kann geschrieben werden, der die C1-Funktionen aufruft und zusätzliche oder korrigierte Logik ausführt. (Beachten Sie, dass, wenn C1 und C2 foo haben, wobei C1s foo fehlerhaft und C2s foo korrigiert ist, es keine Möglichkeit gibt, C1 foo vom Aufrufen zu deaktivieren.)

Eine Registrierung kann, wie in der Antwort von @Alexander beschrieben, verwendet werden, damit andere DApps und Verträge die Registrierung nach der Adresse von Vertrag C abfragen, sodass, wenn C1 durch C2 „ersetzt“ wird, kein DApp-Code geändert werden muss. Die Verwendung einer Registrierung auf diese Weise verhindert, dass die Adresse von C1 hartcodiert wird (damit C2, C3, C4 bei Bedarf an ihre Stelle treten können), aber die DApp muss die Adresse der Registrierung hartcodieren.


BEARBEITEN: Der ENS, Ethereum Name Service, wurde gerade im Testnetz (Ropsten) bereitgestellt.

Einen Schnellstart und weitere Details finden Sie im ENS-Wiki . Hier ist eine Einführung:

ENS ist der Ethereum Name Service, ein verteiltes, erweiterbares Namenssystem, das auf der Ethereum-Blockchain basiert.

ENS kann verwendet werden, um eine Vielzahl von Ressourcen aufzulösen. Der anfängliche Standard für ENS definiert die Auflösung für Ethereum-Adressen, aber das System ist vom Design her erweiterbar, sodass in Zukunft mehr Ressourcentypen aufgelöst werden können, ohne dass die Kernkomponenten von ENS aktualisiert werden müssen.

ENS wird im Ropsten-Testnet unter 0x112234455c3a32fd11230c42e7bccd4a84e02010 bereitgestellt.

Erstgespräch hier .

Und die Registrierung hat, wie jedes Indirektionssystem, ihre eigenen Fehler, Sicherheitsprobleme und Probleme. Wenn Sie den Vertrag wegen eines Sicherheitsrisikos upgraden möchten, sollten Sie dies beachten. Wenn das DAO ein Upgrade-System hätte, wäre es zweifellos für einen Hack verwendet worden ...
@bortzmeyer Stimmen Sie zu, jeder Upgrade-Mechanismus birgt Risiken, die selbst ausgenutzt werden könnten, und diese Risiken sollten berücksichtigt werden.

Die Antwort mit den meisten Stimmen ist zu verwenden, delegatecallund es ist sehr schwierig, sie richtig zu machen.

https://blog.trailofbits.com/2018/09/05/contract-upgrade-anti-patterns beschreibt einige Upgrade-Methoden sowie kritische Überlegungen, damit Sie nicht mehr Fehler oder eine fehlerhafte Upgrade-Methode einführen, die dies nicht tut. Ich arbeite nicht.

Proxy-Musterempfehlungen

Prüfen Sie, ob der Zielvertrag existiert, bevor Sie den Delegiertenruf anrufen. Solidity führt diese Prüfung nicht in Ihrem Namen durch. Die Vernachlässigung der Überprüfung kann zu unbeabsichtigtem Verhalten und Sicherheitsproblemen führen. Sie sind für diese Überprüfungen verantwortlich, wenn Sie sich auf Low-Level-Funktionalität verlassen.

Wenn Sie das Proxy-Muster verwenden, müssen Sie:

Haben Sie ein detailliertes Verständnis der Interna von Ethereum , einschließlich der genauen Mechanik des Delegiertenrufs und detaillierte Kenntnisse der Interna von Solidity und EVM.

Berücksichtigen Sie die Reihenfolge der Vererbung sorgfältig , da sie sich auf das Speicherlayout auswirkt.

Überlegen Sie sorgfältig, in welcher Reihenfolge Variablen deklariert werden. Beispielsweise können Variablenschatten oder sogar Typänderungen (wie unten angegeben) die Absicht des Programmierers beeinflussen, wenn er mit Delegatecall interagiert.

Beachten Sie, dass der Compiler Füll- und/oder Packvariablen verwenden kann. Wenn beispielsweise zwei aufeinanderfolgende uint256 in zwei uint8 geändert werden, kann der Compiler die beiden Variablen in einem statt in zwei Slots speichern.

Bestätigen Sie, dass das Speicherlayout der Variablen eingehalten wird, wenn eine andere Version von solc verwendet wird oder wenn andere Optimierungen aktiviert sind. Verschiedene Versionen von Solc berechnen Speicher-Offsets auf unterschiedliche Weise. Die Speicherreihenfolge der Variablen kann sich auf die Gaskosten, das Speicherlayout und somit auf das Ergebnis des Delegiertenrufs auswirken.

Berücksichtigen Sie sorgfältig die Initialisierung des Vertrags. Je nach Proxy-Variante dürfen Zustandsvariablen während der Konstruktion nicht initialisiert werden. Infolgedessen gibt es während der Initialisierung eine potenzielle Race-Bedingung, die gemildert werden muss.

Berücksichtigen Sie die Namen von Funktionen im Proxy sorgfältig , um Funktionsnamenkonflikte zu vermeiden. Proxy-Funktionen mit demselben Keccak-Hash wie die beabsichtigte Funktion werden stattdessen aufgerufen, was zu unvorhersehbarem oder böswilligem Verhalten führen kann.

Danke für die Vorwarnung. Da dies die neueste und informativste Antwort ist, obwohl nur eine Linkantwort vorliegt, werde ich diese als richtig markieren, da das Publikum alle Aspekte der Aufrüstbarkeit im Detail verstehen muss.
Können wir dem aktualisierten Vertrag neue Funktionen hinzufügen? @eth
@alper Ein einfacherer Weg als delegatecalldie Verwendung von ENS: ethereum.stackexchange.com/questions/77520/…
Ich verschiebe die richtige Antwortmarkierung, da OpenZeppelin jetzt umfangreiche Dokumentationen und Tools dazu bereitstellt.
@MikkoOhtamaa Kein Problem. Das Poster der Antwort, die Sie akzeptiert haben, hat es auch als Antwort auf einige andere Fragen gepostet.

@Nick Johnson hat einen Basisvertrag für erweiterbare Verträge.

Wie er sagt , sollte man vor der Verwendung „die Einschränkungen und Nachteile vollständig verstehen“.

/**
 * Base contract that all upgradeable contracts should use.
 * 
 * Contracts implementing this interface are all called using delegatecall from
 * a dispatcher. As a result, the _sizes and _dest variables are shared with the
 * dispatcher contract, which allows the called contract to update these at will.
 * 
 * _sizes is a map of function signatures to return value sizes. Due to EVM
 * limitations, these need to be populated by the target contract, so the
 * dispatcher knows how many bytes of data to return from called functions.
 * Unfortunately, this makes variable-length return values impossible.
 * 
 * _dest is the address of the contract currently implementing all the
 * functionality of the composite contract. Contracts should update this by
 * calling the internal function `replace`, which updates _dest and calls
 * `initialize()` on the new contract.
 * 
 * When upgrading a contract, restrictions on permissible changes to the set of
 * storage variables must be observed. New variables may be added, but existing
 * ones may not be deleted or replaced. Changing variable names is acceptable.
 * Structs in arrays may not be modified, but structs in maps can be, following
 * the same rules described above.
 */
contract Upgradeable {
    mapping(bytes4=>uint32) _sizes;
    address _dest;

    /**
     * This function is called using delegatecall from the dispatcher when the
     * target contract is first initialized. It should use this opportunity to
     * insert any return data sizes in _sizes, and perform any other upgrades
     * necessary to change over from the old contract implementation (if any).
     * 
     * Implementers of this function should either perform strictly harmless,
     * idempotent operations like setting return sizes, or use some form of
     * access control, to prevent outside callers.
     */
    function initialize();

    /**
     * Performs a handover to a new implementing contract.
     */
    function replace(address target) internal {
        _dest = target;
        target.delegatecall(bytes4(sha3("initialize()")));
    }
}

/**
 * The dispatcher is a minimal 'shim' that dispatches calls to a targeted
 * contract. Calls are made using 'delegatecall', meaning all storage and value
 * is kept on the dispatcher. As a result, when the target is updated, the new
 * contract inherits all the stored data and value from the old contract.
 */
contract Dispatcher is Upgradeable {
    function Dispatcher(address target) {
        replace(target);
    }

    function initialize() {
        // Should only be called by on target contracts, not on the dispatcher
        throw;
    }

    function() {
        bytes4 sig;
        assembly { sig := calldataload(0) }
        var len = _sizes[sig];
        var target = _dest;

        assembly {
            // return _dest.delegatecall(msg.data)
            calldatacopy(0x0, 0x0, calldatasize)
            delegatecall(sub(gas, 10000), target, 0x0, calldatasize, 0, len)
            return(0, len)
        }
    }
}

contract Example is Upgradeable {
    uint _value;

    function initialize() {
        _sizes[bytes4(sha3("getUint()"))] = 32;
    }

    function getUint() returns (uint) {
        return _value;
    }

    function setUint(uint value) {
        _value = value;
    }
}
Das ist meine Lieblingslösung. Die Antwort sollte mehr positiv bewertet werden! Beim letzten Fork entfällt auch die Angabe der Größe der Rückgabewerte ( _sizes[bytes4(sha3("getUint()"))] = 32).
Ich habe dieses Thema hier behandelt: youtube.com/watch?v=KBqDYF5jw-0

Kommen wir zu einem der Grundprinzipien von Ethereum, nämlich einem Smart Contract, der nach der Bereitstellung nicht mehr geändert werden kann.

ABER Sie können immer noch aktualisierbare Smart Contracts haben, wenn Sie Folgendes berücksichtigen

Dies muss von Anfang an geplant werden. Der entscheidende Punkt ist Nummer 4. Aber alle anderen sind für ein echtes und reibungsloses Smart Contract Upgrade unerlässlich.

Sie müssen also Ihren Smart Contract unter Berücksichtigung der folgenden 5 Punkte entwerfen:

  1. Halten Sie Ihre Smart Contracts modular und trennen Sie Regeln und Logik ziemlich von der Datenstruktur. Wenn Sie also etwas ändern müssen, ändern Sie nur den zugehörigen Vertrag und müssen nicht viele oder alle Verträge ändern.
  2. Sie sollten mit einem Notausschalter oder Leistungsschalter vorbereitet sein, um alle Vorgänge während einer Migration stoppen zu können. Weil Sie nicht in einer Situation sein möchten, in der Personen während und nach der Migration noch Daten in die alte Version des Smart Contracts aktualisieren/einfügen können.
  3. Sie sollten zuvor die Möglichkeit haben, alle Daten aus Ihrem Smart Contract auszulesen . Natürlich können Sie ein berechtigtes Lesen durchführen, indem Sie das Lesen aller Daten auf den Eigentümer oder einen anderen vertrauenswürdigen Benutzer oder sogar einen anderen Smart Contract beschränken. Sie müssen die alte Version Ihres Smart Contracts lesen und in die neue Version einfügen.
  4. Sie werden eine der folgenden Strategien verwenden, um mit Ihrem Smart Contract zu kommunizieren. Ich habe sie von Smart Contact Best Practices kopiert :

Unterbrochene Verträge aktualisieren

Der Code muss geändert werden, wenn Fehler entdeckt werden oder Verbesserungen vorgenommen werden müssen. Es ist nicht gut, einen Fehler zu entdecken, aber keine Möglichkeit zu haben, damit umzugehen

...

Es gibt jedoch zwei grundlegende Ansätze, die am häufigsten verwendet werden. Die einfachere der beiden besteht darin, einen Registrierungsvertrag zu haben, der die Adresse der neuesten Version des Vertrags enthält. Ein nahtloserer Ansatz für Vertragsbenutzer ist ein Vertrag, der Anrufe und Daten an die neueste Version des Vertrags weiterleitet.

Beispiel 1: Verwenden Sie einen Registrierungsvertrag, um die neueste Version eines Vertrags zu speichern

In diesem Beispiel werden die Anrufe nicht weitergeleitet, daher sollten Benutzer die aktuelle Adresse jedes Mal abrufen, bevor sie damit interagieren.

contract SomeRegister {
    address backendContract;
    address[] previousBackends;
    address owner;

    function SomeRegister() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner)
        _;
    }

    function changeBackend(address newBackend) public
    onlyOwner()
    returns (bool)
    {
        if(newBackend != backendContract) {
            previousBackends.push(backendContract);
            backendContract = newBackend;
            return true;
        }

        return false;
    }
}

Dieser Ansatz hat zwei Hauptnachteile:

  1. Nutzer müssen immer die aktuelle Adresse nachschlagen, wer das nicht tut, läuft Gefahr, eine alte Vertragsversion zu verwenden

  2. Sie müssen sich genau überlegen, wie Sie mit den Vertragsdaten umgehen, wenn Sie den Vertrag ersetzen

Der alternative Ansatz besteht darin, Anrufe und Daten per Vertrag an die neueste Version des Vertrags weiterzuleiten:

Beispiel 2: Verwenden Sie einen DELEGATECALL, um Daten und Anrufe weiterzuleiten

contract Relay {
    address public currentVersion;
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function Relay(address initAddr) {
        currentVersion = initAddr;
        owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
    }

    function changeContract(address newVersion) public
    onlyOwner()
    {
        currentVersion = newVersion;
    }

    function() {
        require(currentVersion.delegatecall(msg.data));
    }
}

Dieser Ansatz vermeidet die vorherigen Probleme, hat aber seine eigenen Probleme. Sie müssen äußerst vorsichtig damit sein, wie Sie Daten in diesem Vertrag speichern. Wenn Ihr neuer Vertrag ein anderes Speicherlayout als der erste hat, können Ihre Daten beschädigt werden. Außerdem kann diese einfache Version des Musters keine Werte von Funktionen zurückgeben, sondern sie nur weiterleiten, was ihre Anwendbarkeit einschränkt. ( Komplexere Implementierungen versuchen, dies mit Inline-Assemblercode und einer Registrierung von Rückgabegrößen zu lösen.)

Unabhängig von Ihrem Ansatz ist es wichtig, dass Sie Ihre Verträge aktualisieren können, da sie sonst unbrauchbar werden, wenn die unvermeidlichen Fehler darin entdeckt werden.

Ich empfehle jedoch auch, Proxy-Bibliotheken in Solidity zu überprüfen , die von Zeppelin Solutions und Aragon veröffentlicht werden. Es ist geplant, einen Industriestandard für diese Angelegenheit zu schaffen.

  1. Sie müssen gute Teststrategien und -taktiken haben . Denn die Kosten für die Aktualisierung Ihres Smart Contracts können Ihr Leben wirklich ruinieren.

Ich habe dazu eine Story auf Medium mit dem Titel: Essential Design Consideration for Ethereum dApps (1): Upgradeable Smart Contracts erstellt und ein Beispiel für jeden der oben genannten 5 Punkte bereitgestellt.

Wir (ich und mein Team) haben kürzlich an dem Problem der aktualisierbaren Verträge gearbeitet, nachdem wir uns auf den Beitrag von colony.io zu aktualisierbaren Verträgen bezogen hatten . Wir haben also eine Lösung entwickelt, bei der wir verschiedene Vertragsebenen haben, anstatt einen einzigen Vertrag zu haben.

Wenn ich es kurz beschreibe, dann muss man den Speicherteil sehr generisch machen, damit man, sobald man ihn erstellt hat, jede Art von Daten darin speichern kann (mit Hilfe von Setter-Methoden) und darauf zugreifen kann (mit Hilfe von Getter-Methoden). . Das macht Ihre Datenspeicherung ewig, was Sie in Zukunft nicht mehr ändern müssen.

Sehen Sie sich diesen Datenspeichervertrag an, um ihn besser zu verstehen – https://goo.gl/aLmvJ5

Die zweite Schicht sollte der Hauptvertrag mit Ihren Funktionen sein, die zu einem späteren Zeitpunkt aktualisiert werden können, und um den alten Datenspeicher zu verwenden, sollten Sie den Vertrag so gestalten, dass Sie Ihren neu bereitgestellten Vertrag auf den vorhandenen (alten) Vertrag verweisen können. Datenspeicher und dann können Sie den alten Vertrag beenden, nachdem der neue Vertrag korrekt mit dem alten Datenspeicher kommuniziert hat.

Sehen Sie sich unsere Codebasis an, um zu verstehen, wie wir einen aktualisierbaren Vertrag implementiert haben – https://goo.gl/p5zGEv

Hinweis: Im obigen GitHub-Repo verwenden wir aufgrund unseres Anwendungsfalls drei Vertragsebenen. Es ist jedoch möglich, den Vertrag nur mit zwei Schichten aktualisierbar zu machen.

Hoffe das hilft.

Dies ist ein nützliches Repo. könnten Sie bitte mit der neuesten Solidity-Version teilen?

zos hat ein Framework für uns eingeführt, um einen aktualisierbaren Smart Contract einfach zu implementieren

PTAL: https://docs.zeppelinos.org/docs/start.html

Ermöglicht Ihnen einen Vertrag mit einer stabilen Adresse, aber vollständig kontrollierbarem und erweiterbarem Verhalten.

https://github.com/u2/ether-router

https://github.com/ConsenSys/smart-contract-best-practices#upgrading-broken-contracts

Während dies die Frage theoretisch beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.

Bei Blend haben wir ZeppelinOS verwendet, um unsere regulären Ethereum Smart Contracts aktualisierbar zu machen. Hier ist unsere Schritt-für-Schritt-Anleitung und der Beispielcode .

Das eigentliche Problem bei aktualisierbaren Smart Contracts besteht darin, gespeicherte Werte aus dem Vertrag zu migrieren.

Eine viel bessere Möglichkeit, einen aktualisierbaren Smart Contract zu erstellen, besteht darin, Ihren Speicher und Ihre Logik in verschiedenen Verträgen zu differenzieren.

Speichern Sie alle Ihre Vertragsdaten in einem Smart Contract, der nur Anrufe von Ihrem Logikvertrag akzeptiert.

Ändern Sie die Logik Ihres Logikvertrags ständig. Sie müssen jedoch sehr visionär sein, wenn Sie die Variablen des Speichervertrags definieren.