Хранение сложных типов данных в Eternal Storage

Я изучал стратегии написания обновляемых контрактов. Шаблон, который неоднократно возникал, заключается в том, чтобы отделить бизнес-логику ваших контрактов от ее хранилища, чтобы обновления могли происходить без потери данных ( написание обновляемых контрактов в твердости , дизайн контракта с обновляемой твердостью ).

В этой статье говорится, что вечное хранилище — это «простой и расширяемый способ хранения любых данных, от простых значений до массивов и данных сложных типов объектов». Однако, учитывая приведенный ниже контракт хранилища и макет контракта пользователя, мне трудно понять, как лучше всего переместить хранилище пользователя в контракт EternalStorage, учитывая, что он содержит такие вещи, как массивы, сопоставления, структуры и т. д. Любые мысли/ предложения?

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

Ответы (1)

Я бы изменил некоторые детали в фиктивном клиенте. Здесь он близок к шаблону Mapped Structs with Index: Существуют ли хорошо решенные и простые шаблоны хранения для Solidity? .

Возможно, было бы неплохо понять этот шаблон ссылочной целостности: https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42 , прежде чем вы отправитесь в путь, затем...

Сделать без гражданства...

Свести все к парам ключ/значение с bytes32ключами. Это грубый минимальный набросок, оптимизированный для удобочитаемости:

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

}

Мне сошло с рук немного читов, потому что есть только 1 boolи один uint. Что, если бы было несколько uintпеременных?

Продолжайте хешировать...

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

Вы можете сделать еще один шаг и добавить rowили indexк сочетанию массивов и отображений.

Немного поэкспериментировав с такого рода паттернами, я пришел к выводу, что эти специфичные для приложения контракты хранения представляют собой что-то отдельное от логики приложения — просто необходимые гарантии создания, извлечения, обновления, удаления и ссылочной целостности. Затем рассмотрите возможность предоставления контрактам приложений «владеть» одним или несколькими контроллерами данных.

ApplicationContract => Data Controller => EternalStorage

Регулярная замена контроллера данных и/или контракта приложения — это отдельная тема (подсказка: не нарушайте его!). Дизайн предполагает, что в какой-то момент может потребоваться расширить схему.

Надеюсь, поможет.

Спасибо! Это определенно помогает. Чтобы использовать этот шаблон, должны ли мы тогда потерять понятие структуры User?
Довольно много. Вы можете использовать сопоставленную структуру с индексом в качестве вечного хранилища, если вы уверены, что не захотите менять макет. Я не знаю способа сохранить гибкость макета, не сводя все к универсальному хранилищу ключей/значений, где поддерживаемые типы являются скалярными. Вы увидите много «обновляемых» шаблонов, которые не поддерживают надстройки макета. В конце концов, речь идет о степени возможности обновления, к которой вы стремитесь.