
Превратить обычный кошелек в умную учетную запись с EIP 7702
28 июля 2025 г.В последние годы Web3 добился огромных успехов, но усыновление UX и мейнстрим остаются основными препятствиями.Согласно опросам, управление ключами является крупнейшим блокатором: 46,8% пользователей называют «проблемы безопасности» в качестве их главного сдерживающего фактора, и 80% говорят, что им нужна более сильная уверенность в защите счетов перед тем, как совершать сделку.
К сожалению, этот страх действителен.Миллиарды в криптобыли потеряны из -за неуместных ключей, необработанной фразы и кражи личного ключа. Для команд, создающих продукты Web3, это создает болезненный компромисс: интуитивно понятный, бесшовный UX часто означает компромисс в отношении безопасности или самостоятельной работы.
Как говорится - выберите только два: UX, безопасность или суверенитет.
Сообщество работает, чтобы нарушить этот треугольник. Среди самых перспективных направлений:
- EIP-7702-Давайте временно делегируем выполнение в смарт -контракт без изменения адреса или истории пользователя.
- EIP-4337-Стандартизируют счетные учетные записи с интеллектуальными контрактами и приносят необходимую инфраструктуру (например, бундлеры и плательщики), чтобы они работали, как входы в систему Web2.
- Учетные записи MPC- Используйте безопасное совместное использование ключей, чтобы обеспечить социальное вход, потоки восстановления и более высокую устойчивость, не отказываясь от контроля.
Я являюсь наставником в международных хакатонах Web3 и регулярно публикую руководства и инструменты с открытым исходным кодом, чтобы помочь командам быстрее принять современные архитектуры UX. Эти подходы испытываются в борьбе с реальными продуктами и адаптируются по борьбе с блокчейнами. В настоящее время строительство в уникальной сети - инфраструктурном проекте на Polkadot, ориентированный на NFT и инструменты разработчика.
В этой статье я сломаю, как подать заявкуEIP-7702На практике-в качестве примера использование минимального умного кошелька с пошаговой делегированием, инициализацией и безопасной миграцией без потери состояния.
Что такое EIP-7702
Сегодняшние пользователи не будут терпеть плохой UX. Они ожидают плавного опыта, который может быть достигнут за счет абстракции, включая:
- Транзакция партии
- Спонсорство газа
- Повышенная безопасность (социальное восстановление, ограничения расходов, ограниченная делегация)
Значительный прогресс был достигнут с интеллектуальными контрактными кошельками. Тем не менее, экосистема несла унаследованную бремя учетных записей EOA, предотвращая использование приложений от инициативы по абстракции счетов, так как существует фундаментальная разница между тем, что можно сделать с помощью интеллектуальных контрактных кошельков и EOA.
Затем Pectra Hard Fork представила EIP-7702.
EIP-7702 является ключевым шагом в дорожной карте абстракции учетной записи Ethereum. Он предоставляет существующие извне принадлежащие учетные записи (EOAS) доступ к возможностям смарт -контракта - не требуя, чтобы пользователи переключались на новый адрес или отказались от истории своих учетных записей.
С EIP-7702 EOA может временно делегировать выполнение в код смарт-контракта при сохранении своей первоначальной структуры. Проще говоря, он позволяет обновлять ваш обычный кошелек с помощью интеллектуальных функций учетной записи, таких как пакетирование транзакций, пользовательская проверка и спонсорство газа - все это при этом сохраняя ваш существующий адрес.
Что покрывает этот гид
В этом руководстве по разработчике я прохожу через как применять EIP-7702, постепенно создавая интеллектуальный кошелек на основе делегирования-начиная с минимального примера и постепенно добавляя проверки владения, партийное выполнение и более безопасные шаблоны хранения.
Мы рассмотрим:
- Как транзакции типа 4 и
authorization_list
работа - Как обрабатывать инициализацию, безопасность хранения и владение
- Как перевести транзакции и улучшить ux
- Где края-и что EIP-7702 не решает
Весь код написан вПрочностьи протестирован сЛитейный завод.Вы также найдете запускаемые примеры и полные тесты наGitHubПолем
Давайте начнем.
Как EIP-7702 работает технически
EIP-7702 вводит механизм делегирования, который позволяет EOA временно указывать на код смарт-контракта, сохраняя при этом свою первоначальную структуру и историю. Это включено новым типом транзакции (0x04
) это включаетauthorization_list.
chain_id
nonce
max_priority_fee_per_gas
max_fee_per_gas
gas_limit
destination
value
data
access_list
authorization_list
signature_y_parity
signature_r
signature_s
Аauthorization_list
Собственность является наиболее важной частью нового типа транзакции.
Список авторизации
Аauthorization_list
Содержит подписи от владельцев EOA, разрешающих свои счета для делегирования выполнения конкретным интеллектуальным контрактам. Это список кортежей, которые хранят адрес контракта, на который подписывается, подписал выполнение выполнения в контексте их EOA.
authorization_list = [[chain_id, address, nonce, y_parity, r, s], ...]
// ^^^^^^^
// This is the contract address that the EOA (the signer of this tuple) will delegate to
Аauthorization_list
специально разработан для содержания фирменных компонентов (y_parity
Вr
Вs
), включив спонсируемые транзакции, где делегатору не нужно платить за газ. Установленные транзакции кода могут быть выполнены кошельками или приложениями от имени пользователей.
Список авторизации разработан как массив, позволяющий приложениям мигрировать несколько учетных записей в одной транзакции. Тем не менее, это не означает, что вы можете установить различные делегаторы для одного EOA - разрешена только одна цель делегирования на счете
Транзакция SET CODE проходит конкретный процесс проверки, и в случае успеха она устанавливает код для EOA. Код, установленная на учетной записи EOA, называется назначением делегации и имеет определенный формат:
// EIP-7702 delegation designator structure
bytes memory delegationCode = abi.encodePacked(
hex"ef0100",
address(targetContract)
);
// Total: 23 bytes
Где:
0xef
-EIP-3541 Зарезервированный байт, указывающий на специальный код протокола (не исполняемый байт-код)01
-EIP-7702 Идентификатор функции в рамках зарезервированного пространства EIP-354100
-Версия формата делегирования EIP-7702 (в настоящее время версия 0)address (targetContract)
- Последние 20 байтов содержат полный адрес Ethereum интеллектуального контракта, который выполняет все вызовы функций, сделанные для этого EOA
Теперь ваша учетная запись указывает на умный контракт и может выполнить его логику.
Проблемы инициализации
Это поднимает важную деталь реализации. Когда мы делегируем наш EOA, скажем, собственный кошелек, как этот кошелек знает, какой адрес уполномочен выполнять транзакцию? Когда развернут традиционный кошелек, его конструктор работает и устанавливает начальное состояние, настраивая владельцев, требования к подписи и другие важные параметры. Но когда вы делегируете существующий контракт, хранилище вашего EOA начинается полностью пустым.
Это связано с тем, что EIP-7702 делегирует выполнение кода, а не хранилище. Ваш EOA поддерживает собственное пространство для хранения, отделенное от контракта на реализацию. Это означает, что контракт, который вы делегируете, должен предоставить механизм инициализации для установки требуемого состояния в пространстве хранения вашего EOA.
Делегирование в кошелек с интеллектуальным контрактом
Примеры полного кода доступны на GitHub:github.com/maksandre/try-eip-7702
Давайте начнем с внедрения упрощенного интеллектуального кошелька, чтобы понять основную механику, а затем улучшить его шаг за шагом.
Для этого руководства мы будем использовать Foundry для создания и проверки реализации.
Наша первая версия имеет единый методexecute
транзакция:
// SmartWallet.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract NotReallySmartWallet {
function execute(address to, uint256 value, bytes calldata data) external payable {
(bool success,) = to.call{value: value}(data);
require(success, "Transaction reverted");
}
fallback() external payable {}
receive() external payable {}
}
Далее мы напишем тест, чтобы понять, как работает делегирование с помощью ForgeчиткодыПолем Мы будем использоватьvm.signAndAttachDelegation
, который высмеиваетSET_CODE_TX_TYPE
для EOA и умного контракта.
Шаги настройки:
- Создайте два адреса: Алиса (предварительно) и Боб (пусто)
- Развернуть наш контракт на тестовый кошелек
- Убедитесь, что у EOA у Алисы нет кода
contract NotReallySmartWalletTest is Test {
address public ALICE_ADDRESS;
uint256 public ALICE_PRIVATE_KEY;
address public BOB_ADDRESS;
// Test parameters
uint256 constant INITIAL_ALICE_BALANCE = 10 ether;
uint256 constant TRANSFER_AMOUNT = 1 ether;
// The contract that Alice will delegate execution to
NotReallySmartWallet public delegationTarget;
function setUp() public {
// Generate addresses and keys dynamically
(ALICE_ADDRESS, ALICE_PRIVATE_KEY) = makeAddrAndKey("alice");
BOB_ADDRESS = makeAddr("bob");
// Fund Alice's account
vm.deal(ALICE_ADDRESS, INITIAL_ALICE_BALANCE);
// Deploy the delegation contract
delegationTarget = new NotReallySmartWallet();
}
}
В нашем тесте мы начнем с делегирования учетной записи Алисы в предварительную`SmartWallet`
договор.
Затем мы проверяем, что аккаунт Алисы теперь имеет код обозначения делегирования. Помните, у него есть специальный формат, начиная с`ef0100`
:
assertEq(
ALICE_ADDRESS.code,
abi.encodePacked(hex"ef0100", address(delegationTarget))
);
Это важно: EOAS больше нельзя идентифицировать пустым кодом. Вместо этого проверьте, начинается ли код с0xef0100
-Это делегированный EOA под EIP-7702.
Теперь мы можем назвать EOA Алисы как любой контракт:
NotReallySmartWallet(ALICE_ADDRESS).execute(
BOB_ADDRESS,
TRANSFER_AMOUNT,
""
);
Проверьте баланс счетов после транзакции, чтобы убедиться, что она преуспела:
// Verify final balances
assertEq(BOB_ADDRESS.balance, TRANSFER_AMOUNT);
assertEq(
ALICE_ADDRESS.balance,
INITIAL_ALICE_BALANCE - TRANSFER_AMOUNT
);
Создание UX, безопасность и контроль
На этом этапе наш контракт работает - но без контроля доступа или реальной безопасности. Давайте исправим это.
1. Отсутствие контроля доступа
У нас нет механизма авторизации - каждый может позвонить в учетную запись. Чтобы продемонстрировать это, вы можете просто изменить происхождение транзакций с Алисы на Боба, и тест все равно пройдет:
vm.prank(/* ALICE_ADDRESS -> */ BOB_ADDRESS);
NotReallySmartWallet(ALICE_ADDRESS).execute(
BOB_ADDRESS,
TRANSFER_AMOUNT,
""
);
Давайте убедимся, что подпись транзакции на самом деле является делегирующей учетной записью EOA. Если вы хотите ограничить доступ только к этому ключу, вы можете проверить этоmsg.sender == address(this)
Полем
Но что, если вы хотите более гибкий контроль доступа - например, Multisig или Passkeys? АSET_CODE_TX_TYPE
Не позволяет вам развернуть новый контракт с кодом инициации. Вы можете указать только на существующий. Это означает, что договор уже должен быть инициализировандоЭто делегировано.
Самый простой - но неверный - подход - это установить хранилище в контракте на реализацию.
Проблема в том, что делегаты EIP-7702выполнение кода,не хранениеПолем Как и DelegateCall, исполнение происходит в контексте EOA, а не в договоре, к которому вы делегируете.
Возьмите OpenzeppelinOwnable
контракт в качестве примера. Если вы инициализируете этододелегация,onlyOwner
CHECK будет искать владельца в хранилище EOA - не в собственном хранилище контракта - и он не найдет его. Значение, которое вы установили в самом контракте, будет проигнорировано.
Чтобы решить это, вам нужно переместитьowner
инициализация в отдельную функцию и назовите еепослеДелегация:
// Remove constructor initialization
// constructor() {
// owner = msg.sender;
// }
function initialize() external {
require(owner == address(0), "Already initialized");
owner = msg.sender;
}
Теперь, вызываяinitialize
Функция, владелец будет храниться в хранилище EOA и, в результате, правильно распознается. Тем не менее, мы представили еще одну уязвимость.
2
Если делегирование и инициализация являются двумя отдельными транзакциями, кто -то может быстрее инициализировать кошелек и получить доступ к активам учетной записи. Есть два способа предотвратить это:
- Делегируйте и инициализируйте в той же транзакции, включив вызов инициализации в ту же транзакцию, которая выполняет делегирование.
- Используйте
ecrecover
предварительно компилировать, чтобы убедиться, что инициализация выполняется ключом EOA.
Давайте начнем с первого варианта. Вы можете использоватьto
иdata
Поля транзакции EIP-7702, чтобы включить вызов инициализации. Предположим, что наш контракт на реализацию имеет следующую функцию:
function initialize(address _owner) external {
require(owner == address(0), "Already initialized");
owner = _owner;
}
Затем вы можете вызвать эту функцию в той же транзакции, сразу после установки делегирования. Вот как вы можете сделать это с помощью Viem:
...
const authorization = await walletClient.signAuthorization({
contractAddress: implementationAddress,
account: delegator, // <---- 1. the `delegator` account signs the authorization to `implementationAddress`
executor: executor,
});
const eip7702tx = await walletClient.sendTransaction({
account: executor,
// 2. During the execution of the eip7702 transaction, the `executor` calls `delegator.address`
// this transaction will be executed at the end of the transaction. At this point the EOA already
// has code set
to: delegator.address,
data: encodeFunctionData({
abi: artifact.abi,
functionName: "initialize", // <---- the `executor` calls the `initialize` function
args: [delegator.address],
}),
type: "eip7702",
authorizationList: [authorization],
});
Этот подход безопасен, но он поставляется с ограничением: он работает только для одной учетной записи. Вы не можете повторно использовать ту же трюк при переносе нескольких EOA в одной транзакции EIP-7702. В этом случае вам может потребоваться использоватьecrecover
предварительный компилятор для инициализации.
Это большое улучшение - но есть еще одна проблема для решения.
3. Столкновения хранения
Когда учетная запись переходит от одного контракта на другой, который по -разному использует одни и те же слоты хранения, это может вызвать непредсказуемое поведение или даже уязвимости безопасности.
Чтобы проиллюстрировать проблему, давайте создадим два простых контракта, которые используют слот 0, но для разных типов данных:
contract A {
address public owner; // slot 0
// ...
}
contract B {
uint256 public balance; // slot 0
// ...
}
После перераспределения из контракта A на контракт B, предыдущийowner
Значение будет интерпретироваться как огромный баланс. Адрес Алисы (20 байтов) будет прочитан какuint256
в результате нелепо большого количества.
Чтобы избежать таких столкновений, мы можем использоватьEIP-7201, который вводит сцены с именами хранения. Этот стандарт определяет метод для назначения псевдолудочных слотов хранения, значительно снижая риск перекрытия.
Чтобы применить его, мы сначала оберните наши данные в структуру и вычислим уникальный слот, используя формулу EIP-7201. Это гарантирует, что наш контракт использует выделенное место для хранения, которое не будет противоречить другим.
contract ContractA_7201 {
// @custom:storage-location erc7201:example.contractA
struct ContractAStorage {
address owner;
uint256 nonce;
}
// keccak256(abi.encode(uint256(keccak256("example.contractA")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ContractAStorageLocation =
0xd01525294f88a57baa3c94c84cf5cf8d70d334377609d6aabd7ec7c9ce460d00;
Функция доступа возвращает ссылку на наше место, которое наняло на имена, хранится. Он использует сборку, чтобы указать непосредственно на наше расчетное местоположение слота.
function _getContractAStorage() private pure returns (ContractAStorage storage $) {
assembly {
$.slot := ContractAStorageLocation
}
}
Обе функции сначала получают ссылку на хранилище с именами, а затем выполните операции чтения/записи. Аinitialize
функция устанавливает владельца один раз иowner
Функция извлекает это. Все взаимодействия хранилища проходят через наше место, безопасное для столкновения.
function initialize() external {
ContractAStorage storage $ = _getContractAStorage();
require($.owner == address(0), "Already initialized");
$.owner = msg.sender;
}
function owner() public view returns (address) {
ContractAStorage storage $ = _getContractAStorage();
return $.owner;
}
Большой! Мы настроили первую версию управления доступом, которая на самом деле работает. Теперь давайте сосредоточимся на улучшении удобства использования.
4. Лучше UX: пакетные транзакции
Конструкция Ethereum часто требует нескольких отдельных транзакций для связанных действий, создания трения и увеличения затрат на газ. Например, обмена токенами обычно включают в себя одобрение расходов сначала, а затем выполнение свопа - две подписи, два платежа на газ. Одним из ключевых преимуществ Smart Contract Swells является возможность превратить несколько операций в одну транзакцию. Давайте добавим эту возможность:
struct Call {
address to;
uint256 value;
bytes data;
}
function executeBatch(Call[] calldata calls) external payable onlyOwner {
for (uint256 i = 0; i < calls.length; i++) {
(bool success,) = calls[i].to.call{value: calls[i].value}(calls[i].data);
require(success, "Batch transaction reverted");
}
}
Это позволяет пользователям выполнять несколько операций, например, одобрение токена, а затем заменять его на одну транзакцию, снизить затраты на газ и улучшить пользовательский опыт. Нет больше бесконечного подписания транзакций!
Вы также можете представить себе дополнительные улучшения UX, такие как ключи сеансов с ограниченным доступом к средствам кошелька или пределами расходов.
Теперь давайте обратим внимание на безопасность.
5. Улучшения безопасности
Огромное количество средств навсегда потеряно из -за потерянных частных ключей. Неспособность их восстановить является серьезной проблемой. К счастью, с программируемостью мы можем решить эту проблему. Используя интеллектуальные контракты, вы можете создать социальное восстановление, предоставляя доверенным друзьям возможность передавать право собственности на свой аккаунт в новый EOA. Чтобы предотвратить злоупотребление, вы можете добавить временные рамки, которые дают вам время, чтобы отменить вредоносную попытку восстановления.
Другие возможные улучшения безопасности включают в себя установку пределов передачи. Таким образом, даже если вы случайно подпишите злонамеренную транзакцию, злоумышленник может истощать только небольшое количество.
У вас много власти с программируемостью. Но, к сожалению, EIP-7702 не решает ни одной важной проблемы.
Частный ключ остается риском безопасности
Если кто -то получает доступ к личному ключу вашего EOA, у вас проблемы. Ни один метод восстановления не поможет вам вернуть ваши средства или ограничить ваши потери. Этот личный ключ - это как основной ключ ко всему. Неважно, какие функции умной учетной записи вы настроили; Частный ключ может переопределить их все.
Единственный способ по -настоящему защитить себя от этого сценария - это использовать чистые смарт -учетные записи, которые не полагаются на единый личный ключ.
Делегация очистка
Пользователи могут удалить свой прикрепленный код, разрешив делегирование на нулевой адрес. После этого код, стоящий за EOA, становится пустым - это больше не0xef01000000
.... однако, обратите внимание, что это делаетнетСбросить или протрите хранилище, связанное с EOA.
Вот как это сделать, используя VIEM:
const cleanupAuthorization = await walletClient.signAuthorization({
contractAddress: zeroAddress,
account: delegator,
executor: "self",
});
const cleanupTx = await walletClient.sendTransaction({
account: delegator,
type: "eip7702",
authorizationList: [cleanupAuthorization],
});
Недостатки
В то время как EIP-7702 приносит значимые улучшения, остается несколько ключевых ограничений.
Спонсорство газа требует EIP-4337
EIP-7702 обеспечивает функциональность смарт-контракта, но действительно без газа транзакции все еще зависят от инфраструктуры EIP-4337. Список авторизации поддерживает спонсируемые транзакции, но вам все равно понадобятся бундлеры и платежные мастера для полного опыта без газа.
Сложность и векторы атаки
Делегация вводит дополнительную сложность, которая может привести к неожиданному поведению или новым поверхностям атаки. Разработчики должны тщательно управлять макетом хранения, логикой инициализации и возможными конфликтами контракта при работе с EIP-7702.
Опыт разработчика
Разделение выполнения кода (делегировано) и хранилища (локальное для EOA) представляет новую ментальную модель. Непонимание этой модели может легко привести к ошибкам или непреднамеренному побочным эффектам.
Несмотря на эти проблемы, EIP-7702 отмечает серьезный шаг к тому, чтобы Ethereum более интуитивно понял и гибкий, сохраняя при этом безопасность и децентрализацию, которые определяют платформу. Это помогает преодолеть разрыв между EOA и абстракцией полной учетной записи, обеспечивая постепенный путь, который сохраняет пользовательский контроль и историю учетной записи.
Это основополагающий шаг к абстракции нативного аккаунта. Будущие EIPS, скорее всего, нарастают на нем, чтобы предложить более комплексные решения, потенциально объединяя EOA и интеллектуальные контракты в единую, бесшовную модель счета.
Оригинал