Dual-VM Implementation for CoinEx Smart Chain

in coinex •  5 years ago 

This article is written by the CoinEx Chain lab. CoinEx Chain is the world’s first public chain exclusively designed for DEX, and will also include a Smart Chain supporting smart contracts and a Privacy Chain protecting users’ privacy.

Motivation

WebAssembly is a fast, low-level and light-weighted virtual machine, which has been a great success in web environment. It can support many languages such as C/C++, Rust, Golang and AssemblyScript and extends the programability of frontend largely from pure javascript. And its ecosystem is getting richer and richer every month.

Since it's fast, multi-lingual and rich toolset support, it makes a good platform for smart contracts. EOS, Polkadot and ETH2.0 all choose it.

However, Solidity and EVM are still the most mature and popular environment for developing and running smart contracts. There are many valuable legacy source codes. So although EVM is slower, migrating from it to WebAssembly is still a hard decision.

How to make the migration more smooth? A simple answer is to support both of them. The legacy smart contracts in EVM byte can still be deployed and the new contracts can choose to switch to WebAssembly, without losing the interoperability with old contracts using EVM.

That's what CoinEx Smart Chain will do. Here we share the basic methods to support smart contracts in both EVM and WebAssembly.

How to re-use the legacy code developed for ETH1.0

How can we deploy some legacy code developed for ETH1.0 in a new blockchain? There are following possibilities:

  1. Compile Solidity source code to WebAssembly bytecode. SOLL is such kind of compiler. However, it is still under development.

  2. Transpile EVM bytecode to WebAssembly bytecode. Since high-level information is lost when compiling Solidity source code to EVM bytecode, such transpilers can not make enough optimizations and the resulted WebAssembly will be large and slow.

  3. Compile a EVM interpretter written in C++ or rust into WebAssembly bytecode, thus we can use WebAssembly VM to interpret EVM bytecode. This method certainly works. But since WebAssembly VM is still slower than native binary, this method has performance penalty.

  4. Implement both an EVM and a WebAssembly on the same blockchain. This is the most straight-forward method.

We decided to use method 4 for the best performance. We chose to use evmone as the EVM implementation and wasmer as the WebAssembly VM implementation, because they are mature and have good modularity.

Build the common infrastructure

Although they follow different bytecode specification, evmone and warmer has many in common. For example, they both need host functions to work.

For evmone, the following host interface must be provided:

classHostInterface{
public:
   virtualboolaccount_exists(constaddress&addr) constnoexcept=0;
   virtualbytes32get_storage(constaddress&addr, constbytes32&key) constnoexcept=0;
   virtualevmc_storage_statusset_storage(constaddress&addr,
                                           constbytes32&key,
                                           constbytes32&value) noexcept=0;
   virtualuint256beget_balance(constaddress&addr) constnoexcept=0;
   virtualsize_tget_code_size(constaddress&addr) constnoexcept=0;
   virtualbytes32get_code_hash(constaddress&addr) constnoexcept=0;
   virtualsize_tcopy_code(constaddress&addr,
                            size_tcode_offset,
                            uint8_t*buffer_data,
                            size_tbuffer_size) constnoexcept=0;
   virtualvoidselfdestruct(constaddress&addr, constaddress&beneficiary) noexcept=0;
   virtualresultcall(constevmc_message&msg) noexcept=0;
   virtualevmc_tx_contextget_tx_context() constnoexcept=0;
   virtualbytes32get_block_hash(int64_tblock_number) constnoexcept=0;
   virtualvoidemit_log(constaddress&addr,
                         constuint8_t*data,
                         size_tdata_size,
                         constbytes32topics[],
                         size_tnum_topics) noexcept=0;
};

For wasmer, the following cgofunctions must be provided to support smart contract:

typedefint32_tptr32_t;
voidstorageLoad(ptr32_tpathOffset, ptr32_tvalueOffset);
voidstorageStore(ptr32_tpathOffset, ptr32_tvalueOffset);
voidgetBalance(ptr32_taddressOffset, ptr32_tresultOffset);
uint32_tgetCodeSize();
voidgetExtCodeHash(ptr32_taddressOffset, ptr32_tresultOffset)
voidextCodeCopy(ptr32_taddressOffset, ptr32_tresultOffset, uint32_tcodeOffset, uint32_tlength);
uint32_tcreate(ptr32_tvalueOffset, ptr32_tdataOffset, uint32_tlength, ptr32_tresultOffset);
uint32_tcall(int64_tgasLimit, ptr32_taddressOffset, ptr32_tvalueOffset, ptr32_tdataOffset, uint32_tdataLength);
uint32_tcallCode(int64_tgasLimit, ptr32_taddressOffset, ptr32_tvalueOffset, ptr32_tdataOffset, uint32_tdataLength);
voidfinish(ptr32_tdataOffset, uint32_tlength);
uint32_tcallDelegate(int64_tgasLimit, ptr32_taddressOffset, ptr32_tdataOffset, uint32_tdataLength);
uint32_tcreate2(ptr32_tvalueOffset, ptr32_tdataOffset, uint32_tlength, ptr32_tresultOffset);
uint32_tcallStatic(int64_tgasLimit, ptr32_taddressOffset, ptr32_tdataOffset, uint32_tdataLength);
voidgetTxOrigin(ptr32_taddressOffset);
voidgetCaller(ptr32_tresultOffset);
voidgetCallValue(ptr32_tresultOffset);
uint32_tgetCallDataSize();
voidcallDataCopy(ptr32_tresultOffset, uint32_tdataOffset, uint32_tlength);
voidcodeCopy(ptr32_tresultOffset, uint32_tcodeOffset, uint32_tlength);
voidgetTxGasPrice(ptr32_tvalueOffset);
voidgetBlockHash(uint64_tnumber, ptr32_tresultOffset);
voidlogN(ptr32_tdataOffset, uint32_tlength, uint32_tnumberOfTopics, uint32_ttopic1, uint32_ttopic2, uint32_ttopic3, uint32_ttopic4);
// There are some other functions, which are not listed because of limited space

Despite the different appearance of these code, they require the same functionalities of the host environment. So we just use one struct ExeContextto provide these functionalities to both VMs, and each VM use an adapter to map these functionalities to the C/C++ functions needed by it. As is shown below:

To start the execution of a smart contract, no matter on which VM, we must input some information and expect some outputs from it. So we can abstract the input/output behavior of the execution of smart contract as following interface:

typeContractExecutorinterface{
Execute(ctxinterface{}, revevm.Revision,
kindevm.CallKind, staticbool, depthint, gasint64,
destinationsdk.AccAddress, sendersdk.AccAddress, input[]byte, valueint64,
code[]byte, create2Salt[]byte) (output[]byte, gasLeftint64, errerror)
}

The VM's adapters will both implement this ContractExecutor interface, for the host environment to start them.

ABI and Interoperability

When a smart contract is called, by an external account or another smart contract, it takes a byte slice as input and outputs the results as another byte slice. Looking from the external world, no matter a smart contract is written in EVM or WebAssembly, it behaves the same way. You cannot distinguish a contract in EVM from a contract in WebAssembly, just from its behavior showing to the external world.

At the source code level, you can also confirm this. Both the VMs support the same ContractExecutor interface.

So an EVM contract can call a WebAssembly contract, just like it calls another EVM contract, and vice visa. Thus, the new WebAssembly contracts can interoperate with the legacy EVM contracts.

Ethereum has a specification of ABI for EVM, which defines how concrete data structs are serialized to input/output byte slices of the contracts. For smooth interoperating, the WebAssembly contracts must follow this ABI, too. On CoinEx Smart Chain, there are several host functions which help programmer to parse/return parameters according to the EVM ABI.

Summary

Through modularization and abstracting both VMs into one common interface, we maximized the reusable code and limit the effort of supporting an extra VMs to just writing some adapter logic. CoinEx Smart Chain can support both EVM and WebAssembly VM, and its smart contracts can interoperate with each other even they run in different VMs.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!