
近些年来,基于区块链技术,出现了一些火爆的区块链游戏、去中心化金融(DeFi)等应用,而大部分这些应用,都是基于以太坊合约开发的。
以太坊被称为区块链2.0,号称永不停机的世界计算机(The Unstoppable World Computer),提供了强大的去中心运算能力。而以太坊协议的核心是以太坊虚拟机(简称EVM),是一个基于堆栈的准图灵完备的虚拟机,以沙箱模式嵌入在每个以太坊节点中,用来处理智能合约的部署和执行。在以太坊中,除了基础的外部账户间的转账,剩余所有操作都涉及到EVM,用来执行合约,更新对应账户的状态。用高级语言Solidity编写的以太坊合约,编译为EVM字节码后,就可以在EVM中执行。下面将介绍EVM的组成、以及设计。
EVM组成-设计
EVM主要由三部分组成:链的上下文StateDB环境、指令解释器Interpreter、Environment Function;作为独立的三部分,各自在运行虚拟机中运行中起着不同的作用。
-
链的上下文
StateDB环境:向EVM虚拟机提供链上数据支持,同时可以将合约执行过程中的需要存储的数据持久化至链上;例如:合约执行过程中账户余额的更新、合约账户内部状态的更新等。类似与通用计算机中的硬盘。 -
指令解释器
Interpreter:解释执行编译后合约字节码,与一般虚拟机不同的是,EVM执行中有Gas的概念,用来解决停机和资源消耗问题,所以在解释器执行指令时,也会对计算相应指令的Gas消耗。解释器依据PC调用相应的指令,从堆栈、内存中获取该指令所需的操作数;如果属于简单指令(如:算术ADD、比较指令GT...),则解释器直接计算相应指令的结果;如果属于EVM语义的指令(如:SSTORE、CALL),则将操作数与StateDB或Enviroment Function进行交互,计算指令结果,然后将结果存入堆栈。 -
Environment Function(简写ENV FUNC):提供EVM特有指令的执行逻辑,使用指令的操作数、StateDB进行交互,计算执行结果(如:对于CALL指令,EVM先从堆栈中获取指令所需的操作数,将操作数和StateDB传入ENV FUNC,ENV FUNC使用自身的执行逻辑和传入数据,计算结果,返回给解释器);部分代码展示如下func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ... { //1. 从堆栈、内存获取指令操作数 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. 使用ENV FUNC计算指令结果 res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig()) .... //3. 将计算结果压回站上 stackvalue.SetBytes(addr.Bytes()) callContext.stack.push(&stackvalue) callContext.contract.Gas += returnGas }
从更宏观上来看,可以将EVM分为两层抽象,如下图所示。

-
从VM层进行抽象,从根本上来说,EVM只对外暴漏了两个方法调用
Call、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: 依据链上交易,来创建指定的合约;参数为创建者地址、原始合约字节码、gas、金额,返回创建的合约地址、合约存储字节码和剩余gas。Call: 依据链上交易,来调用指定合约;参数为调用者地址、合约地址、调用输入、gas、金额,返回合约执行结果、剩余gas。
-
解释器层抽象,解释器的功能比较单纯,解释执行合约指令,并将计算压入栈。
type Interpreter interface { Run(contract *Contract, input []byte, static bool) ([]byte, error) CanRun([]byte) bool }Run: 用来解释执行合约;参数为合约信息、调用输入,返回执行结果。CanRun: 解释器的额外控制逻辑,可以作为解释器的拓展点。- 当前的有一些解释器的开源实现和规范
- go-ethereum项目自带的官方解释器
- 遵循
EVMC解释器规范实现的C++版解释器EVMONE,C++版解释器Hera,其中Hera是一个Wasm版字节码实现。
-
EVM整体结构上,为如下组织
type VM interface{ ... } type EVM struct { .... StateDB StateDB interpreter Interpreter } func (evm *EVM) Call(...)... func (evm *EVM) Create(...)...Go 的类型系统为
Duck typing,不要求一个类型显式地声明它实现了某个接口。因此go-ethereum的EVM结构体实现了VM接口。- Duck typing 的准则是 “If you can do it, you can be used here”。
-
EVM的整体执行流程如下图所示

小结
所以在一个准备兼容EVM的其它区块链项目中,可以通过下面几种方案,来引入EVM:
-
只引用现成的EVM字节码解释器,项目中自己实现
ENV FUNC.- 例如使用遵从EVMC规范的解释器实现:EVMONE、Hera ...
- 官方的go-ethereum解释器,并未拆分为一个单独的可导入组件,无法独立使用。
-
引用官方go-ethereum的EVM,然后替换其中的Interpreter实现。
- 可以使用上述提到的那些解释器项目
但无论使用上述那种方式,都需要给虚拟机提供链的上下文StateDB,用于和链之间进行数据交互。
*本文由CoinEx Chain开发团队成员撰写。CoinEx Chain是全球首条基于Tendermint共识协议和Cosmos SDK开发的DEX专用公链,借助IBC来实现DEX公链、智能合约链、隐私链三条链合一的方式去解决可扩展性(Scalability)、去中心化(Decentralization)、安全性(security)区块链不可能三角的问题,能够高性能的支持数字资产的交易以及基于智能合约的Defi应用。