Interoperable Delegated Accounts
Abstract
This proposal outlines the interfaces to make delegated EOAs interoperable after the merge of EIP-7702. With EIP-7702, EOAs will be able to enable execution abstraction, which leads to a more feature-rich account, including gas sponsorship, batch execution, and more.
However, there is a need to help facilitate storage management for redelegation, as invalid management of storage may incur storage collisions that can lead to unexpected behavior of accounts (e.g., account getting locked, security vulnerabilities, etc)
The interface InteroperableDelegatedAccount
suggests the interfaces for delegated EOAs to be interoperable and facilitate a better environment for redelegation.
Motivation
After the merge of EIP-7702, it is expected that a considerable number of EOA wallets will migrate from pure EOA accounts to delegated EOA accounts.
This is to enable a more appealing wallet UX, including a 1-step swap, automated subscription, gas sponsorship, and more.
However, considering the fact that delegated EOAs will utilize its own storage bound to their Smart Account implementation, the storage management is essential to foster migration between wallets to better ensure sovereignty of users to freely migrate their wallet app whenever they want.
EOA (Externally Owned Account) is currently comprised of cryptographic key pair that is mostly managed in the form of mnemonic phrase.
This simplicity provided frictionless interoperability between wallets that gave users the freedom to freely migrate between different wallet applications.
However, after the merge of EIP-7702, each EOA will be given the ability to delegate itself to a smart account which will impact migration as storage remains in the continuous context while EOA can be delegated to diverse smart accounts if the user migrates their wallet.
Account Abstraction Wallets, given the wallet-specific validation and execution logic, also have the interoperability issue to be considered but its importance in EOA is much more significant as EOA users are already familiar with wallet migration and its a common action to migrate wallets.
This spec provides a standard approach for fetching the storage base used in the delegated account together with an optional mechanism to clean up the storage.
Specification
The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
interface IInteroperableDelegatedAccount {
/*
* @dev Provides the namespace of the account.
* namespace of accounts can possibly include, account version, account name, wallet vendor name, etc
* @notice this standard does not standardize the namespace format
* e.g., "v0.1.2.7702Account.WalletProjectA"
*/
function accountId() external view returns (string);
/*
* @dev Externally shares the storage bases that has been used throughout the account.
* Majority of 7702 accounts will have their distinctive storage base to reduce the chance of storage collision.
* This allows the external entities to know what the storage base is of the account.
* Wallets willing to redelegate already-delegated accounts should call accountStorageBase() to check if it confirms with the account it plans to redelegate.
*
* The bytes32 array should be stored at the storage slot: keccak(keccak('InteroperableDelegatedAccount.ERC.Storage')-1) & ~0xff
* This is an append-only array so newly redelegated accounts should not overwrite the storage at this slot, but just append their base to the array.
* This append operation should be done during the initialization of the account.
*/
function accountStorageBases() external view returns (bytes32[]);
}
interface IRedelegableDelegatedAccount {
/*
* @dev Function called before redelegation.
* This function should prepare the account for a delegation to a different implementation.
* This function could be triggered by the new wallet that wants to redelegate an already delegated EOA.
* It should uninitialize storages if needed and execute wallet-specific logic to prepare for redelegation.
* msg.sender should be the owner of the account.
*/
function onRedelegation() external returns (bool);
}
Accounts MUST implement the IInteroperableDelegatedAccount
to be compliant with the standard.
Accounts MAY implement the IRedelegableDelegatedAccount
.
accountId()
This function is a view function to fetch the account information.
A use case for this could be wallet showing the redelegation process e.g., Are you willing to migrate your account “Wallet A” → “Wallet B”
.
Wallet A information could be extracted from accountId()
.
accountStorageBases()
This function returns the list of base storage slots of that account has used.
EIP-7702 Accounts do plan to use a custom non-zero storage slot to avoid storage collision as much as possible, however, there hasn’t been a standardized approach on how to fetch them.
This function provides a standardized approach for wallets and other applications to check the base storage slots of an account, and verify if the base storage slots are far enough from the newly to-be-redelegated account’s base storage slot.
Note that there could be some exceptions for mappings, etc depending on how account manages storage.
This provides Wallets and applications a standard approach to fetch the base storage slots for verification rather than just relying on the probability of hash.
If the account uses external storage, it should return ERC Number prefixed slot with the external storage contract address concatenated.
<ERC-Number><ERC-Number><ERC-Number>..N...<Storage-Contract-Address>
For example, if the storage contract address is 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
the returned value should be
0x777977797779777977797779AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
When the account is delegated(e.g., during the account initialization stage), the account implementation should append the base storage slot of the account to the following slot position: keccak(keccak('InteroperableDelegatedAccount.ERC.Storage')-1) & ~0xff
.
The storage variable would be using the bytes32[]
type and is an append-only variable which newly delegated accounts append its storage slot hash. The account may choose to not append its storage base to an array if there is an identical entry that already exist.
In case the account identifies that there are colliding storage bases, the account can perform further storage verification either off-chain or on-chain and decide whether the delegation would happen.
Wallet may revert the delegation if the colliding storage includes a suspicious storage values that may target the user(e.g., shadow signer, etc).
onRedelegation()
This function is to prepare for the redelegation to a new account.
When this function is called, the existing delegated account should perform actions to not limit or impact the user when the user redelegates to a new account as much as possible.
For example, the account could uninitialize the storage variables as much as it can to provide clean storage for new wallet.
This standard, however, does not explicitly state the behavior to be done during this function call as wallet implementations have very distinctive architecture and details.
The standard expect the wallet implementation to revert it back to a clean storage state **as much as possible with this function.**
onRedelegation()
should validate if the caller is indeed the authorized user by checking the msg.sender
value.
This could also be done through a “self-call” if a custom validation scheme is implemented or at the wallet’s discretion as a side case.
Rationale
Storage base checks
This standard is designed with the need of wallets to validate the storage of the EOA, even if some may consider that the probability of hash is already big that the account doesn’t have to check, assuming that each wallet uses a different storage base slot.
In fact, this standard thinks exactly the opposite. It is worth scanning the storage, or at least the storage that the delegated account will use, which the wallet wants to delegate to. E.g., Just like developers validating the storage of Facets in Diamond (ERC-2535) to prevent storage collision and not just relying on hash probability.
In line with this, the accountStorageBases()
was designed to not only return the storage base of the current wallet implementation, but return the full historical storage slots that the account has used.
This could provide valuable information for the storage scanning of the EOA before delegation.
Optional onRedelegation()
onRedelegation()
was designed to be optional to lower the barrier of being compliant with this standard. Also, there could be accounts that does not functionally require a hook-logic to be in place before redelegation, or accounts that does not suite with the design principle of the onRedelegation()
e.g., excessive use of mapping or data types that’s hard to uninitialize.
It is worth noting that, onRedelegation()
does not obligate the account to completely whipe out it’s storage. It’s more of a best effort function to leave the cleanest stage for the future use of the EOA in a new wallet. Or to execute a function to prepare for redelegation.
Backwards Compatibility
Existing smart accounts that was built prior to the EIP-7702 discussion will need changes to support this standard.
This standard was specifically for Smart Accounts for EOA, but this could be applied further to diverse cases and architecture.
Security Considerations
- Calling
onRedelegation()
should include security mechanism to properly authentication the owner. - This standard enforces the accounts to
- provide proper Base Storage Slot when
accountStorageBase()
is called - perform proper actions to make account storage to a clean state as much as possible (e.g., uninitialization of storage variables, etc) IF the account supports
onRedelegation()
However, whether the account follows the above three enforcements with behavioral actions is dependant of the account.
If needed, accounts may check the authenticity of the information through off-chain approaches.
- provide proper Base Storage Slot when
-
Wiping out the storage completely may not be an adaptable action depending on how account implementation manages storage.
This standard recommends the account to completely wipe out its storage, however, exceptions apply if the account is incapable of doing that.
In the case when the account cannot completely wipe out its storage, the standard expect the account to perform the best degree of action it can do to support the redelegation operation for the user.
Also, the account should make sure the initializer cannot be triggered by an arbitrary entity after
onRedelegation()
is called. -
onRedelegation()
should not reset the replay protection considering that it could incur a vulnerability(e.g., signature reply attack). - It is worth noting that this standard is an ERC, which means that even if the ERC enforces it, the actual implementation may not be compliant with it. e.g., accounts pretending to support this standard which is not, in fact. So it is recommend to validate if the account is a know implementation that is secure and compliant with the standard.
Copyright
Copyright and related rights waived via CC0.