Stateful Precompile Developer UX
Introduction
Stateful precompiles are required to give the EVM atomic access to Cosmos-SDK modules. The currently proposed implementation (https://github.com/evmos/ethermint/pull/1131) manually maintains a StateDBJournal Log and only makes the underlying changes to the SDK at commit time. This caveat creates a huge developer UX problem and makes interoperability between the EVM and SDK Modules difficult.
If not resolved, I believe this issue will have massive long-term implications for Ethermint chains such as Evmos and Berachain and will cause them to fall behind WASM-based chains that can more seamlessly utilize ICS, ICA, ICQ, and other applications of IBC.
Current Problem
The proposed implementation leads to poor dev UX and duplicate code within Cosmos SDK modules. You must completely duplicate a module's logic to make it compatible with this code flow.
@yihuang has built an example: here. The StateDB journalling method is manageable for simple stateful precompiles, but for more complex logic it is a severe problem.
Underlying Issue
It appears as if the reason for the currently proposed design is the behavior created around the semantics of the MsgEthereumTx and how it is handled. When an EVM transaction reverts, the MsgEthereumTxResponse goes through, albeit with a return payload that indicates revert as shown in the VMError field.
This design choice is problematic for Stateful Precompiles because, since the native Cosmos level transaction was guarded as a success, any writes to the chain's store will not revert, the EVM just clears the StateDBJournal, and all is well. Thus if we were to call a module's logic through a stateful precompile and NOT use the journal semantics, even though the EVM transition reverted, the KVStore changes would persist.
Overall, the current design has been a major barrier towards getting Berachain to a level of developer UX that is familiar to EVM folk, while also ensuring we can leverage the SDK and IBC to its full potential.
End Goal
The end goal for ideal developer UX would be to enable something like what we see in CosmWasm where we have wasmbindings. For example, Osmosis allows users to execute a wide variety of dex logic from within their CosmWasm VM here.
For instance, on Berachain, we want users to be able to atomically swap with our decentralized exchange (which is written as an SDK module similar to Osmosis), but be able to do so from the EVM and receive tokens atomically.
Example Use Case
- EVM Contract 1 calls the stateful precompile.
- The stateful precompile transfers
abcERC20 tokens to itself, converts them to native denomabccoins. - "x/swap" module is invoked and swaps
abccoins todcfcoins, on the SDK level exchange, which operates in native denoms. - The stateful precompile converts
dcfcoins to ERC20dcftokens. - The stateful precompile transfers ERC20
dcftokens to EVM Contract 1. - EVM Contract 1 is able to transfer and use these new tokens as the SDK logic as been executed synchronously.
We could write this logic utilizing the StateDBJournal semantics, but then we would have a ton of duplicate code, as we also want to support swapping native denoms on our dex over IBC, Keplr wallet, etc.
Suggested Solution
We believe that the underlying issue results from the MsgEthereumTx revert behavior and that resolving the underlying MsgEthereumTx architecture design is the best path forward for both stateful precompiles and Ethermint as a whole.
@marbar3778 @jackzampolin @zmanian would love all of your thoughts here as well.
I
We've tried nested cache context to handle snapshot/revert before, the performance is much worse than journal logs.
Could you elaborate further? If you are doing SnapshotRevert for cosmos modules that aren't x/evm the cache size should be manageable. Then keep the current StateDB for just x/evm.
Seems as if https://github.com/evmos/ethermint/issues/1227 would be helpful here.
Could you elaborate further? If you are doing SnapshotRevert for cosmos modules that aren't
x/evmthe cache size should be manageable. Then keep the current StateDB for justx/evm.
https://github.com/evmos/ethermint/blob/e9f1ab646c128360b0779c93cc6a3ce68b5d4a22/x/evm/keeper/context_stack.go#L87 this is the solution we use before switch to journal logs, it works well with native cosmos-sdk calls, just slow, the slowness is described here: https://github.com/cosmos/cosmos-sdk/issues/10310.
I'm thinking, maybe there is a way to bifurcate the caching of data being written to modules outside of x/evm.
Thus caching EVM StateDB changes using the journal, and then calls into a module separately. By doing so you are going to significantly reduce the depth of this wrapping / probably even removing nesting completely.
This issue is stale because it has been open 45 days with no activity. Remove Status: Stale label or comment or this will be closed in 7 days.