Какие шаблоны проектирования подходят для модификации структуры данных в смарт-контрактах Ethereum?

Я хочу услышать о шаблонах проектирования, которые люди реализовали в смарт-контрактах Ethereum, чтобы разрешить модификацию структур данных после развертывания.

Например, скажем, у меня есть контракт, который содержит структуру, определяющую адрес. Должен ли я в будущем понять, что хочу добавить свойство адреса электронной почты в адрес. Есть ли какие-либо соображения по шаблону проектирования перед развертыванием, которые позволили бы мне это сделать.

Немного более сложный пример. Если бы у меня было два свойства контракта с именами «вопрос» и «ответ», и вдруг я хочу иметь несколько возможных ответов.. как можно было бы внести такое изменение?

Моя проблема/беспокойство заключается в том, что если вы взаимодействуете с указанным контрактом, например, через браузер, вы можете просто обновить контракт, на который указывает интерфейс (после обновления). НО, как вы решаете проблему сохранения любых данных контракта при обновлении развертывания..?

Спасибо Т

Некоторые ответы в этом посте могут вам помочь: ethereum.stackexchange.com/questions/2159/…

Ответы (4)

Вам необходимо принять во внимание следующее:

Это нужно планировать с самого начала. Вам нужно будет разработать свой смарт-контракт с учетом следующих 5 пунктов:

  1. Вы должны иметь хорошую отработку стратегии и тактики . Потому что стоимость обновления вашего смарт-контракта может действительно разрушить вашу жизнь.
  2. Держите свои смарт-контракты модульными и отделяйте правила и логику от структуры данных. Поэтому, если вам нужно будет что-то изменить, вы измените только соответствующий контракт, и вам не нужно будет изменять многие или все контракты.
  3. Вы должны быть готовы к аварийному останову или автоматическому выключателю , чтобы иметь возможность остановить все операции во время любой миграции. Потому что вы не хотите оказаться в ситуации, когда люди все еще могут обновлять/вставлять данные в старую версию смарт-контракта во время миграции и после нее.
  4. Вы должны предварительно предоставить возможность считывать все данные из вашего смарт-контракта . Конечно, вы можете выполнить разрешенное чтение, ограничив чтение всех данных владельцем или любым другим доверенным пользователем или даже другим смарт-контрактом. Вам нужно будет прочитать старую версию вашего смарт-контракта и вставить в новую версию.
  5. Вы будете использовать одну из следующих стратегий для связи со своим смарт-контрактом. Я скопировал их из Smart Contact Best Practices :

Обновление нарушенных контрактов

Код необходимо будет изменить, если будут обнаружены ошибки или если необходимо внести улучшения. Нехорошо обнаружить ошибку, но не иметь возможности с ней справиться

...

Тем не менее, есть два основных подхода, которые наиболее часто используются. Проще всего иметь контракт реестра, содержащий адрес последней версии контракта. Более простой подход для контрактных пользователей — иметь контракт, который перенаправляет вызовы и данные в последнюю версию контракта.

Пример 1. Использование контракта реестра для хранения последней версии контракта

В этом примере вызовы не переадресовываются, поэтому пользователи должны каждый раз получать текущий адрес, прежде чем взаимодействовать с ним.

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

У этого подхода есть два основных недостатка:

  1. Пользователи всегда должны искать текущий адрес, и любой, кто этого не делает, рискует использовать старую версию контракта.

  2. Вам нужно будет тщательно продумать, как обращаться с данными контракта при замене контракта.

Альтернативный подход заключается в том, чтобы контракт перенаправлял вызовы и данные в последнюю версию контракта:

Пример 2: Используйте DELEGATECALL для пересылки данных и вызовов

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

Этот подход позволяет избежать предыдущих проблем, но имеет свои собственные проблемы. Вы должны быть предельно осторожны с тем, как вы храните данные в этом контракте. Если ваш новый контракт имеет другую схему хранения, чем первый, ваши данные могут быть повреждены. Кроме того, эта простая версия шаблона не может возвращать значения из функций, а только пересылать их, что ограничивает ее применимость. ( Более сложные реализации пытаются решить эту проблему с помощью встроенного ассемблерного кода и реестра возвращаемых размеров.)

Независимо от вашего подхода, важно иметь какой-то способ обновить ваши контракты, иначе они станут непригодными для использования, когда в них будут обнаружены неизбежные ошибки.

Для этого я создал статью на Medium под заголовком: Основные аспекты проектирования децентрализованных приложений Ethereum (1): обновляемые смарт-контракты.

Мы также могли бы вернуться к решению типа VARIANT (да, оно мне тоже не нравится).
Иметь сопоставление (uint => StorageItem).

  • uint — это идентификатор поля.

  • Контракт StorageItem будет иметь строковое значение и целочисленный тип. Тип позволит вам преобразовать строку в нужный конечный тип.

Одним из недостатков здесь является то, что в какой-то момент Solidity будет поддерживать новые типы, которыми мы захотим воспользоваться.

Я полагаю, вы могли бы сохранить хранилище в отдельном контракте, как предлагается здесь, но структура будет хранить данные в объекте JSON (строка). Затем вы можете внести любые необходимые вам модификации (расширить или изменить модель).

Например:

{"address": "Some Street","phone": 123456}

станет

{"address": "Some Street","phone": 123456, "email":"someone@email.com"}

Вы можете обрабатывать отсутствующие значения на стороне клиента или в вызывающем контракте.

Существует ряд подходов, с помощью которых вы можете обновить Contract1до Contract2, сохраняя его состояние (данные и баланс) с тем же адресом, что и раньше.

Как это работает? Один из способов — использовать прокси-контракт с fallbackфункцией, в которой каждый вызов метода/trx делегируется контракту реализации (который содержит всю логику).введите описание изображения здесь

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

При таком подходе пользователи взаимодействуют только с прокси-контрактом, и мы можем изменить контракт реализации, сохранив тот же прокси-контракт.

введите описание изображения здесь

Функция fallbackбудет выполняться при любом запросе, перенаправляя запрос в реализацию и возвращая результирующее значение (используя коды операций).

Это было базовое объяснение, которого нам достаточно для работы с обновляемыми контрактами. В случае, если вы хотите углубиться в код контракта прокси и различные шаблоны прокси, ознакомьтесь с этими сообщениями.

Как я могу написать обновляемые смарт-контракты?

OpenZeppelin предоставляет отличные инструменты CLI и JS-библиотеки , которые заботятся обо всех вышеперечисленных сложных proxyконтрактах, связывая их с контрактами реализации (логики) и управляя всеми контрактами, которые вы развертываете, используя CLI для возможности обновления, «из коробки».

Единственное, что вам нужно сделать, это написать свои контракты и использовать OpenZeppelin CLI или библиотеки для развертывания контрактов.

ПРИМЕЧАНИЕ. Есть несколько ограничений , о которых вам следует знать, в отношении того, как вам нужно писать свои контракты и как вы должны их обновлять. Также в этом посте есть ряд обходных путей этих ограничений .