Шаблон прокси позволяет смарт-контрактам обновлять свою логику, сохраняя при этом свои адреса в цепочке и значения состояния. Вызов прокси-контракта выполнит код из логического контракта через delegateCall, чтобы изменить состояние прокси-контракта.
В этой статье будет представлен обзор типов прокси-контрактов, связанных с ними инцидентов безопасности и рекомендаций, а также передовой опыт использования прокси-контрактов.
Введение в обновляемые контракты и режим прокси
Мы все знаем о «неподдающейся манипуляции» функции блокчейна, и код смарт-контракта нельзя изменить после развертывания в блокчейне.
Поэтому, когда разработчики хотят обновить код контракта для обновления логики, исправления ошибок или обновлений безопасности, они должны развернуть новый контракт, и будет сгенерирован новый адрес контракта.
Чтобы решить эту проблему, вы можете использовать режим прокси.
Режим прокси реализует возможность обновления контракта без изменения адреса развертывания контракта, что в настоящее время является наиболее распространенным режимом обновления контракта.
Прокси-режим — это обновляемая система контрактов, включающая прокси-контракт и контракт на реализацию логики.
Прокси-контракт обрабатывает взаимодействие с пользователем и хранение данных и состояния контракта. Пользовательский вызов прокси-контракта выполнит код из логического контракта через delegatecall(), тем самым изменив состояние прокси-контракта. Обновление реализуется путем обновления адреса логического контракта, записанного в предопределенном слоте хранения прокси-контракта.
Три более традиционных режима прокси — это прозрачный прокси, прокси UUPS и прокси-маяк.
Прозрачный прокси
В режиме прозрачного прокси функция обновления реализована в прокси-контракте. Роль администратора прокси-контракта получает прямые полномочия на управление прокси-контрактом для обновления логического адреса реализации, соответствующего прокси. Вызывающие абоненты без прав администратора делегируют свои вызовы реализующему контракту.
Примечание. Администратор прокси-контракта не может играть ключевую роль в логической реализации контракта и даже не может быть обычным пользователем, потому что прокси-администратор не может взаимодействовать с контрактом на реализацию.
Прокси UUPS
В режиме UUPS (Universal Upgradeable Proxy Standard) функция обновления контракта реализована в логическом контракте. Поскольку механизм обновления хранится в контракте логики, обновленная версия может удалить логику, связанную с обновлением, чтобы запретить будущие обновления. В этом режиме все вызовы прокси-контракта перенаправляются в контракт реализации логики.
Прокси-маяк
Режим прокси-маяка позволяет нескольким прокси-контрактам совместно использовать одну и ту же логическую реализацию, ссылаясь на контракт маяка. Контракт маяка предоставляет адрес контракта реализации логики для вызываемого контракта прокси.При обновлении до нового адреса реализации логики необходимо обновить только адрес, записанный в контракте маяка.
Злоупотребление прокси-сервером и нарушения безопасности
Разработчики могут использовать контракты режима прокси для реализации обновляемых систем контрактов. Тем не менее, режим прокси также имеет определенные эксплуатационные пороги, при неправильном использовании он может привести к разрушительным проблемам с безопасностью проекта. В следующих разделах показаны инциденты, связанные с неправильным использованием прокси-серверов, а также риски централизации, которые представляют прокси-серверы.
Раскрытие управляемого прокси-ключа
Администратор прокси-сервера управляет механизмом обновления прозрачного режима прокси-сервера. В случае утечки закрытого ключа администратора злоумышленник может обновить логический контракт и выполнить свою собственную злонамеренную логику в состоянии прокси-сервера.
5 марта 2021 года PAID Network подверглась атаке «чеканки», вызванной плохим управлением закрытым ключом. PAID Network была использована злоумышленником, который украл закрытый ключ администратора прокси-сервера и активировал механизм обновления для изменения логического контракта.
После обновления злоумышленник может уничтожить PAID пользователя и отчеканить для себя партию PAID, которую потом можно будет продать. В самом коде нет уязвимости безопасности, но злоумышленник получил закрытый ключ для обновления контракта у администратора.
Для режима прокси UUPS во время инициализации прокси-контракта начальные параметры передаются в прокси-контракт вызывающей стороной, а затем прокси-контракт вызывает функцию initialize() в логическом контракте для достижения инициализации.
Функция initialize() обычно защищена модификатором «initializer», чтобы ограничить вызов функции только один раз. После вызова функции initialize() с точки зрения прокси-контракта инициализируется логический контракт.
Однако с точки зрения логического контракта логический контракт не инициализируется, потому что initialize() не вызывается непосредственно в логическом контракте. Учитывая, что сам логический контракт не инициализирован, любой может вызвать функцию initialize() для его инициализации, присвоить переменной состояния вредоносное значение и, возможно, взять на себя логический контракт.
Последствия передачи логического контракта зависят от кода контракта в системе. В худшем случае злоумышленник может обновить логический контракт в режиме прокси-сервера UUPS до вредоносного контракта и выполнить вызов функции «самоуничтожения», что может привести к тому, что весь контракт прокси станет бесполезным, а активы в контракте будут удалены. быть безвозвратно уничтожены потеряны.
случай
① Заморозка мультиподписи по четности: логический контракт не инициализирован. Злоумышленник запускает инициализацию многих кошельков и блокирует эфир в контракте, вызывая selfdestruct().
② Harvest Finance, Teller, KeeperDAO и Rivermen используют неинициализированные логические контракты, что позволит злоумышленникам произвольно устанавливать параметры инициализации контрактов и выполнять selfdestruct() во время delegatecall() для уничтожения прокси-контракта.
Конфликт хранилища
В обновляемой контрактной системе прокси-контракт не объявляет переменные состояния, а использует псевдослучайные слоты хранения для хранения важных данных.
Прокси-контракты хранят значения логических переменных состояния контракта относительно того места, где они были объявлены. Если прокси-контракт объявляет свои собственные переменные состояния, а прокси-контракт и логический контракт пытаются использовать один и тот же слот хранилища, возникает конфликт хранилища.
Прокси-контракт, предоставляемый библиотекой OpenZeppelin, не объявляет переменные состояния в контракте, но на основе стандарта EIP 1967 сохраняет значение, которое необходимо сохранить (например, адрес управления), в определенном слоте хранения для предотвращения конфликтов.
случай
23 июля 2022 года по пекинскому времени децентрализованная музыкальная платформа Audius была взломана из-за введения новой логики в прокси-контракт, что привело к конфликтам хранения.
Контракт прокси объявляет переменную состояния адреса proxyAdmin, и ее значение будет считано неправильно при выполнении кода логического контракта.
Значение proxyAdmin, настроенное участником проекта, было ошибочно расценено как значение initialized и initialized, так что модификатор initializer вернул неправильный результат, что позволило злоумышленнику снова вызвать функцию initialize() и предоставить себе полномочия на управление договор. Затем злоумышленники изменили параметры голосования и передали свое вредоносное предложение, чтобы украсть активы Audius.
Вызов делегата() в логическом контракте или ненадежном контракте
Предположим, функция delegatecall() существует в логическом контракте, и контракт не проверяет должным образом цель вызова. В этом случае злоумышленник может использовать эту функцию для выполнения вызовов вредоносных контрактов, которые они контролируют, для подрыва реализации логики или для выполнения пользовательской логики.
Точно так же, если в логическом контракте есть неограниченная функция address.call(), как только злоумышленник злонамеренно предоставит поля адреса и данных, ее можно использовать в качестве прокси-контракта.
случай
Атаки Pickle Finance, Furucombo и dYdX.
В этих инцидентах уязвимый контракт был одобрен токеном пользователя, и в контракте есть call()/delegatecall(), предоставленный пользователем для вызова адреса и данных контракта, злоумышленник сможет вызвать Контракт функции transferFrom() для вывода пользовательских балансов. Во время инцидента с dYdX dYdX провела собственную белую атаку для защиты средств.
Лучшие практики
в целом
(1) Используйте режим прокси только при необходимости
Не каждый контракт нуждается в обновлении. Как показано выше, существует множество рисков, связанных с использованием шаблона прокси. Свойство «обновляемое» также вызывает проблемы с доверием, поскольку администраторы прокси-серверов могут обновлять контракты без согласия сообщества. Мы рекомендуем интегрировать шаблон прокси в проекты только в случае необходимости.
(2) Не изменяйте прокси-библиотеку
Библиотека прокси-контрактов сложна, особенно та ее часть, которая связана с механизмами управления хранилищем и обновлениями. Любые ошибки в модификации повлияют на работу прокси и логических контрактов. Большое количество серьезных ошибок, связанных с агентами, которые мы обнаружили в ходе наших аудитов, были вызваны неправильными модификациями библиотеки агентов. Инцидент с Audius — яркий пример последствий ненадлежащего изменения агентских контрактов.
Ключевые моменты работы с агентскими контрактами и управления ими
(1) Инициализировать логический контракт
Злоумышленник может захватить неинициализированный логический контракт и потенциально скомпрометировать систему прокси-контрактов. Поэтому инициализируйте логический контракт после развертывания или используйте _disableInitializers() в конструкторе логического контракта, чтобы автоматически отключить инициализацию.
(2) Обеспечьте безопасность учетной записи управления агентом.
Обновляемая система контрактов обычно требует привилегированной роли «прокси-администратора» для управления обновлениями контрактов. В случае утечки ключа управления злоумышленник может свободно обновить контракт до вредоносного контракта, который может украсть активы пользователей. Мы рекомендуем тщательно управлять закрытыми ключами учетных записей администратора прокси, чтобы избежать любого потенциального риска взлома. Кошельки с мультиподписью можно использовать для предотвращения сбоев в управлении ключами в одной точке.
(3) Используйте отдельную учетную запись для прозрачного управления прокси.
Управление прокси-сервером и управление логикой должны быть отдельными адресами, чтобы предотвратить потерю взаимодействия с реализацией логики. Если управление прокси-сервером и логическое управление относятся к одному и тому же адресу, вызовы для выполнения привилегированных функций не перенаправляются, что запрещает внесение изменений в функции управления.
Связано с хранением прокси-контрактов
(1) Будьте осторожны при объявлении переменных состояния в прокси-контрактах.
Как объяснялось во взломе Audius, прокси-контракты должны быть осторожны при объявлении собственных переменных состояния. Переменные состояния, объявленные обычным образом в прокси-контрактах, могут вызывать конфликты данных при чтении и записи данных. Если для прокси-контракта требуется переменная состояния, сохраните значение в ячейке хранилища, например EIP1967, чтобы предотвратить конфликты при выполнении кода логического контракта.
(2) Поддерживать порядок объявления переменных и тип логического контракта
Каждая версия логического контракта должна поддерживать один и тот же порядок и тип переменных состояния, а новые переменные состояния необходимо добавлять в конец существующих переменных. В противном случае вызовы делегатов могут привести к тому, что прокси-контракты будут считывать или перезаписывать неверные сохраненные значения, а старые данные могут быть связаны с вновь объявленными переменными, что может вызвать серьезные проблемы для приложений.
(3) Включите пробелы в хранилище в базовый контракт
Логические контракты должны включать пробелы в памяти в коде контракта, чтобы предвидеть новые переменные состояния при развертывании новых логических реализаций. После добавления новой переменной состояния необходимо соответствующим образом обновить размер разрыва.
(4) Не устанавливайте значение переменной состояния в конструкторе или процессе объявления
Присвоение переменной состояния во время объявления или в конструкторе влияет только на значение в логическом контракте, а не в прокси-контракте. Неизменяемые параметры должны быть назначены с помощью функции initialize().
Контрактное наследование
(1) Обновляемые контракты могут наследоваться только от других обновляемых контрактов.
Обновляемые контракты имеют другую структуру, чем необновляемые контракты. Например, конструктор не совместим с изменением состояния агента, он использует функцию initialize() для установки переменных состояния.
Любой контракт, который наследуется от другого контракта, должен использовать функцию initialize() своего унаследованного контракта для назначения соответствующих переменных. При использовании библиотеки OpenZeppelin или написании собственного кода убедитесь, что обновляемые контракты могут наследовать только другие обновляемые контракты.
(2) Не создавать экземпляры новых контрактов в логических контрактах
Контракты, созданные и реализованные через Solidity, нельзя будет обновить. Контракты следует развертывать по отдельности и передавать свой адрес в качестве параметра обновляемому логическому контракту для достижения обновляемого состояния.
(3) Риск инициализации родительского контракта
При инициализации родительского контракта функция __{ContractName}_init инициализирует его родительский контракт. Несколько вызовов __{ContractName}_init могут привести к повторной инициализации родительского контракта. Обратите внимание, что __{ContractName}_init_unchained() будет инициализировать только параметры {ContractName} и не будет вызывать инициализатор своего родительского контракта.
Однако это не рекомендуется, поскольку все родительские контракты должны быть инициализированы, а невыполнение инициализации требуемых контрактов вызовет проблемы с выполнением в будущем.
Реализация логического контракта
Избегайте selfdestruct() или selegatecall()/call() для ненадежных контрактов
Если в контракте есть selfdestruct() или delegatecall(), злоумышленник может использовать эти функции, чтобы нарушить реализацию логики или выполнить пользовательскую логику. Разработчики должны проверять пользовательский ввод и не разрешать контрактам выполнять вызовы делегатов/вызовы ненадежных контрактов. Кроме того, не рекомендуется использовать delegatecall() в логических контрактах, потому что это было бы громоздко управлять схемой хранения в цепочке делегатов из нескольких контрактов.
Написано в конце
Прокси-контракты обходят неизменную природу блокчейнов, позволяя протоколам обновлять логику своего кода после развертывания. Тем не менее, разработка прокси-контрактов по-прежнему требует особой осторожности, а неправильная реализация может привести к проблемам с безопасностью и логикой проекта.
В целом, рекомендуется использовать авторитетные и тщательно протестированные решения, поскольку режимы Transparent, UUPS и Beacon Proxy имеют проверенные механизмы обновления для соответствующих вариантов использования. В дополнение к этому, привилегированные роли для эскалирующих агентов также должны надежно управляться, чтобы злоумышленники не могли изменить логику агента.
Контракт реализации логики также должен быть осторожным, чтобы не использовать delegatecall(), который может помешать злоумышленникам выполнить какой-либо вредоносный код, такой как selfdestruct().
Хотя соблюдение передовых практик обеспечивает стабильное развертывание прокси-контрактов при сохранении гибкости с возможностью обновления, весь код подвержен новым проблемам безопасности или логики, которые могут поставить под угрозу проект. Таким образом, весь код лучше всего проверять группой экспертов по безопасности, имеющих опыт аудита и защиты протоколов прокси-контрактов.
Посмотреть Оригинал
Содержание носит исключительно справочный характер и не является предложением или офертой. Консультации по инвестициям, налогообложению или юридическим вопросам не предоставляются. Более подробную информацию о рисках см. в разделе «Дисклеймер».
Нарушение неизменности блокчейна: как прокси-модель может обеспечить обновление смарт-контрактов
Шаблон прокси позволяет смарт-контрактам обновлять свою логику, сохраняя при этом свои адреса в цепочке и значения состояния. Вызов прокси-контракта выполнит код из логического контракта через delegateCall, чтобы изменить состояние прокси-контракта.
В этой статье будет представлен обзор типов прокси-контрактов, связанных с ними инцидентов безопасности и рекомендаций, а также передовой опыт использования прокси-контрактов.
Введение в обновляемые контракты и режим прокси
Мы все знаем о «неподдающейся манипуляции» функции блокчейна, и код смарт-контракта нельзя изменить после развертывания в блокчейне.
Поэтому, когда разработчики хотят обновить код контракта для обновления логики, исправления ошибок или обновлений безопасности, они должны развернуть новый контракт, и будет сгенерирован новый адрес контракта.
Чтобы решить эту проблему, вы можете использовать режим прокси.
Режим прокси реализует возможность обновления контракта без изменения адреса развертывания контракта, что в настоящее время является наиболее распространенным режимом обновления контракта.
Прокси-режим — это обновляемая система контрактов, включающая прокси-контракт и контракт на реализацию логики.
Прокси-контракт обрабатывает взаимодействие с пользователем и хранение данных и состояния контракта. Пользовательский вызов прокси-контракта выполнит код из логического контракта через delegatecall(), тем самым изменив состояние прокси-контракта. Обновление реализуется путем обновления адреса логического контракта, записанного в предопределенном слоте хранения прокси-контракта.
Три более традиционных режима прокси — это прозрачный прокси, прокси UUPS и прокси-маяк.
Прозрачный прокси
В режиме прозрачного прокси функция обновления реализована в прокси-контракте. Роль администратора прокси-контракта получает прямые полномочия на управление прокси-контрактом для обновления логического адреса реализации, соответствующего прокси. Вызывающие абоненты без прав администратора делегируют свои вызовы реализующему контракту.
Примечание. Администратор прокси-контракта не может играть ключевую роль в логической реализации контракта и даже не может быть обычным пользователем, потому что прокси-администратор не может взаимодействовать с контрактом на реализацию.
Прокси UUPS
В режиме UUPS (Universal Upgradeable Proxy Standard) функция обновления контракта реализована в логическом контракте. Поскольку механизм обновления хранится в контракте логики, обновленная версия может удалить логику, связанную с обновлением, чтобы запретить будущие обновления. В этом режиме все вызовы прокси-контракта перенаправляются в контракт реализации логики.
Прокси-маяк
Режим прокси-маяка позволяет нескольким прокси-контрактам совместно использовать одну и ту же логическую реализацию, ссылаясь на контракт маяка. Контракт маяка предоставляет адрес контракта реализации логики для вызываемого контракта прокси.При обновлении до нового адреса реализации логики необходимо обновить только адрес, записанный в контракте маяка.
Злоупотребление прокси-сервером и нарушения безопасности
Разработчики могут использовать контракты режима прокси для реализации обновляемых систем контрактов. Тем не менее, режим прокси также имеет определенные эксплуатационные пороги, при неправильном использовании он может привести к разрушительным проблемам с безопасностью проекта. В следующих разделах показаны инциденты, связанные с неправильным использованием прокси-серверов, а также риски централизации, которые представляют прокси-серверы.
Раскрытие управляемого прокси-ключа
Администратор прокси-сервера управляет механизмом обновления прозрачного режима прокси-сервера. В случае утечки закрытого ключа администратора злоумышленник может обновить логический контракт и выполнить свою собственную злонамеренную логику в состоянии прокси-сервера.
5 марта 2021 года PAID Network подверглась атаке «чеканки», вызванной плохим управлением закрытым ключом. PAID Network была использована злоумышленником, который украл закрытый ключ администратора прокси-сервера и активировал механизм обновления для изменения логического контракта.
После обновления злоумышленник может уничтожить PAID пользователя и отчеканить для себя партию PAID, которую потом можно будет продать. В самом коде нет уязвимости безопасности, но злоумышленник получил закрытый ключ для обновления контракта у администратора.
** Реализация неинициализированного прокси-сервера UUPS **
Для режима прокси UUPS во время инициализации прокси-контракта начальные параметры передаются в прокси-контракт вызывающей стороной, а затем прокси-контракт вызывает функцию initialize() в логическом контракте для достижения инициализации.
Функция initialize() обычно защищена модификатором «initializer», чтобы ограничить вызов функции только один раз. После вызова функции initialize() с точки зрения прокси-контракта инициализируется логический контракт.
Однако с точки зрения логического контракта логический контракт не инициализируется, потому что initialize() не вызывается непосредственно в логическом контракте. Учитывая, что сам логический контракт не инициализирован, любой может вызвать функцию initialize() для его инициализации, присвоить переменной состояния вредоносное значение и, возможно, взять на себя логический контракт.
Последствия передачи логического контракта зависят от кода контракта в системе. В худшем случае злоумышленник может обновить логический контракт в режиме прокси-сервера UUPS до вредоносного контракта и выполнить вызов функции «самоуничтожения», что может привести к тому, что весь контракт прокси станет бесполезным, а активы в контракте будут удалены. быть безвозвратно уничтожены потеряны.
случай
① Заморозка мультиподписи по четности: логический контракт не инициализирован. Злоумышленник запускает инициализацию многих кошельков и блокирует эфир в контракте, вызывая selfdestruct().
② Harvest Finance, Teller, KeeperDAO и Rivermen используют неинициализированные логические контракты, что позволит злоумышленникам произвольно устанавливать параметры инициализации контрактов и выполнять selfdestruct() во время delegatecall() для уничтожения прокси-контракта.
Конфликт хранилища
В обновляемой контрактной системе прокси-контракт не объявляет переменные состояния, а использует псевдослучайные слоты хранения для хранения важных данных.
Прокси-контракты хранят значения логических переменных состояния контракта относительно того места, где они были объявлены. Если прокси-контракт объявляет свои собственные переменные состояния, а прокси-контракт и логический контракт пытаются использовать один и тот же слот хранилища, возникает конфликт хранилища.
Прокси-контракт, предоставляемый библиотекой OpenZeppelin, не объявляет переменные состояния в контракте, но на основе стандарта EIP 1967 сохраняет значение, которое необходимо сохранить (например, адрес управления), в определенном слоте хранения для предотвращения конфликтов.
случай
23 июля 2022 года по пекинскому времени децентрализованная музыкальная платформа Audius была взломана из-за введения новой логики в прокси-контракт, что привело к конфликтам хранения.
Контракт прокси объявляет переменную состояния адреса proxyAdmin, и ее значение будет считано неправильно при выполнении кода логического контракта.
Значение proxyAdmin, настроенное участником проекта, было ошибочно расценено как значение initialized и initialized, так что модификатор initializer вернул неправильный результат, что позволило злоумышленнику снова вызвать функцию initialize() и предоставить себе полномочия на управление договор. Затем злоумышленники изменили параметры голосования и передали свое вредоносное предложение, чтобы украсть активы Audius.
Вызов делегата() в логическом контракте или ненадежном контракте
Предположим, функция delegatecall() существует в логическом контракте, и контракт не проверяет должным образом цель вызова. В этом случае злоумышленник может использовать эту функцию для выполнения вызовов вредоносных контрактов, которые они контролируют, для подрыва реализации логики или для выполнения пользовательской логики.
Точно так же, если в логическом контракте есть неограниченная функция address.call(), как только злоумышленник злонамеренно предоставит поля адреса и данных, ее можно использовать в качестве прокси-контракта.
случай
Атаки Pickle Finance, Furucombo и dYdX.
В этих инцидентах уязвимый контракт был одобрен токеном пользователя, и в контракте есть call()/delegatecall(), предоставленный пользователем для вызова адреса и данных контракта, злоумышленник сможет вызвать Контракт функции transferFrom() для вывода пользовательских балансов. Во время инцидента с dYdX dYdX провела собственную белую атаку для защиты средств.
Лучшие практики
в целом
(1) Используйте режим прокси только при необходимости
Не каждый контракт нуждается в обновлении. Как показано выше, существует множество рисков, связанных с использованием шаблона прокси. Свойство «обновляемое» также вызывает проблемы с доверием, поскольку администраторы прокси-серверов могут обновлять контракты без согласия сообщества. Мы рекомендуем интегрировать шаблон прокси в проекты только в случае необходимости.
(2) Не изменяйте прокси-библиотеку
Библиотека прокси-контрактов сложна, особенно та ее часть, которая связана с механизмами управления хранилищем и обновлениями. Любые ошибки в модификации повлияют на работу прокси и логических контрактов. Большое количество серьезных ошибок, связанных с агентами, которые мы обнаружили в ходе наших аудитов, были вызваны неправильными модификациями библиотеки агентов. Инцидент с Audius — яркий пример последствий ненадлежащего изменения агентских контрактов.
Ключевые моменты работы с агентскими контрактами и управления ими
(1) Инициализировать логический контракт
Злоумышленник может захватить неинициализированный логический контракт и потенциально скомпрометировать систему прокси-контрактов. Поэтому инициализируйте логический контракт после развертывания или используйте _disableInitializers() в конструкторе логического контракта, чтобы автоматически отключить инициализацию.
(2) Обеспечьте безопасность учетной записи управления агентом.
Обновляемая система контрактов обычно требует привилегированной роли «прокси-администратора» для управления обновлениями контрактов. В случае утечки ключа управления злоумышленник может свободно обновить контракт до вредоносного контракта, который может украсть активы пользователей. Мы рекомендуем тщательно управлять закрытыми ключами учетных записей администратора прокси, чтобы избежать любого потенциального риска взлома. Кошельки с мультиподписью можно использовать для предотвращения сбоев в управлении ключами в одной точке.
(3) Используйте отдельную учетную запись для прозрачного управления прокси.
Управление прокси-сервером и управление логикой должны быть отдельными адресами, чтобы предотвратить потерю взаимодействия с реализацией логики. Если управление прокси-сервером и логическое управление относятся к одному и тому же адресу, вызовы для выполнения привилегированных функций не перенаправляются, что запрещает внесение изменений в функции управления.
Связано с хранением прокси-контрактов
(1) Будьте осторожны при объявлении переменных состояния в прокси-контрактах.
Как объяснялось во взломе Audius, прокси-контракты должны быть осторожны при объявлении собственных переменных состояния. Переменные состояния, объявленные обычным образом в прокси-контрактах, могут вызывать конфликты данных при чтении и записи данных. Если для прокси-контракта требуется переменная состояния, сохраните значение в ячейке хранилища, например EIP1967, чтобы предотвратить конфликты при выполнении кода логического контракта.
(2) Поддерживать порядок объявления переменных и тип логического контракта
Каждая версия логического контракта должна поддерживать один и тот же порядок и тип переменных состояния, а новые переменные состояния необходимо добавлять в конец существующих переменных. В противном случае вызовы делегатов могут привести к тому, что прокси-контракты будут считывать или перезаписывать неверные сохраненные значения, а старые данные могут быть связаны с вновь объявленными переменными, что может вызвать серьезные проблемы для приложений.
(3) Включите пробелы в хранилище в базовый контракт
Логические контракты должны включать пробелы в памяти в коде контракта, чтобы предвидеть новые переменные состояния при развертывании новых логических реализаций. После добавления новой переменной состояния необходимо соответствующим образом обновить размер разрыва.
(4) Не устанавливайте значение переменной состояния в конструкторе или процессе объявления
Присвоение переменной состояния во время объявления или в конструкторе влияет только на значение в логическом контракте, а не в прокси-контракте. Неизменяемые параметры должны быть назначены с помощью функции initialize().
Контрактное наследование
(1) Обновляемые контракты могут наследоваться только от других обновляемых контрактов.
Обновляемые контракты имеют другую структуру, чем необновляемые контракты. Например, конструктор не совместим с изменением состояния агента, он использует функцию initialize() для установки переменных состояния.
Любой контракт, который наследуется от другого контракта, должен использовать функцию initialize() своего унаследованного контракта для назначения соответствующих переменных. При использовании библиотеки OpenZeppelin или написании собственного кода убедитесь, что обновляемые контракты могут наследовать только другие обновляемые контракты.
(2) Не создавать экземпляры новых контрактов в логических контрактах
Контракты, созданные и реализованные через Solidity, нельзя будет обновить. Контракты следует развертывать по отдельности и передавать свой адрес в качестве параметра обновляемому логическому контракту для достижения обновляемого состояния.
(3) Риск инициализации родительского контракта
При инициализации родительского контракта функция __{ContractName}_init инициализирует его родительский контракт. Несколько вызовов __{ContractName}_init могут привести к повторной инициализации родительского контракта. Обратите внимание, что __{ContractName}_init_unchained() будет инициализировать только параметры {ContractName} и не будет вызывать инициализатор своего родительского контракта.
Однако это не рекомендуется, поскольку все родительские контракты должны быть инициализированы, а невыполнение инициализации требуемых контрактов вызовет проблемы с выполнением в будущем.
Реализация логического контракта
Избегайте selfdestruct() или selegatecall()/call() для ненадежных контрактов
Если в контракте есть selfdestruct() или delegatecall(), злоумышленник может использовать эти функции, чтобы нарушить реализацию логики или выполнить пользовательскую логику. Разработчики должны проверять пользовательский ввод и не разрешать контрактам выполнять вызовы делегатов/вызовы ненадежных контрактов. Кроме того, не рекомендуется использовать delegatecall() в логических контрактах, потому что это было бы громоздко управлять схемой хранения в цепочке делегатов из нескольких контрактов.
Написано в конце
Прокси-контракты обходят неизменную природу блокчейнов, позволяя протоколам обновлять логику своего кода после развертывания. Тем не менее, разработка прокси-контрактов по-прежнему требует особой осторожности, а неправильная реализация может привести к проблемам с безопасностью и логикой проекта.
В целом, рекомендуется использовать авторитетные и тщательно протестированные решения, поскольку режимы Transparent, UUPS и Beacon Proxy имеют проверенные механизмы обновления для соответствующих вариантов использования. В дополнение к этому, привилегированные роли для эскалирующих агентов также должны надежно управляться, чтобы злоумышленники не могли изменить логику агента.
Контракт реализации логики также должен быть осторожным, чтобы не использовать delegatecall(), который может помешать злоумышленникам выполнить какой-либо вредоносный код, такой как selfdestruct().
Хотя соблюдение передовых практик обеспечивает стабильное развертывание прокси-контрактов при сохранении гибкости с возможностью обновления, весь код подвержен новым проблемам безопасности или логики, которые могут поставить под угрозу проект. Таким образом, весь код лучше всего проверять группой экспертов по безопасности, имеющих опыт аудита и защиты протоколов прокси-контрактов.