ERC-5559: "Cross Chain Write Deferral Protocol"
The cross chain write deferral protocol provides a mechanism to defer the storage & resolution of mutations to off-chain handlers
Abstract
The following standard provides a mechanism in which smart contracts can request various tasks to be resolved by an external handler. This provides a mechanism in which protocols can reduce the gas fees associated with storing data on mainnet by deferring the handling of it to another system/network. These external handlers act as an extension to the core L1 contract.
This standard outlines a set of handler types that can be used for managing the execution and storage of mutations (tasks), as well as their corresponding tradeoffs. Each handler type has associated operational costs, finality guarantees, and levels of decentralization. By further specifying the type of handler that the mutation is deferred to, the protocol can better define how to permission and secure their system.
This standard can be implemented in conjunction with EIP-3668 to provide a mechanism in which protocols can reside on and be interfaced through an L1 contract on mainnet, while being able to resolve and mutate data stored in external systems.
Motivation
EIP-3668 provides a mechanism by which off-chain lookups can be defined inside smart contracts in a transparent manner. In addition, it provides a scheme in which the resolved data can be verified on-chain. However, there lacks a standard by which mutations can be requested through the native contract, to be performed on the off-chain data. Furthermore, with the increase in L2 solutions, smart contract engineers have additional tools that can be used to reduce the storage and transaction costs of performing mutations on the Ethereum mainnet.
A specification that allows smart contracts to defer the storage and resolution of data to external handlers facilitates writing clients agnostic to the storage solution being used, enabling new applications that can operate without knowledge of the underlying handlers associated with the contracts they interact with.
Examples of this include:
- Allowing the management of ENS domains externally resolved on an L2 solution or off-chain database as if they were native L1 tokens.
- Allowing the management of digital identities stored on external handlers as if they were in the stored in the native L1 smart contract.
Specification
Overview
There are two main handler classifications: L2 Contract and Off-Chain Database. These are determined based off of where the handler is deployed. The handler classifications are used to better define the different security guarantees and requirements associated with its deployment.
From a high level:
- Handlers hosted on an L2 solution are EVM compatible and can use attributes native to the Ethereum ecosystem (such as address) to permission access.
- Handlers hosted on an Off-Chain Database require additional parameters and signatures to correctly enforce the authenticity and check the validity of a request.
A deferred mutation can be handled in as little as two steps. However, in some cases the mutation might be deferred multiple times.
- Querying or sending a transaction to the contract
- Querying or sending a transaction to the handler using the parameters provided in step 1
In step 1, a standard blockchain call operation is made to the contract. The contract either performs the operation as intended or reverts with an error that specifies the type of handler that the mutation is being deferred to and the corresponding parameters required to perform the subsequent mutation. There are two types of errors that the contract can revert with, but more may be defined in other EIPs:
StorageHandledByL2(chainId, contractAddress)
StorageHandledByOffChainDatabase(sender, url, data)
In step 2, the client builds and performs a new request based off of the type of error received in (1). These handshakes are outlined in the sections below:
In some cases, the mutation may be deferred multiple times
Data Stored in an L1
In the case in which no reversion occurs, data is stored in the L1 contract when the transaction is executed.
Data Stored in an L2
The call or transaction to the L1 contract reverts with the StorageHandledByL2(chainId, contractAddress)
error.
In this case, the client builds a new transaction for contractAddress
with the original callData
and sends it to a RPC of their choice for the corresponding chainId
. The chainId
parameter corresponds to an L2 Solution that is EVM compatible.
Example
Suppose a contract has the following method:
Data for this mutations is stored and tracked on an EVM compatible L2. The contract author wants to reduce the gas fees associated with the contract, while maintaining the interoperability and decentralization of the protocol. Therefore, the mutation is deferred to a off-chain handler by reverting with the StorageHandledByL2(chainId, contractAddress)
error.
One example of a valid implementation of setAddr
would be:
For example, if a contract returns the following data in an StorageHandledByL2
:
The user, receiving this error, creates a new transaction for the corresponding chainId
, and builds a transaction with the original callData
to send to contractAddress
. The user will have to choose an RPC of their choice to send the transaction to for the corresponding chainId
.
Data Stored in an Off-Chain Database
The call or transaction to the L1 contract reverts with the StorageHandledByOffChainDatabase(sender, url, data)
error.
In this case, the client performs a HTTP POST request to the gateway service. The gateway service is defined by url
. The body attached to the request is a JSON object that includes sender
, data
, and a signed copy of data
denoted signature
. The signature is generated according to a EIP-712, in which a typed data signature is generated using domain definition, sender
, and the message context, data
.
sender
ia an ABI-encoded struct defined as:
data
ia an abi encoded struct defined as:
signature
is generated by using the sender
& data
parameters to construct an EIP-712 typed data signature.
The body used in the HTTP POST request is defined as:
Example
Suppose a contract has the following method:
Data for this mutations is stored and tracked in some kind of off-chain database. The contract author wants the user to be able to authorize and make modifications to their Addr
without having to pay a gas fee. Therefore, the mutation is deferred to a off-chain handler by reverting with the StorageHandledByOffChainDatabase(sender, url, data)
error.
One example of a valid implementation of setAddr
would be:
For example, if a contract reverts with the following:
The user, receiving this error, constructs the typed data signature, signs it, and performs that request via a HTTP POST to url
.
Example HTTP POST request body including requestParams
and signature
:
Note that the message could be altered could be altered in any way, shape, or form prior to signature and request. It is the backend's responsibility to correctly permission and process these mutations. From a security standpoint, this is no different then a user being able to call a smart contract with any params they want, as it is the smart contract's responsibility to permission and handle those requests.
Data Stored in an L2 & an Off-Chain Database
The call or transaction to the L1 contract reverts with the StorageHandledByL2(chainId, contractAddress)
error.
In this case, the client builds a new transaction for contractAddress
with the original callData
and sends it to a RPC of their choice for the corresponding chainId
.
That call or transaction to the L2 contract then reverts with the StorageHandledByOffChainDatabase(sender, url, data)
error.
In this case, the client then performs a HTTP POST request against the gateway service. The gateway service is defined by url
. The body attached to the request is a JSON object that includes sender
, data
, and signature
-- a typed data signature corresponding to EIP-712.
Events
When making changes to core variables of the handler, the corresponding event MUST be emitted. This increases the transparency associated with different managerial actions. Core variables include chainId
and contractAddress
for L2 solutions and url
for Off-Chain Database solutions. The events are outlined below in the WriteDeferral Interface.
Write Deferral Interface
Below is a basic interface that defines and describes all of the reversion types and their corresponding parameters.
Use of transactions with storage-deferral reversions
In some cases the contract might conditionally defer and handle mutations, in which case a transaction may be required. It is simple to use this method for sending transactions that may result in deferral reversions, as a client should receive the corresponding reversion while preflighting
the transaction.
This functionality is ideal for applications that want to allow their users to define the security guarantees and costs associated with their actions. For example, in the case of a decentralized identity profile, a user might not care if their data is decentralized and chooses to defer the handling of their records to the off-chain handler to reduce gas fees and on-chain transactions.
Rationale
Use of revert
to convey call information
EIP-3668 adopted the idea of using a revert
to convey call information. It was proposed as a simple mechanism in which any pre-existing interface or function signature could be satisfied while maintain a mechanism to instruct and trigger an off-chain lookup.
This is very similar for the write deferral protocol, defined in this EIP; without any modifications to the ABI or underlying EVM, revert
provides a clean mechanism in which we can "return" a typed instruction - and the corresponding elements to complete that action - without modifying the signature of the corresponding function. This makes it easy to comply with pre-existing interfaces and infrastructure.
Use of multiple reversion & handler types to better define security guarantees
By further defining the class of the handler, it gives the developer increased granularity to define the characteristics and different guarantees associated storing the data off-chain. In addition, different handlers require different parameters and verification mechanisms. This is very important for the transparency of the protocol, as they store data outside of the native ethereum ecosystem. Common implementations of this protocol could include storing non-operational data in L2 solutions and off-chain databases to reduce gas fees, while maintaining open interoperability.
Backwards Compatibility
Existing contracts that do not wish to use this specification are unaffected. Clients can add support for Cross Chain Write Deferrals to all contract calls without introducing any new overhead or incompatibilities.
Contracts that require Cross Chain Write Deferrals will not function in conjunction with clients that do not implement this specification. Attempts to call these contracts from non-compliant clients will result in the contract throwing an exception that is propagated to the user.
Security Considerations
Deferred mutations should never resolve to mainnet ethereum. Such attempts to defer the mutation back to ETH could include hijacking attempts in which the contract developer is trying to get the user to sign and send a malicious transaction. Furthermore, when a transaction is deferred to an L2 system, it must use the original calldata
, this prevents against potentially malicious contextual changes in the transaction.
Fingerprinting attacks
As all deferred mutations will include the msg.sender
parameter in data
, it is possible that StorageHandledByOffChainDatabase
reversions could fingerprint wallet addresses and the corresponding IP address used to make the HTTP request. The impact of this is application-specific and something the user should understand is a risk associated with off-chain handlers. To minimize the security impact of this, we make the following recommendations:
- Smart contract developers should provide users with the option to resolve data directly on the network. Allowing them to enable on-chain storage provides the user with a simple cost-benefit analysis of where they would like their data to resolve and different guarantees / risks associated with the resolution location.
- Client libraries should provide clients with a hook to override Cross Chain Write Deferral
StorageHandledByOffChainDatabase
calls - either by rewriting them to use a proxy service, or by denying them entirely. This mechanism or another should be written so as to easily facilitate adding domains to allowlists or blocklists.
We encourage applications to be as transparent as possible with their setup and different precautions put in place.
Copyright
Copyright and related rights waived via CC0.