Komplexe Datentypen in Eternal Storage speichern

Ich habe mich mit Strategien zum Schreiben erweiterbarer Verträge beschäftigt. Ein häufig aufgetauchtes Muster besteht darin, die Geschäftslogik Ihrer Verträge von ihrem Speicher zu trennen, damit Upgrades ohne Datenverlust erfolgen können ( Schreiben von aktualisierbaren Verträgen in Solidität , Aktualisierbare Soliditätsvertragsgestaltung ).

In diesem Artikel heißt es, dass der ewige Speicher eine „einfache und erweiterbare Möglichkeit ist, jede Art von Daten zu speichern, von einfachen Werten bis hin zu Arrays und komplexen Objekttypdaten“. Angesichts des bereitgestellten Speichervertrags und des Scheinbenutzervertrags unten fällt es mir jedoch schwer, den besten Weg zu verstehen, den Benutzerspeicher in den EternalStorage-Vertrag zu verschieben, da er Dinge wie Arrays, Mappings, Strukturen usw. enthält. Irgendwelche Gedanken / Vorschläge?

pragma solidity ^0.4.24;

contract UserRegistry {
  struct User {
      address addr;
      uint points;
      address[] friendsList;
      mapping(address => bool) friends;
  }

  mapping(address => User) users;
  mapping(address => mapping(address => uint)) public gamesPlayedTogether; // user address => (friendAddress => games played together)

  function createUser() public {
      User memory user = User(msg.sender, 0, new address[](0));
      users[msg.sender] = user;
  }

  // Various other business logic
}

contract Storage {

  mapping(bytes32 => uint256)    private uIntStorage;
  mapping(bytes32 => string)     private stringStorage;
  mapping(bytes32 => address)    private addressStorage;
  mapping(bytes32 => bytes)      private bytesStorage;
  mapping(bytes32 => bool)       private boolStorage;
  mapping(bytes32 => int256)     private intStorage;

  function getAddress(bytes32 _key) public view returns (address) {
      return addressStorage[_key];
  }

  function getUint(bytes32 _key) public view returns (uint) {
      return uIntStorage[_key];
  }

  function getString(bytes32 _key) public view returns (string) {
      return stringStorage[_key];
  }

  function getBytes(bytes32 _key) public view returns (bytes) {
      return bytesStorage[_key];
  }

  function getBool(bytes32 _key) public view returns (bool) {
      return boolStorage[_key];
  }

  function getInt(bytes32 _key) public view returns (int) {
      return intStorage[_key];
  }


  function setAddress(bytes32 _key, address _value) public {
      addressStorage[_key] = _value;
  }

  function setUint(bytes32 _key, uint _value) public {
      uIntStorage[_key] = _value;
  }

  function setString(bytes32 _key, string _value) public {
      stringStorage[_key] = _value;
  }

  function setBytes(bytes32 _key, bytes _value) public {
      bytesStorage[_key] = _value;
  }

  function setBool(bytes32 _key, bool _value) public {
      boolStorage[_key] = _value;
  }

  function setInt(bytes32 _key, int _value) public {
      intStorage[_key] = _value;
  }
}

Antworten (1)

Ich würde einige Details im Mock-Client ändern. Es ist nah am Mapped Structs with Index-Muster hier drüben: Gibt es gut gelöste und einfache Speichermuster für Solidity? .

Es könnte gut sein, dieses Muster für referentielle Integrität zu verstehen: https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42 bevor Sie sich auf den Weg machen, dann …

Machen Sie es staatenlos ...

Reduzieren Sie alles auf Schlüssel/Wert-Paare mit bytes32Schlüsseln. Dies ist eine grobe, auf Lesbarkeit optimierte Minimalskizze:

contract StatelessUserRegisty {

    Storage dataStore;

    constructor() public {
        dataStore = new Storage();
    }

    function userKey(address userAddr) public pure returns(bytes32 userID) {
        return keccak256(abi.encodePacked(userAddr));
    }

    function isUser(address userAddr) public view returns(bool isIndeed) {
        return dataStore.getBool(userKey(userAddr));
    }

    function createUser(address userAddr) public returns(bool success) {
        require(!isUser(userAddr));
        dataStore.setBool(userKey(userAddr),true);
        return true;
    }

    function updateUserPoints(address userAddr, uint userPoints) public returns(bool success) {
        require(!isUser(userAddr));
        dataStore.setUint(userKey(userAddr),userPoints);
        return true;
    }

}

Ich bin mit einem kleinen Cheat davongekommen, weil es nur 1 boolund eins gibt uint. uintWas wäre, wenn es mehrere Variablen gäbe ?

Finger weg von Hashing ...

bytes32 key1 = keccak256(userAddr, "first");
bytes32 key2 = keccak256(userAddr, "second");

Sie können noch einen Schritt weiter gehen und der Mischung für Arrays und Mappings rowoder hinzufügen index.

Nachdem ich ziemlich viel mit dieser Art von Muster herumgespielt habe, bin ich zu dem Schluss gekommen, dass diese anwendungsspezifischen Speicherverträge etwas sind, das von der Anwendungslogik getrennt ist – nur die wesentlichen Zusicherungen zum Erstellen, Abrufen, Aktualisieren, Löschen und zur referenziellen Integrität. Erwägen Sie dann, Anwendungsverträgen einen oder mehrere Datenverantwortliche „besitzen“ zu lassen.

ApplicationContract => Data Controller => EternalStorage

Der geordnete Austausch des Datenverantwortlichen und/oder des Anwendungsvertrags ist ein weiteres Thema (Tipp: nicht brechen!). Das Design geht davon aus, dass das Schema möglicherweise irgendwann erweitert werden muss.

Ich hoffe es hilft.

Danke schön! Das hilft auf jeden Fall. Müssen wir dann, um dieses Muster zu verwenden, den Begriff der Benutzerstruktur aufgeben?
Ja schon. Sie können eine abgebildete Struktur mit Index als ewigen Speicher verwenden, sofern Sie sicher sind, dass Sie das Layout nicht ändern möchten. Ich kenne keine Möglichkeit, das Layout flexibel zu halten, ohne die Dinge auf einen Catch-All-Schlüssel/Wert-Speicher zu reduzieren, in dem die unterstützten Typen Skalar sind. Sie werden viele "aufrüstbare" Muster sehen, die keine Layout-Add-Ons unterstützen. Letztlich geht es um das angestrebte Maß an Aufrüstbarkeit.