Как стать автором
Обновить
53.28
MetaLamp
Доступно рассказываем о web3 и блокчейне

Почему native Account Abstraction в zkSync лучше ERC-4337

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров1.3K

Привет, Хабр! Меня зовут Рома Ярлыков, я разрабатываю смарт-контракты на Solidity в MetaLamp. В этой статье я расскажу, как реализован Account Abstraction в zkSync и чем такой подход отличается от уже популярного стандарта ERC-4337. Чтобы понять, что за блокчейн zkSync, рекомендую сначала прочитать эту статью. А чтобы разобраться с Account Abstraction можно прочитать вот эту.

В Ethereum существует два типа аккаунтов: внешние аккаунты (Externally Owned Accounts, EOA), которые могут инициировать транзакции, но не обладают программируемой логикой, и смарт-контракты, имеющие возможность выполнения произвольной логики, но неспособные инициировать транзакции самостоятельно. Это вызывает большое количество проблем при взаимодействии с экосистемой блокчейна и повышает порог входа для новых пользователей.

Первым шагом к решению этой проблемы стал стандарт ERC-4337, который внедряет концепцию Abstract Accounts (AA) в дополнение к EOA и обычным смарт-контрактам. Согласно стандарту предлагается объединить преимущества EOA и смарт-контрактов.

Напомню основные отличия EOA и AA.

EOAs

Abstract Accounts

Проверка подписи инициатора транзакции

Логика проверки подписи транзакций жестко закодирована в EVM

Логика проверки подписи программируема в учетной записи смарт-контракта

Обращение с приватными ключами

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

Могут использоваться альтернативные методы для подписи транзакций, такие как биометрия или аутентификаторы

Возможность восстановления

Нет способа восстановить приватный ключ. Если вы потеряли ваш приватник, вы теряете ваши средства

Методы восстановления могут быть реализованы в смарт-контракте, например, социальное восстановление или резервные ключи

С этим разобрались, теперь поговорим о том, как блокчейн zkSync переосмыслил AA чтобы встроить его в архитектуру самого блокчейна. Для начала стоит посмотреть на различия потока транзакций EOA и AA в Ethereum и zkSync.

Примечание: В своей документации zkSync использует термин "Smart contract account", т.к. они делают акцент на том, что EOA в zkSync тоже является AA. Для простоты восприятия здесь я буду чаще использовать изначальный термин - Account Abstraction (AA), но на схемах будет "Smart account" или "Smart contract account" (когда я рисовал схемы для нашей blockchain-wiki - использовал терминологию zkSync), если что - это фактически одно и то же.

Верхнеуровневые различия в потоке транзакций

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

EOA и AA транзакции в Ethereum

ERC-4337 не требует изменений в основном протоколе Ethereum, но для реализации АА необходимо внедрять специальный сервис - Bundler. Этот сервис отвечает за работу альтернативного mempool по обработке и отправке транзакций в блокчейн. Транзакции проходят через системный контракт EntryPoint.sol для валидации.

Mempool - область хранения для транзакций, ожидающих подтверждения и включения в блок.

Путь транзакций EOA и AA в Ethereum
Путь транзакций EOA и AA в Ethereum

Однако, этот подход имеет ограничения, например, невозможность использования Paymaster в связке с EOA в рамках ERC-4337.

Paymaster - смарт-контракт который может осуществлять оплату газа в нативной валюте за пользователя. При этом он может обладать любой логикой оплаты: списывать у пользователя ERC20, делать доп. проверки либо просто может спонсировать любые транзакции не требуя ничего взамен.

Это проблема, потому что для реализации безгазовых транзакций потребуется интеграция отдельного сервиса, например GSN (к слову, на базе опыта которого был разработан данный ERC).

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

EOA и AA транзакции в zkSync

Теперь посмотрим какой путь проделывает транзакция в решении от zkSync. В данном случае у разработчиков была возможность реализовать нативную поддержку Abstract Account и Paymaster без необходимости сторонних сервисов. Здесь все аккаунты являются смарт-контрактами (даже EOA), также в zkSync любые аккаунты являются "first-class citizen", то есть они собираются в единый Mempool и обрабатываются одинаково.

Путь транзакций EOA и AA в zkSync
Путь транзакций EOA и AA в zkSync

После Mempool транзакции обрабатываются и выполняются оператором. Если проводить параллель с Ethereum, здесь оператор будет выполнять роль валидатора и бандлера. После чего транзакция попадает в смарт-контракт bootloader.

bootloader - это системный смарт-контракт L2 zkSync. По сути это ядро протокола, контракт связывает и консолидирует работу всех основных системных смарт-контрактов протокола.

В разрезе AA, контракт bootloader является аналогом EntryPoint. Если грубо описать происходящее - на этом этапе определяется есть ли какая-то дополнительная логика у аккаунта и по необходимости вызывается Paymaster.

Сравнительная таблица ERC-4337 и Native AA zkSync:

ERC-4337

Native AA zkSync

Степень интеграции

Не нативная. Требует доп инфраструктуру и сервисы

Нативная. Поддерживается в блокчейне "из коробки"

Учетные записи и платежные агенты

Поддержка Paymaster только для AA

Все аккаунты, включая EOA, ведут себя как аккаунты смарт-контрактов и поддерживают Paymaster

Поток транзакций

Два потока транзакций с разной логикой, два мемпула

Единый мемпул и поток транзакций, не зависит от того, инициируется ли транзакция EOA или AA

Формирование транзакций

Отправка объектов UserOperation вместо обычных транзакций

Обычные транзакции, но с дополнительными полями

Далее рассмотрим нативную реализацию AA более подробно и по порядку.

Подробный разбор native AA

Создание AA в zkSync

Как я уже говорил, EOA в zkSync также являются смарт-контрактами. Подключив свой EOA к сети zkSync Era внешне не будет никакой разницы, но под капотом это теперь не просто кошелек, а базовый AA. Базовый, потому что из всех преимуществ которые дает абстракция учетной записи с таким аккаунтом вы сможете воспользоваться только подключением Paymaster (изменить флоу оплаты за газ). Но и это уже является большим преимуществом.

EOA в zkSync это базовый AA
EOA в zkSync это базовый AA

Чтобы воспользоваться всеми плюсами абстракции аккаунта - нужно такой аккаунт создать. За это отвечает системный смарт-контракт ContractDeployer (код контракта можно посмотреть тут).

Системный смарт-контракт ContractDeployer в zkSync играет ключевую роль в развертывании смарт-контрактов, включая AA. Он предоставляет несколько методов, таких как createcreate2 для развертывания стандартных смарт-контрактов и createAccountcreate2Account для развертывания AA.

Методы create и create2 хорошо известны разработчикам и отвечают за развертывание обычных смарт-контрактов, метод create2, если грубо описать его функцию, дает возможность получить предсказуемый адрес смарт-контракта до его развертывания (можно даже подобрать красивый). Следуя этой логике для создания AA также сделали два метода (с возможностью передачи соли и без).

Создание аккаунта в zkSync
Создание аккаунта в zkSync

Для развертывания своей реализации AA понадобится:

  • написать смарт-контракт который наследуется от интерфейса IAccount и реализует обязательные методы

  • скомпилировать смарт-контракт используя zksolc

  • использовать zkSync SDKs для настройки, деплоя и интеграции AA в приложение

Можно посмотреть туториалы тут и тут, они позволят понять, как создать простую версию такого аккаунта, поэтому здесь я не буду вдаваться в технические подробности деплоя и настройки, а объясню только суть процесса.

Подсказка: Если вы компилируете контракты в проекта hardhat не забудьте в настройках компилятора zksolc включит флаг isSystem: true.

Возвращаясь к методам контракта ContractDeployer, для создания аккаунта понадобится:

  • соль (необходима только для create2Account)

  • хэш байт-кода смарт-контракта с логикой AA (в zkSync для развертывания всегда используется хэш байт-кода а не сам байт-код)

  • аргументы для конструктора вашего AA (какие-либо значение которые вы хотите задать в момент развертывания, пример: адреса аккаунтов для создания мультисига)

  • версия AA, на данный момент версия одна - Version1 (можно выбрать None, но это значение в основном для адресов, роль которых не удалось определить, с таким аккаунтом не получится ничего сделать)

Процесс создания AA в zkSync
Процесс создания AA в zkSync
    function createAccount(
        bytes32 /** */,
        bytes32 _bytecodeHash, // Хэш байт-кода нашей версии аккаунта
        bytes calldata _input, // Значения для конструктора
        AccountAbstractionVersion _aaVersion // Версия аккаунта
    ) external payable returns (address newAddress);

На выходе мы получаем адрес нашего AA, которым теперь можно совершать транзакции (используя API zkSync).

Как определить инициатора транзакции?

В контексте работы с AA (Abstract Account) в zkSync, важно понимать, как система идентифицирует инициатора транзакции. Этот процесс особенно критичен для интеграции с Externally Owned Accounts (EOA).

Тут все просто, ContractDeployer регистрирует хэш байт-кода каждого развернутого им смарт-контракта в системный контракт AccountCodeStorage системы. Это можно сравнить с записью в маппинг (сопоставление) типа address => bytecodeHash.

AccountCodeStorage - системный смарт-контракт который хранит хэши байт-кода развернутых смарт-контрактов.

Процесс регистрации байткода развернутых AA
Процесс регистрации байткода развернутых AA

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

    /// @dev Checks whether an address is an EOA (i.e. has not code deployed on it)
    /// @param addr The address to check
    function isEOA(addr) -> ret { ... } // bootlader code fragment

Когда инициатор транзакции EOA, для имитации поведения аккаунта Ethereum вызывается смарт-контракт DefaultAccount, если же у вызывающего есть развернутый код и поддерживается версия AA, значит это Smart account (AA).

Определение типа аккаунта
Определение типа аккаунта

Именно поэтому в zkSync EOA тоже считаются смарт-контрактами, за логику его работы всегда отвечает контракт DefaultAccount (код контракта можно посмотреть тут). Главная его задача вести себя как обычный EOA, но при этом иметь возможность оплаты транзакции через Paymaster. При этом версия АА (AccountAbstractionVersion) отражает реализацию как Smart account так и Default account (базовая логика у них одинаковая, есть все обязательные методы).

Таким образом инициатора транзакции всегда можно отнести в одну из категорий:

  • EOA: Прямое управление пользователем

  • Smart account: Расширенные функции управления, реализация AA

  • Smart contract: Автономный код в блокчейне

  • System contract: Специализированные системные контракты со служебной логикой, как правило вызывается только внутри системы

  • AccountAbstractionVersion.None: Все остальные инициаторы транзакций, которые никак не определены в системе.

В зависимости от того кто инициировал транзакцию будет выполняться дальнейшая логика.

Процесс валидации и исполнения транзакций

Давайте рассмотрим, как в zkSync происходит валидация и исполнение транзакций, инициированных AA, при этом учтем, что для EOA процесс аналогичен, за исключением того, что все обязательные методы для EOA уже реализованы и закреплены в контракте DefaultAccount.

Обязательные методы - это validateTransaction и executeTransaction. Для AA мы можем реализовать в них любую логику, но это совсем не означает, что нет никаких ограничений, все таки базовая безопасность должна быть обеспечена.

Процесс валидации транзакции следующий:

  1. Система проверяет, что nonce транзакции не использовался

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

  3. Система проверит что вы пометили nonce, как использованный

  4. Если Paymaster не указан в транзакции производится перевод средств для оплаты контракту bootloader . Если Paymaster указан в транзакции вызывается метод для обработки его логики и оплаты транзакции (подробности чуть позже)

  5. Системой выполняется проверка, что bootloader получил достаточно средств для оплаты газа за транзакцию.

Процесс валидации транзакции
Процесс валидации транзакции

Nonce означает "number used once" (число, используемое однократно). Это число, которое используется в криптографии для обеспечения безопасности и уникальности каждой транзакции.

После этих действий происходит выполнение транзакции, здесь вызывается второй обязательный метод (executeTransaction), порядок такой:

  1. Вызывается executeTransaction, здесь можно обработать вызов системного контракта (например, для деплоя контракта) либо выполнить любой другой вызов.

  2. (Только в случае участия Paymaster в транзакции) Вызывается метод postTransaction контракта Paymaster. Этот шаг обычно следует использовать для возврата средств за неиспользованный газ.

Выполнение транзакции
Выполнение транзакции

Некоторые ограничения

Для защиты системы от DoS-атак этап верификации имеет следующие ограничения:

  • Логика AA может взаимодействовать только со слотами, которые принадлежат аккаунту. Это определение охватывает не только слоты, фактически находящиеся в смарт-контракте аккаунта, но и некоторые поля других смарт-контрактов (например баланс токенов ERC20 или сумма разрешенная для списания)

  • Логика аккаунта не может использовать контекстные переменные (например, block.number)

  • Требуется, чтобы аккаунт увеличивал nonce на 1. Это ограничение необходимо только для сохранения устойчивости к коллизиям хэшей транзакций.

Интеграция Paymaster и безгазовые транзакции

Перед тем как погрузиться в детали функционала Paymaster, давайте разберемся, как в zkSync определяется способ оплаты транзакции.

Все довольно просто, во время формирования транзакции к ней добавляются два поля:

  • paymaster - это значение uint256, если в транзакции участвует Paymaster из этого значения можно вытащить его адрес, в противном случае там будет 0

  • paymasterInput - это массив типа bytes, где в первых 4 байтах указан тип Paymaster, дальше идут дополнительны данные

О каких типах идет речь? Существуют два основных типа Paymaster: ApprovalBased и General.

ApprovalBased Paymaster - это еще одно преимущество нативной реализации AA, сейчас все объясню.

Он принимает три аргумента:

  • Адрес ERC20 токена

  • Минимальное значение allowance, которое нужно установить перед переводом средств

  • Дополнительные данные, которые можно передать при необходимости

      function approvalBased(
        address _token,
        uint256 _minAllowance,
        bytes calldata _innerInput
      ) external;

Что это дает? Это позволяет нам заранее рассчитать объем газа для транзакции и, соответственно, необходимое количество ERC20 токенов для её оплаты. После определения этой суммы мы устанавливаем минимальный allowance для Paymaster, давая системе зелёный свет на вызов approve за нас. Это дает возможность Paymaster'у легко списать нужные токены от имени инициатора транзакции ?, ну разве это не круто?

General Paymaster принимает только один аргумент (input), который позволит передать в него произвольные значения. Этот тип Paymaster может выполнять любую заданную вами логику, включая сценарии, где не требуется предварительное разрешение на списание токенов.

Два вида Paymaster
Два вида Paymaster

Создание Paymaster

Чтобы создать Paymaster, необходимо наследоваться от интерфейса IPaymaster и реализовать ключевые методы, такие как validateAndPayForPaymasterTransaction (обязательный) и postTransaction (опциональный), каждый из которых играет важную роль в логике оплаты транзакций.

Метод validateAndPayForPaymasterTransaction определяет готовность Paymaster оплатить транзакцию. Он принимает три параметра: хэш транзакции, подписанный EOA хэш, и данные транзакции. Если Paymaster согласен с оплатой, метод отправляет оператору нативную валюту, равную или превышающую tx.gasPrice * tx.gasLimit. Также метод возвращает контекст для вызова postTransaction.

    function validateAndPayForPaymasterTransaction(
        bytes32 _txHash, // Хэш транзакции
        bytes32 _suggestedSignedHash, // Хэш транзакции, подписанной EOA
        Transaction calldata _transaction // Данные транзакции
    ) external payable returns (bytes4 magic, bytes memory context);

Метод postTransaction является необязательным и активируется после завершения транзакции. В отличие от ERC-4337, его вызов не гарантирован: например, при неудачной транзакции из-за нехватки газа, метод не вызывается.

postTransaction принимает четыре аргумента: контекст, созданный validateAndPayForPaymasterTransaction, информацию о транзакции, индикатор её успешности и максимальный объем газа, подлежащий возврату Paymaster'у.

    function postTransaction(
        bytes calldata _context, // Контекст, который передается из метода validateAndPayForPaymasterTransaction
        Transaction calldata _transaction, // Данные транзакции
        bytes32 _txHash, // Хэш транзакции
        bytes32 _suggestedSignedHash, Хэш транзакции, подписанной EOA
        ExecutionResult _txResult, // Результат выполнения транзакции
        uint256 _maxRefundedGas // Количество газа, которое может быть возвращено контракту Paymaster
    ) external payable;

Примечание: Данные методы вызываются только системным контрактом bootloader.

Примеры написания простых Paymasters можно посмотреть тут и тут.

Для деплоя Paymaster не требуется взаимодействие с системными контрактами; он разворачивается как стандартный смарт-контракт. Параметры Paymaster подготавливаются для включения в транзакцию через API zkSync.

Заключение

В заключении стоит отметить, что нативная поддержка Abstract Accounts в zkSync представляет собой более простое и интуитивно понятное архитектурное решение по сравнению с ERC-4337. Создание и интеграция AA в такой парадигме несколько проще, также упрощается тестирование – всё, что требуется, это среда разработки zkSync, без необходимости в дополнительных сервисах, таких как bundler'ы.

Важно также и то, что Paymaster'ы могут быть легко интегрированы с обычными EOA, причем с возможностью избежать лишней транзакции для одобрения списания ERC20-токенов.

Стоит отметить, что такие возможности, как оплата транзакций в ERC20-токенах или multicall (объединение нескольких транзакций в одну) это только верхушка айсберга, AA может обладать очень полезным и разнообразным набором функций. Я думаю мы увидим еще много различных кейсов его использования.

Спасибо, что дочитали статью до конца, надеюсь, она была для вас полезной :)

Ссылки

Теги:
Хабы:
Всего голосов 5: ↑5 и ↓0+5
Комментарии2

Публикации

Информация

Сайт
metalamp.ru
Дата регистрации
Численность
51–100 человек
Местоположение
Россия