Я хочу услышать о шаблонах проектирования, которые люди реализовали в смарт-контрактах Ethereum, чтобы разрешить модификацию структур данных после развертывания.
Например, скажем, у меня есть контракт, который содержит структуру, определяющую адрес. Должен ли я в будущем понять, что хочу добавить свойство адреса электронной почты в адрес. Есть ли какие-либо соображения по шаблону проектирования перед развертыванием, которые позволили бы мне это сделать.
Немного более сложный пример. Если бы у меня было два свойства контракта с именами «вопрос» и «ответ», и вдруг я хочу иметь несколько возможных ответов.. как можно было бы внести такое изменение?
Моя проблема/беспокойство заключается в том, что если вы взаимодействуете с указанным контрактом, например, через браузер, вы можете просто обновить контракт, на который указывает интерфейс (после обновления). НО, как вы решаете проблему сохранения любых данных контракта при обновлении развертывания..?
Спасибо Т
Это нужно планировать с самого начала. Вам нужно будет разработать свой смарт-контракт с учетом следующих 5 пунктов:
Обновление нарушенных контрактов
Код необходимо будет изменить, если будут обнаружены ошибки или если необходимо внести улучшения. Нехорошо обнаружить ошибку, но не иметь возможности с ней справиться
...
Тем не менее, есть два основных подхода, которые наиболее часто используются. Проще всего иметь контракт реестра, содержащий адрес последней версии контракта. Более простой подход для контрактных пользователей — иметь контракт, который перенаправляет вызовы и данные в последнюю версию контракта.
Пример 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;
}
}
У этого подхода есть два основных недостатка:
Пользователи всегда должны искать текущий адрес, и любой, кто этого не делает, рискует использовать старую версию контракта.
Вам нужно будет тщательно продумать, как обращаться с данными контракта при замене контракта.
Альтернативный подход заключается в том, чтобы контракт перенаправлял вызовы и данные в последнюю версию контракта:
Пример 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 или библиотеки для развертывания контрактов.
ПРИМЕЧАНИЕ. Есть несколько ограничений , о которых вам следует знать, в отношении того, как вам нужно писать свои контракты и как вы должны их обновлять. Также в этом посте есть ряд обходных путей этих ограничений .
осевой