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?
Ja. Es gibt eine Reihe von Ansätzen, mit denen Sie a auf aktualisieren können Contract1
, Contract2
wobei 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 fallback
Funktion zu verwenden, bei der jeder Methodenaufruf/trx an den Implementierungsvertrag (der die gesamte Logik enthält) delegiert wird.
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.
Die fallback
Funktion 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 proxy
Verträ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 .
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 DELEGATECALL
Opcode. Auf diese Weise können Sie Anrufe im Wesentlichen an einen separaten Vertrag weiterleiten, während Sie die msg.sender
gesamte 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;
}
}
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.Eine Methode besteht darin, ein System von Verträgen zu verwenden, wie unten beschrieben:
Backend
;Frontend
mit Backend
;Register
und Adresse abrufen;Backend
und registrieren Sie die Adresse von Backend
in bereits implementiert Register
;Register
in die Quelle von Backend
. Bevor Sie anrufen, sollten Sie Ihre anrufen und die tatsächliche Adresse von Backend
erhalten .Frontend
Register
Backend
Dann können Sie Ihren Backend
Vertrag 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:
Register
, der andere Verträge mit seiner eigenen Adresse als Konstruktorargument bereitstellen kann;Register
die Adresse von benötigen;
Register
und geben Sie die Daten des Konstrukteurs an - alle anderen Verträge aus Schritt 2.Aktualisierung:
Register
;
Register
andere Verträge einsetzen können - geben Sie es ihmRegister
.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:
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 .
Die Antwort mit den meisten Stimmen ist zu verwenden, delegatecall
und 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.
delegatecall
die Verwendung von ENS: ethereum.stackexchange.com/questions/77520/…@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;
}
}
_sizes[bytes4(sha3("getUint()"))] = 32
).Kommen wir zu einem der Grundprinzipien von Ethereum, nämlich einem Smart Contract, der nach der Bereitstellung nicht mehr geändert werden kann.
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:
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:
Nutzer müssen immer die aktuelle Adresse nachschlagen, wer das nicht tut, läuft Gefahr, eine alte Vertragsversion zu verwenden
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.
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.
zos hat ein Framework für uns eingeführt, um einen aktualisierbaren Smart Contract einfach zu implementieren
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
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.
Muhammad Altabba
schämen
A. Gupta