Almacenamiento de tipos de datos complejos en Eternal Storage

He estado leyendo sobre estrategias para escribir contratos actualizables. Un patrón que ha surgido varias veces es separar la lógica comercial de sus contratos de su almacenamiento para que las actualizaciones puedan ocurrir sin pérdida de datos ( Escribir contratos actualizables en solidez , Diseño de contrato de solidez actualizable ).

En este artículo, se dice que el almacenamiento eterno es "una forma simple y extensible de almacenar cualquier tipo de datos, desde valores simples hasta matrices y datos de tipos de objetos complejos". Sin embargo, dado el contrato de almacenamiento provisto y el contrato de usuario simulado a continuación, me está costando entender cuál es la mejor manera de mover el almacenamiento del usuario al contrato EternalStorage dado que contiene elementos como arreglos, asignaciones, estructuras, etc. sugerencias?

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;
  }
}

Respuestas (1)

Cambiaría algunos detalles en el cliente simulado. Está cerca del patrón Mapped Structs with Index aquí: ¿Existen patrones de almacenamiento simples y bien resueltos para Solidity? .

Puede ser bueno comprender este patrón de integridad referencial: https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42 antes de partir, luego...

Que sea apátrida...

Reduzca todo a pares clave/valor con bytes32claves. Este es un boceto mínimo aproximado optimizado para facilitar la lectura:

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;
    }

}

Me salí con la mía con un poco de trampa porque solo hay 1 booly uno uint. ¿Qué pasaría si hubiera varias uintvariables?

Sigue haciéndote...

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

Puede ir un paso más allá y agregar rowo indexa la combinación de arreglos y asignaciones.

Después de haber jugado un poco con este tipo de patrón, he llegado a pensar en estos contratos de almacenamiento específicos de la aplicación como algo separado de la lógica de la aplicación: solo las garantías esenciales de creación, recuperación, actualización, eliminación e integridad referencial. Luego, considere dejar que los contratos de aplicación "posean" uno o más controladores de datos.

ApplicationContract => Data Controller => EternalStorage

El reemplazo ordenado del controlador de datos y/o el contrato de aplicación es otro tema (pista: ¡no lo rompa!). El diseño anticipa que uno podría necesitar extender el esquema en algún momento.

Espero eso ayude.

¡Gracias! Esto definitivamente ayuda. Para usar este patrón, ¿estamos obligados a perder la noción de la estructura de Usuario?
bastante Puede usar una estructura mapeada con índice como almacenamiento eterno en la medida en que esté seguro de que no querrá cambiar el diseño. No conozco una manera de mantener el diseño flexible sin reducir las cosas a un almacén de clave/valor general, donde los tipos admitidos son escalares. Verá muchos patrones "actualizables" que no admiten complementos de diseño. Al final, se trata del alcance de la capacidad de actualización que está buscando.