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.
Recent years have seen the rise of blockchain games, decentralized finance (DeFi), and other applications based on blockchain technology, mostly on the Ethereum protocol.
Known as Blockchain 2.0, Ethereum is hailed as The Unstoppable World Computer, which provides powerful decentralized computing capabilities. The core of the Ethereum protocol lies in the Ethereum Virtual Machine (or EVM for short), a stack-based quasi-Turing complete virtual machine, which is embedded in each Ethereum node in a sandbox mode for the deployment and execution of smart contracts. In Ethereum, all operations but the basic transfer between external accounts involve EVM which is used to execute contracts and update the status of corresponding accounts. The Ethereum Contract written in the high-level language Solidity
can be executed in the EVM after being compiled into EVM bytecode. The composition and design of EVM are as below.
The Composition of EVM - Design
EVM is mainly composed of three parts: StateDB
environment, which is the context of the chain, Interpreter
, which is the instruction interpreter, and the Environment Function
; each of the three independent parts plays a different role in running a virtual machine.
StateDB
environment, the chain's context: It provides on-chain data support for the EVM virtual machine, and can persist the data that needs to be stored during the execution of the contract on the chain, for example, the update of the account balance during the execution of the contract and the update of the internal status of contract accounts. It is similar to the hard disk in a general-purpose computer.Interpreter
, the instruction interpreter: It interprets and executes the compiled contract bytecode. Different from the general virtual machine, it contains the concept of Gas in the execution of EVM to solve downtime and resource consumption, so when the interpreter executes instructions, it will also consume Gas for corresponding instructions. The interpreter calls relevant instructions according to the PC and obtains the operand required by the instruction from the stack and memory; if it is a simple instruction (e.g. the arithmeticADD
and the comparison instructionGT
), the interpreter directly calculates the result; if it is an instruction that belongs to EVM semantics (e.g.SSTORE
andCALL
), the operand interacts withStateDB
orEnvironment Function
to calculate the result of the instruction, and then stores the result on the stack.Environment Function
(orENV FUNC
for short): It provides the execution logic of EVM-specific instructions, interacts through the operands of the instructions andStateDB
, and calculates the execution results (for example, for CALL instructions, EVM first obtains the operands required by the instruction from the stack, and passes the operand andStateDB
intoENV FUNC
which uses its own execution logic and incoming data to calculate the result and returns the result to the interpreter); part of the code is shown as below:func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ... {
//1. Obtain the operands required by the instruction from the stack
var (
value = callContext.stack.pop()
offset, size = callContext.stack.pop(), callContext.stack.pop()
input = callContext.memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = callContext.contract.Gas
)
....
//2. Calculate the instruction result with ENV FUNC
res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig())
....
//3. Press the calculation result back to the stack
stackvalue.SetBytes(addr.Bytes())
callContext.stack.push(&stackvalue)
callContext.contract.Gas += returnGas
}From a macro perspective, EVM can be divided into two levels of abstraction, as shown in the following figure.
1.For abstraction from the VM layer, fundamentally speaking, EVM only exposes two methods to call Call
and Create
.
type VM interface{
Create(caller types.ContractRef, code []byte, gas uint64, val sdk.Int) (ret []byte, contractAddr sdk.AccAddress, gasLeft uint64, err error)
Call(caller types.ContractRef, toAddr sdk.AccAddress, input []byte, gas uint64, val sdk.Int) (ret []byte, gasLeft uint64, err error)
}
Create
: It is to create the specific contract according to the transaction on the chain. The parameters are the creator’s address, the original contract bytecode, gas, the amount, the created contract address to be returned, contract storage bytecode, and remaining gas.Call
: It is to call the specified contract based on the transaction on the chain. The parameters are the caller's address, contract address, call input, gas, the amount, the contract execution result to be returned, and the remaining gas.
2.The interpreter layer is abstract. The function of the interpreter is relatively simple, which is to interpret and execute contract instructions and push calculations onto the stack.
Run
: It is to explain the execution of the contract; the parameters are contract information, call input, and the execution result to be returned.
CanRun
: It is to explain the execution of the contract; the parameters are contract information, call input, and the execution result to be returned.It is to explain the execution of the contract; the parameters are contract information, call input, and the execution result to be returned.
→ The official interpreter that comes with the go-ethereum project
→ The C++ version of the interpreter EVMONE
and C++ version of interpreter Hera implemented in accordance with EVMC
interpreter specifications, among which Hera
is a Wasm bytecode implementation.
3. The overall structure of EVM is organized as follows
type VM interface{ ... }
type EVM struct {
....
StateDB StateDB
interpreter Interpreter
}
func (evm *EVM) Call(...)...
func (evm *EVM) Create(...)...
Go's type system is Duck typing
, which does not require a type to explicitly declare that it implements an interface. Therefore, the EVM structure of go-ethereum implements the VM interface.
Duck typing follows the criterion that “If you can do it, you can be used here”。
4.The overall execution process of EVM is shown in the figure below
Summary
Therefore, in other blockchain projects that are ready to be compatible with EVM, EVM can be introduced through the following schemes:
1.Only quote the ready-made EVM bytecode interpreter, and implement ENV FUNC
in the project.
For example, use an interpreter that conforms to the EVMC specification: EVMONE or Hera;
The official go-ethereum interpreter is not split into a single importable component and thus cannot be used independently.
2.Quote the official go-ethereum EVM, and then replace the Interpreter implementation.
All the interpreter projects mentioned above can be used.
But no matter which method is used, the context StateDB
of the chain needs to be provided for the virtual machine for data exchange with the chain.