Ich bin auf der Suche nach Designmustern, die Menschen in Smart Contracts von Ethereum implementiert haben, um die Änderung von Datenstrukturen nach der Bereitstellung zu ermöglichen.
Angenommen, ich habe einen Vertrag, der eine Struktur enthält, die eine Adresse definiert. Sollte ich auf der ganzen Linie feststellen, dass ich der Adresse eine E-Mail-Adresseigenschaft hinzufügen möchte.
Ein etwas komplexeres Beispiel. Wenn ich zwei Eigenschaften eines Vertrags mit den Namen "Frage" und "Antwort" hätte und plötzlich mehrere mögliche Antworten haben möchte ... wie würde man vorgehen, um eine solche Änderung vorzunehmen?
Mein Problem / meine Sorge ist, dass Sie, wenn Sie beispielsweise über einen Browser mit diesem Vertrag kommunizieren, einfach den Vertrag aktualisieren könnten, auf den das Frontend verweist (nach dem Update). ABER wie lösen Sie das Problem der Aufrechterhaltung von Vertragsdaten über die Aktualisierung hinweg Einsätze..?
Danke
Dies muss von Anfang an geplant werden. Sie müssen 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 ist ein Registrierungsvertrag, 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 habe dazu eine Story auf Medium mit dem Titel erstellt: Essential Design Consideration for Ethereum dApps (1): Upgradeable Smart Contracts
Wir könnten auch auf eine Lösung vom Typ VARIANT zurückgreifen (ja, gefällt mir auch nicht).
Haben Sie eine Zuordnung (uint => StorageItem).
Die uint ist eine Feld-ID.
Der StorageItem-Vertrag hätte einen Zeichenfolgenwert und einen ganzzahligen Typ. Der Typ würde es Ihnen ermöglichen, von der Zeichenfolge in den erforderlichen Endtyp zu konvertieren.
Eine Schwäche hier ist, dass die Solidität irgendwann neue Typen unterstützen wird, die wir nutzen möchten.
Ich nehme an, Sie könnten den Speicher wie hier vorgeschlagen in einem separaten Vertrag aufbewahren, aber die Struktur würde die Daten in einem JSON-Objekt (String) speichern. Anschließend können Sie alle gewünschten Modifikationen vornehmen (Modell erweitern oder ändern).
zum Beispiel:
{"address": "Some Street","phone": 123456}
würde werden
{"address": "Some Street","phone": 123456, "email":"someone@email.com"}
Sie können fehlende Werte auf der Clientseite oder im aufrufenden Vertrag behandeln.
Es gibt eine Reihe von Ansätzen, mit denen Sie a auf aktualisieren können , wobei der Status (Daten und Kontostand) mit derselben Adresse wie zuvor beibehalten wird.Contract1
Contract2
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 fantastische 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 .
axisch