# WrappedEVM Eth RPC compatibility layer

**URL:** <https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775>\
**Category:** Tech Talk\
**Tags:** frame, smart-contracts, frontier, ethereum\
**Created:** [May 3, 2023, 7:36pm UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775 "2023-05-03T19:36:05Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![bernardoaraujor](https://dub1.discourse-cdn.com/flex005/user_avatar/forum.polkadot.network/bernardoaraujor/32/733_2.png) [@bernardoaraujor](https://forum.polkadot.network/u/bernardoaraujor)\
**Post date:** [May 3, 2023, 7:36pm UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775/1 "2023-05-03T19:36:05Z")

</div>

Here is a scheme for categorizing Ethereum compatibility for some given blockchain. The key is to assess the layers of Program Equivalence:

- **Level 0** : Full compatibility (Essentially a chain code fork)
- **Level 1** : Bytecode level compatibility
- **Level 2** : Code compile-level language compatibility (Eg. Solang)
- **Level 3** : RPC/endpoint extrinsic compatibility (Eg. Ability to sign using Metamask / Ability to read data using the same block explorers)

While [`Frontier`](https://github.com/paritytech/frontier)’s `pallet-evm` and `pallet-ethereum` already provide Level 0 compatibility to Substrate, there’s instances where we might prefer native Substrate execution over Frontier’s [SputnikVM](https://github.com/rust-blockchain/evm) Runner, as a workaround to its inherent performance penalties. So we want to be able to navigate this scheme as a spectrum of compatibility with legacy EVM standards.

The easiest place to start is at Level 3. From there, climbing up the compatibility levels implies that things can go in a couple different directions, namely:

1. **EVMless Execution** : Re-routing Eth RPCs for native Subtrate execution under multiple FRAME pallets.
2. **Wasm Contract Execution** : Re-routing Eth RPCs for execution under `pallet-contracts`.

I recently learned about a community effort that is apparently being called **`WrappedEVM`** , including teams like Interlay, Centrifuge and HydraDX. So I’m starting this thread as a way to coordinate those efforts.

## EVMless Execution (`pallet-evmless`)

The [`dispatch`](https://github.com/paritytech/frontier/tree/master/frame/evm/precompile/dispatch) precompile is made available by `pallet-evm`. It an obvious initial step in this direction, as it opens up the possibility of triggering FRAME execution via Eth RPCs. It is already available and a really low-hanging-fruit for teams aiming to go down this route.

It is still a bit cumbersome, however, because the RPC side is still partially “contaminated” with Substrate, as it requires the actual FRAME calls to be SCALE encoded.

For some cases, we might want the Eth RPCs to believe they’re interacting entirely with an EVM ABI, while staying completely agnostic in regards to the Substrate realm. **This starts from the assumption that there are already pallets deployed into the Runtime that are able to cover for all the expected functionality that the ABIs expose**.

This would require some mechanism for the Runtime to receive the EVM dispatchables and re-route them for native Substrate execution. This can be achieved with a stripped down version of `pallet-evm` (let’s call it `pallet-evmless`), that provides a simplified EVM shell Runner. Then, for each `Solidity` use-case, different precompiles would be written ad-hoc, either tighly or loosely coupling with the pallets responsible for execution. [Unique’s `evm-coder`](https://forum.polkadot.network/t/introducing-evm-coder-a-library-for-seamless-call-translation-between-rust-and-solidity/2551) crate can be leveraged here so that developers can focus on writing their precompile logic in Rust without worrying too much about the low-level ABI details.

Ideally, we should standardize `pallet-evmless` as a new upstream feature on `Frontier`.

Note: I’m using the term `EVMless` to describe this specific path of action, while `WrappedEVM` is being used as the umbrella term for the overall initiative (which also includes the `pallet-contracts` path described below).

## Wasm Contract Execution (`pallet-contracts`)

While still a WIP, there’s a few tools in our community that have great potential to onboard `Solidity` devs into Substrate:

### evm-coder

Maintained by Unique Network, [`evm-coder`](https://github.com/UniqueNetwork/evm-coder) is a Rust library for seamless call translation between Rust and Solidity code. Not only it can be quite helpful writing precompiles based on `Solidity` contracts (on the `EVMless` context described above), but it can also be used while writing `ink!` contracts as well.

### Sol2Ink

[`Sol2ink`](https://github.com/Supercolony-net/sol2ink) is able to parse compilable `Solidity` interfaces into ink! traits and compilable `Solidity` contracts into ink! contracts, while leveraging the power of OpenBrush. The effort is led by SuperColony, but the repository seems stale for the past few months at the time of writing.

### Solang

Maintained by the HyperLedger Foundation, [`Solang`](https://github.com/hyperledger/solang) is a `Solidity` compiler for Solana (which we don’t really care about here) and Substrate. It is able to compile `Solidity` contracts for execution under `pallet-contracts`. The project is under active development, with frequent contributions from Parity’s Core Engineering.

### pallet-contracts

Be it some `Solidity` contract:

- transpiled to `ink!` and then compiled into `Wasm` via `Sol2ink` or `evm-coder`
- compiled straight into `Wasm` via `Solang`

While deploying them into `pallet-contracts`, we might want to still interact with them via Eth RPCs. This would imply new features being added into `pallet-contracts`. While theoretically feasible, there might be a few challenges to be tackled along the way:

- **ABI compatibility** : currently, `ink!` and `Solang` are not following the EVM ABI calling conventions. Making this happen (if even possible) would imply changing the `ink!` and `Solang` calling conventions, which opens a pandora box of breaking changes between different versions and can be seen as tabu. There’s also the problem that the `Wasm` contracts use types as arguments and return values that don’t exist in the EVM ABI. In this case we would probably just specify them as as bytes and live with the fact that a Dapp needs custom code to encode/decode them. Bottom line is: this topic needs more research.

Edit: as added by @lach in comment below , `evm-coder` can potentially help solve some of these issues of ABI compatibility for `ink!`.

- **EVM tooling compatibility** : Tooling might inspect the code. It might be as little as checking for some magic byte and as sophisticated as generating constructor code. It doesn’t matter if it is just a trivial check. That tool will not work as we will be feeding it wasm code. We would need to try to upstream compatibility fixes for the tooling projects.
- **Contract creation** : A contract running on `pallet-contracts` contains a dedicated “construction” entry point. When creating a new contract, input data to is passed to this constructor as data in the transaction. This is different from EVM, where the code passed as data is the constructor itself. There are certainly some ways so work around this but it will require `Solidity` developers to do things slightly different from what they are used to.

So there will likely be no perfect on-size-fits-all solution, and we will have to deal with fragmentation at some point. Which is arguably unavoidable, as we can already see in some Eth L2s breaking EVM bytecode and RPC compatibility (e.g.: zkSync and ERC1167).

Nevertheless, this is still a path worth pursuing, as onboarding Ethereum dApps and devs will be really important for the vision of [Hybrid Chains](https://www.rob.tech/blog/hybrid-chains/) in our Ecosystem.

---

<div class="post-metadata">

**Author:** ![lach](https://dub1.discourse-cdn.com/flex005/user_avatar/forum.polkadot.network/lach/32/1342_2.png) [@lach](https://forum.polkadot.network/u/lach)\
**Post date:** [May 4, 2023, 8:30am UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775/2 "2023-05-04T08:30:26Z")

</div>

> **ABI compatibility** : currently, `ink!` and `Solang` are not following the EVM ABI calling conventions. Making this happen (if even possible) would imply changing the `ink!` and `Solang` calling conventions, which opens a pandora box of breaking changes between different versions and can be seen as tabu.

Regarding mentioned `evm-coder` - it is not dependent on substrate code, so it can be used inside of `ink!` contracts as well. I.e (example code, implemented using not-yet-existing evm\_coder\_ink adapter):

```rust
struct Contract;

/// Ink calls
impl Contract {
    #[ink(message)]
    pub fn hello_ink(&self) -> String {
        "Hello from ink! side of a contract!".into()
    }

    // Forward ethereum calls to evm_coder
    #[ink(message)]
    pub fn evm_call(&mut self, calldata: Vec<u8>) -> evm_coder_ink::Result {
        let call = MyContractCall::parse(&calldata)?;
        let value = self.env().transferred_balance();
        let caller = self.env().caller();
        self.call(evm_coder::Msg {
            call,
            value,
            caller,
        }).into()
    }
}

/// Evm calls
#[evm_coder::solidity_interface(name = MyContract)]
impl Contract {
    pub fn hello_evm(&self) -> String {
        "Hello from evm side of a contract!".into()
    }
}

```

---

<div class="post-metadata">

**Author:** ![bernardoaraujor](https://dub1.discourse-cdn.com/flex005/user_avatar/forum.polkadot.network/bernardoaraujor/32/733_2.png) [@bernardoaraujor](https://forum.polkadot.network/u/bernardoaraujor)\
**Post date:** [May 4, 2023, 3:39pm UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775/3 "2023-05-04T15:39:19Z")

</div>

that’s awesome, thanks for this info @lach  
I edited the OP to make sure I’m covering these aspects of `evm-coder`.

---

<div class="post-metadata">

**Author:** ![bernardoaraujor](https://dub1.discourse-cdn.com/flex005/user_avatar/forum.polkadot.network/bernardoaraujor/32/733_2.png) [@bernardoaraujor](https://forum.polkadot.network/u/bernardoaraujor)\
**Post date:** [June 23, 2023, 8:52pm UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775/4 "2023-06-23T20:52:29Z")

</div>

Here’s a question that has been lingering in my mind:

Most parachains use `AccountId32` (`H256`), but the Eth RPCs only understand `H160`.

While Frontier provides a translation scheme for `H256 → H160` (simply drop the last 96 bits LE), there’s no deterministic way to convert `H160 → H256` at execution time. So when some incoming RPC carries an `H160` address as argument, how do we convert that into `H256` on the fly?

The only solution I can think for this problem is to come up with some kind of on-chain mapping for `H160 → H256`. That does result in some extra burden on the UX (e.g.: the user needs to keep track of 2 addresses for each account) and some storage bloat.

Are there any alternative solutions for this problem that I’m not seeing here?

p.s.: we will carry a session during the Parachain Summit in Copenhagen on this WrappedEVM topic. Hopefully that will be an opportunity to move this discussion forward. I’ll try to collect some insights and share them here afterwards.

---

<div class="post-metadata">

**Author:** ![xlc](https://dub1.discourse-cdn.com/flex005/user_avatar/forum.polkadot.network/xlc/32/690_2.png) [@xlc](https://forum.polkadot.network/u/xlc)\
**Post date:** [June 25, 2023, 8:34am UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775/5 "2023-06-25T08:34:10Z")

</div>

For Acala EVM+, we developed a pallet to handle the mapping and allow binding EVM address and Substrate address. The built-in MultiAddress from Substrate also makes the integration more smooth. For example, it is possible to use balances.transfer to send native token to EVM address in Acala.

---

<div class="post-metadata">

**Author:** ![lach](https://dub1.discourse-cdn.com/flex005/user_avatar/forum.polkadot.network/lach/32/1342_2.png) [@lach](https://forum.polkadot.network/u/lach)\
**Post date:** [July 6, 2023, 9:57am UTC](https://forum.polkadot.network/t/wrappedevm-eth-rpc-compatibility-layer/2775/6 "2023-07-06T09:57:50Z")

</div>

> While Frontier provides a translation scheme for `H256 → H160` (simply drop the last 96 bits LE), there’s no deterministic way to convert `H160 → H256` at execution time. So when some incoming RPC carries an `H160` address as argument, how do we convert that into `H256` on the fly?

Not quite right, Frontier provides translation to the other side: `H160 -> H256`, `trait AddressMapping`.  
The default implementation for the Substrate is `HashedAddressMapping`: `hash("evm:" ++ address)`.  
This mapping determines which Substrate address should hold the Ethereum account balance and nonce.

`H256 -> H160` mapping (`trait EnsureAddressOrigin`) is not exposed directly; there is only a trait provided to check if `H160` address provided by the user is allowed to be used for `evm_call`/`evm_withdraw` for the specified `H256`.  
The default implementation for the Substrate is `EnsureAddressTruncated`: `origin[0..20] == address`.

While it is possible to create on-chain mapping between H160 and H256 addresses, this approach is not perfect, as it requires some form of registration.  
This registration procedure may break some Ethereum RPC compatibility, i.e. Acala requires a custom web3.js provider to work.  
Without such a form of compatibility layer, you can’t reliably share any resources between substrate and Ethereum accounts.

As the Ethereum account linking may be added at an arbitrary time, and you can’t know the mapped address ahead of time, you still can’t use a single address for everything.  
Example:

- Alice has linked Ethereum address Alith on chain A.
- Someone transfers an NFT to Alith on the chain B. Chain B doesn’t yet know which substrate address Alith will have, so it still needs to have the original Ethereum address (Alith) stored.
- When Alice registers Alith’s mapping on chain B, assets should be either transferred to Alice (And this may not be possible if there is a large number of assets) or kept on Alith address (Then why do we need the mapping in the first place?).

At Unique, we have embraced the asymmetrical mapping for Ethereum (hashing when converting `H160 -> H256` and truncation when converting `H256 -> H160` (`TruncateAddress`)).  
There were a couple of challenges to solve, but in the end, we believe that our solution is pretty good for the general usage.

For cases when we need to return the resource owner to the Ethereum side, e.g NFT `ownerOf`, we keep an original user address in `enum CrossAccount {Substrate(AccountId), Ethereum(H160)}`: [https://github.com/UniqueNetwork/unique-frontier/blob/b1ff36163b15a36c1f73950c722cb1f3939d287d/frame/evm/src/account.rs#L53](https://github.com/UniqueNetwork/unique-frontier/blob/b1ff36163b15a36c1f73950c722cb1f3939d287d/frame/evm/src/account.rs#L53)  
What problem does it solve?

- Let Alice be the Substrate account.
- Let Alith be `TruncateAddress(Alice)`.
- We mint NFT to Alith’s account.
- If we only store the substrate address as an NFT owner, then the resulting NFT owner will be, say, Alyssa = `HashedAddressMapping(TruncateAddress(Alice))`.
- And then, `ownerOf` will return `TruncateAddress(Alyssa)`, which is not the same as `TruncateAddress(Alice)`, thus the result of `ownerOf` is useless.

Instead, we store `CrossAccount::Ethereum(Alith)`, so `ownerOf` will return the correct address: Alith.  
The same applies when the token transfer target is Alice. In this case, we store `CrossAccount::Substrate(Alice)`, and `ownerOf` will return `TruncateAddress(Alice)`, which is precisely Alith.

The other problem, which arises when no bidirectional translation is available, is Ethereum transaction fees/payment. Our solution is to delay any address conversions and use `CrossAccountId` in Frontier everywhere it is possible: [feat: move address conversions to the lower level · UniqueNetwork/unique-frontier@f814b3f · GitHub](https://github.com/UniqueNetwork/unique-frontier/commit/f814b3f446945ca5c44cb8f7fce5c0ec411ce9f6#diff-e8a51ec8b80d5831b2388d4679b2319251e9b72930e9dc19bf710ced2f94661fR52)  
This way, when the user tries to call an Ethereum contract using `pallet_evm.call` method, payment (Both fee and value) is done from the substrate address, instead of using `HashedAddressMapping(TruncateAddress(origin))`, as in vanilla frontier.
