I apologize, I misjudged the concept of outdated when I assumed that once most wallets had completed the ECDSA conversion, they would no longer write to them, the reality is that they will still use this contract to read the account status.
Using system contracts creates at least 8 extra bytes of state (uint64), whereas setting a prohibition indicator would be less than. The network encourages reducing state creation, and we can achieve that.
We could implement a similar contract, but not a system contract, and allow users to delegate authority to it. Then we initiate the cancellation process and then completely cancel it after a certain period of time. This process is done manually and requires a opcode as mentioned above to complete, however, the current infrastructure is capable of absorbing this limitation.
A system contract is fundamentally the same as a normal contract in terms of state storage. In EVM, whether a contract is designated as a âsystem contractâ (like those in EIP-4788 or EIP-7002) or a user-deployed ânormal contractâ, they are both bytecode and utilize standard 32-byte storage slots. Storing an 8-byte uint64 (such as the finalizationTimestamp for the 7-day delay) consumes the same amount of state regardless of which contract holds it.
Additionally, alternative methods and the exact justification for choosing a system contract are thoroughly illustrated in the Rationale section of the update PR: Update EIP-7851: Move to Draft by colinlyguo ¡ Pull Request #11316 ¡ ethereum/EIPs ¡ GitHub . Trade-offs are explained there: adding a new account-state field would require RLP encoding and p2p protocol changes, and a precompile lacks a natural storage mechanism. A system contract is the cleanest fit to consolidate deactivation, cancellation, and status queries. There are also other hybrid methods that I thought of, and they will eventually encounter similar issues regarding implementing complexity, overhead introduced, etc.
The analysis in the last paragraph of your last reply seems mostly high-level and directional, and itâs already aligned with the current design. Opcode can be a design choice, as is system contract, precompile, and there are plenty of details about pros and cons, if itâs feasible and doable, etc. So to make the discussion go deeper and align with the understanding of both parties, could you please provide specific technical details? And also itâs nice to check the update PR.
Thank you, your solution is not contradictory, but what is a problem with using an authorization indicator like 0xef0101 to identify a cancelled private key eoa. Identifying it doesnât significantly increase client workload, while reducing one state access step instead of checking the system contract and further reducing 32 bytes of state needed for timestamp writing, thus reducing user costs. No new state fields or precompiled contracts were introduced; it was all just a one-byte change to the authorization indicator.
If this update is intended to allow a delay period, we could also have an EOA use 7702 and set parameters on it. Once the valid period has elapsed, we can call the update function via code and remove the status used for marking to receive a refund.
I also acknowledge that the solution uses more complex in client implementation, but it covers more use cases for both EOA private key cancellation and contracts that want minimal code deployment. These two could be separated into two different solutions, but I found a unified solution for them and suggest that it should be done this way for overall optimization, and it also reduces some cost for end users during use.
The ideas regarding delegation cancellation and minimum code for contracts have already been proposed, therefore, I will not open any new PR, but will only provide minor technical details to improve them.
The execution flow of the code I have presented here, I also want to know the potential limitations of this design
First a small clarification: reading the account status does not need to âinteract withâ (i.e., execute) the system contract, because the per-account status storage location is predictable. Concretely, if the status is stored in a mapping(address => ...) at slot 0, then the storage key for a given account is:
keccak256(abi.encode(account, uint256(0)))
So for client-side read queries in various validation paths, it only needs to perform a storage read at that deterministic slot (no contract execution).
With that out of the way, I provide a code-based design summary (brainstormed before and thanks for the chance to post during the discussion) below to make it easier for the community to review and discuss. Also, proposing and iterating on an update PR is not a blocker for me. I can adjust the spec/implementation quickly once thereâs alignment.
Design Prototype
Without Cancellation (simplest model)
If we decide that a cancellation/finalization waiting period is not needed, we can encode an immediately-finalized deactivation directly in code (i.e., no timestamp at all). Two simple encodings:
- Prefix-based (dedicated âfinalized deactivatedâ prefix):
e.g.,0xef0101 || address(or0xef01fe || addressif we want to treat it as a âban-styleâ prefix and keep smaller prefixes available for other extensions).
Semantics: the account is deactivated/frozen immediately and the delegated code becomes non-updatable as soon as this encoding is set. - Suffix-flag-based (keep
0xef0100, append a single byte):
0xef0100 || address || 0x00
Semantics: the trailing0x00indicates an immediately-finalized deactivation (again: no waiting period; code becomes non-updatable immediately).
This method goes back to the original draft of this EIP by removing reactivation.
With Cancellation
Code Encoding
A straightforward semantics:
- Keep the existing prefix (no new encoding required):
0xef0100 || address. - When a user initiates key deactivation, append a
uint64 finalization_timestampto the end of the code:
0xef0100 || address || finalization_timestamp
The presence offinalization_timestampsimply means âa deactivation has been scheduled (with an activation time)â.
Note that choices about âkeeping 0xef0100â or âusing a new prefixâ also have trade-offs related to implementation and integration validation logic (e.g., backward compatibility). I would like to hear voices and opinions from devs.
Cancel Period / Precompile Interaction
For interaction, both a new precompile and an opcode can do the job. Iâd lean toward a precompile because both an EOA and a smart contract wallet can directly interact with it; with a new opcode, an EOA would need a âproxyâ contract to access it, adding another risk layer. To keep the authorization boundary hard and the implementation simple, the precompile acts only on msg.sender (no target parameter).
The precompile can use the first byte/bit of calldata as a function selector, e.g.:
deactivate(): setmsg.sendercode to0xef0100 || address || finalization_timestamp,finalization_timestampis defined asblock.timestamp + finalization_waiting_periodat the timedeactivate()is called.cancel_deactivate(): allowed only whileblock.timestamp < finalization_timestamp(returns to0xef0100 || address).
Rule: cancellation is allowed at any time before finalization_timestamp; once block.timestamp >= finalization_timestamp, the account is considered finalized (no further changes via this path). Reauthorization during the cancellation period is equivalent to canceling the pending finalization and overwriting the code with the newly authorized delegated code; after block.timestamp >= finalization_timestamp, the code becomes non-updatable (i.e., reauthorization is no longer possible).
Design Choices about State Pruning
Choice 1: No Consensus-Level Automatic Cleanup
The simplest approach would be: do not add any consensus-level âautomatic cleanupâ. Even after the timestamp has passed, we donât rewrite the code into 0xef0101 ... or remove fields; we just specify in the rules that once block.timestamp >= timestamp, the account is in its final deactivated/frozen semantics. This keeps client implementation much simpler and avoids introducing any global expiry data structure.
Choice 2: Eagerly Clean-Up Timestamps
Clients proactively track expirations and rewrite/remove timestamps when block.timestamp >= timestamp. This requires maintaining extra data structures and can introduce DoS/griefing surfaces (e.g., repeatedly resetting timestamps to create unavoidable maintenance/scan costs).
Choice 3: Lazily Clean-Up Timestamps
Perform expiry checks and potential pruning only on the next interaction (a deterministic hook on tx processing). This raises the attacker cost (they must pay to trigger checks), but accounts that deactivate once and never touch the chain again might never be âcleaned upâ. In practice, such state growth is in the same category as general state bloat where someone pays to write persistent bytes.
For the prune strategy, I lean toward choice 3, the reasons are provided above.
Finally, Iâm going to collect feedback from the community and client developers on these design preferences (system-contract-based or code-based, whether we want a cancel period, whether we want any expiry cleanup). Since it currently seems that common designs converge to a certain degree of completion, and adding an update PR wonât be the bottleneck, I can iterate quickly once thereâs alignment.
I recall that precompiled contracts have had consensus errors in the past, so I lean towards using opcode instead. Proxy isnât a major issue with the current design a delay like that is long enough to allow users to take action in case of unforeseen problems.
Cleaning up the old state can be done manually in the future, so I still lean towards option 3.
Additionally, I like this suggestion and hope it will be implemented in future network upgrades.
Why not use a different authorization type instead of a system contract or an opcode? An EIP-7702 transaction with one of these authorizations would permanently delegate (and deactivate) the account, using the 0xef0101 (or whatever) prefix.
The new authorization type doesnât even need to be fundamentally different. We could use a nonce of 2**64-1 to signal a permanent delegation.
With respect to the deactivation window: I donât think the complexity is worth it. Users can simply delegate with the current mechanism, use it for 7 days or 7 months or however long they want, and then permanently delegate when they feel comfortable.