Возможность обновления Firefly на базе OpenZeppelin
*Русскоязычный перевод оригинальной статьи от 15 июня 2021 года
Есть две распространенные причины, по которым требуется возможность обновления смарт-контрактов: новые функции и безопасность (ошибки). Обновляемые контракты более масштабируемы и безопасны, поскольку их можно обновлять в соответствии с современными практиками и стандартами, действующими в рамках всего сообщества.
Поскольку Firefly — это протокол, управляемый сообществом держателей токенов, он должен обеспечивать гибкость для внесения изменений основной бизнес-логики посредством процесса децентрализованного управления. Вот почему тщательно проверенная платформа OpenZeppelin с открытым исходным кодом для обновления позволяет создавать передовые, обновляемые смарт-контракты с высоким уровнем безопасности. В этой статье контракты на страховой фонд и управление Firefly будут служить примерами того, как обновляемые контракты работают изнутри.
По замыслу смарт-контракты неизменны. Эта мощная функция предотвращает изменение доверенного контракта злоумышленником. Однако это также может быть препятствием, мешающим добавлять функции и повышать безопасность смарт контракта. С другой стороны, возможность обновления требует, чтобы адрес администратора был доверенным и ответственным за правильное изменение контрактов. Это добавляет основную точку отказа и противоречит ненадежной природе децентрализованных приложений. Чтобы дать возможность модернизации, сохраняя доверенность протоколу Firefly, право собственности на контракты передается децентрализованному управлению Firefly с помощью генезиса. Обновление требует, чтобы предложение было представлено и одобрено держателями собственного токена управления Firefly. Должен быть соблюден минимальный кворум, после которого большинство должно проголосовать за предложение о выполнении обновления.
Разобравшись как это работает, давайте теперь перейдем к основной теме: как сделать контракты обновляемыми. Обновляемые контракты достигаются с помощью шаблона прокси, который требует наличия двух отдельных контрактов для одного обновляемого. Как показано ниже, контракт «прокси» действует как интерфейс между внешним миром (пользователями) и контрактом «реализации», в котором хранится основная бизнес-логика.
Пользователь взаимодействует с прокси-контрактом, который перенаправляет полученные вызовы функций на контракт реализации, как показано на рисунке II.
Прокси-сервер также имеет несколько важных общедоступных функций, таких как setAdmin / updateAdmin (та же функция с двумя соглашениями об именах), которые позволяют пользователям изменять администратора прокси. Админ может быть адресом, принадлежащим одному пользователю, кошельку с несколькими подписями или другому контракту. Только текущий администратор прокси-сервера может передавать управление, поэтому к этим функциям обычно применяется модификатор onlyAdmin.
Администратор также является единственным адресом, который может вызывать функцию setImplementation на прокси-контакте, который обновляет адрес контракта реализации, хранящегося в прокси-сервере. После обновления прокси-контракт направляет все вызовы функций в новый контракт, как показано на рисунке III. Такой дизайн позволяет пользователям продолжать использовать ту же точку доступа (контракт прокси), одновременно имея доступ к новому контракту реализации.
Другой немаловажный процесс, необходимый для успешного обновления контракта, — это миграция текущего состояния контракта на реализацию в состояние нового контракта. Если новый контракт не имеет того же состояния, данных и параметров, что и старый, это может привести к большой потере данных или, что еще хуже, к сбою всего протокола. Это аналогично миграции базы данных при обновлении внутренних серверов приложения. Если приложение перемещается из AWS в Azure, а база данных также не переносится, приложение потеряет все предыдущие данные, такие как настройки клиента, баланс и т.д.
Написание кода для прокси контрактов и миграции — нетривиальная задача; это требует много времени и усилий, чтобы гарантировать, что обновление может быть успешно реализовано, когда это необходимо, с сохранением его предыдущего состояния. В этом заключается преимущество плагина Upgrades от OpenZeppelin — он обеспечивает основу для создания, тестирования, развертывания и обновления контрактов безопасным и упорядоченным образом. Плагин Upgrades реализует вышеупомянутый дизайн и логику прокси, позволяя разработчикам вместо этого сосредоточиться на обновлении бизнес-логики контракта на реализацию. Это достигается путем принуждения пользователей следовать этим руководящим принципам фреймворка. Вот несколько основных из них:
- Контракт с возможностью обновления должен происходить из базовых контрактов с возможностью обновления (при необходимости)
- Обновляемый контракт должен реализовывать свой собственный публичный инициализатор, который может быть запущен только один раз — при развертывании первого контракта реализации.
- Новая реализация обновляемого контракта не может изменить порядок хранения переменных
Эти и многие другие руководящие принципы, изложенные в структуре, соблюдаются, чтобы сделать страховой фонд Firefly и контракты на управление обновляемыми. За исключением контракта DETToken, который не подлежит обновлению из-за важных соображений дизайна, все другие контракты можно легко обновить с помощью предложения по управлению. Эти контракты включают:
- InsuranceFund: позволяет пользователям делать ставки в USDC и получать вознаграждения в DET
- TokenVesting: позволяет пользователям создавать контракты на передачу прав и помогает в распределении токенов, переданных в течение определенного периода времени
- Governance: позволяет пользователям создавать предложения по обновлению протокола
- TimeLock: позволяет руководству выполнять действия, предложенные в предложении
На рисунке V изображен график владения / администрирования; Контракт TimeLock является администратором всех прокси, включая его собственный. Когда предложение инициируется и принимается посредством голосования по Управлению, Контракт TimeLock вызывается Управлением для выполнения действий, указанных в предложении. Самостоятельное администрирование TimeLock — это особый случай, который был тщательно исследован, проверен и протестирован перед внедрением. Он существует для обслуживания случая, когда предложение по обновлению для обновления контракта TimeLock передается Управлению. В таком сценарии сам контракт TimeLock должен будет обновить свой адрес реализации, вызвав функцию upgradeTo своего прокси, поскольку он находится под модификатором onlyAdmin.
Прежде чем предложение может быть представлено руководству, предлагающий должен развернуть новую реализацию в сети следующим образом:
// The proposer must first prepare and deloy the new implementation of the contract
// in order to upgrade // token vesting contract deployed on local/testnet/mainnet const tokenVesting:TokenVesting; const proxyAddress:string = tokenVesting.address // returns the address of proxy // gets the new implementation for TokenVesting const factory = await hardhat.ethers.getContractFactory(`TokenVestingV2`) // openzeppelin upgrade function first checks if the new implementation
// adheres to upgrade plugin rules and deploys on the network and returns the address const newImplAddress = await hardhat.upgrades.prepareUpgrade(proxyAddress, factory)
Как только нововведения установлены, предложение может быть внесено в Governance для обновления прокси контракта TokenVesting необходимо выполнить следующие действия:
const values = 0;
// address of the proxy
const targets = tokenVesting.address; // signature of the function
const signatures = "upgradeTo(address)"; // argument to upgradeTo is the address of new
implementation const callData = encodeParameters(["address"], [newImplAddress]); // governance contract deployed on local/testnet/mainnet const governance:Governance; // proposes an upgrade for TokenVesting await governance.propose( targets,
values,
signatures,
callDatas,
"Upgrading TokenVesting to V2"
);// once the proposal is successfully approved by majority of DET holders,
// it can be executed via governance, which internally calls TimeLock to execute
// each function call in proposal await governance.execute(<proposalId>)
Как только предложение набирает достаточно голосов, Governance выполняет его. Governance использует TimeLock для выполнения upgradeTo
функции TokenVesting Contract proxy и обновляет реализацию на новый адрес.
- Firefly