Skip to content

Latest commit

 

History

History
176 lines (143 loc) · 9.6 KB

File metadata and controls

176 lines (143 loc) · 9.6 KB

EVM简介

EVM全称是以太坊虚拟机(Ethereum Virtual Machine),它为以太坊提供了智能合约的运行时环境。这个运行时环境是一个沙盒环境,在运行期间不能访问宿主机的网络、文件、系统,即使不同的合约之间也只有限的访问权限。智能合约中的代码必须经历从人类编写的高级语言代码到机器可读代码的转换。这需要将文本转换为十六进制值,从而将代码转换为字节码。字节码由编译器生成后在区块链上经由EVM处理后执行。

结合以太坊的特点,以太坊开发组为EVM给出了如下设计目标:

  • 简单性,操作码尽可能少且低级,数据类型尽可能少,虚拟机的结构尽可能简单。
  • 确定性,EVM的语句没有产生歧义的空间,结果在不同机器上的执行结果是确定一致的。
  • 节约空间,EVM的组件尽可能紧凑。
  • 区块链定制化的,必须可以处理20bytes的账户地址,自定义32bytes密码学算法的等。
  • 安全模型简单安全,Gas的计价模型应该是简单易行准确的。
  • 便于优化,以便即时编译(JIT)和VM的性能优化。

为了满足这些目标,EVM被设计成了基于栈的虚拟机。EVM中的栈采用了32字节(256bit)的字长,最多可以容纳1024个也作为EVM最小的操作单位。EVM提供了一个抽象层,允许代码跨平台运行和移植,市面上支持Solidity合约的绝大部分链都是对EVM进行了移植。就像其他基于堆栈的编程语言一样,你输入的最后一个输入是你取出的第一个输出。这是后进先出(LIFO)的概念。它使用零地址格式指令集,其中操作数始终位于已知位置。这不需要寄存器中的地址,因为堆栈中的值是临时的。因此,当执行操作时,将“弹出”堆栈顶部的值,并将结果值“推”回堆栈中,这个也叫逆波兰表示。

让我们来看下面的例子。

x = 12 + 4 * 5

对于这个简单的式子,x等于4和5乘以12的乘积。

基于栈使用后进先出的原则后,对它的读取就大不相同了。在这种情况下,公式将类似于此示例。

x = 4 5 * 12 +

从调用堆栈中读取的方式为:取值4和5,然后相乘,将值弹出堆栈,然后将结果乘积放在堆栈顶部。然后取乘积和值12以获得x的总和,然后将值弹出堆栈。将总和x推回堆栈顶部。

在这两种情况下,我们为此操作获得的结果均为32。将数据放入调用堆栈的操作使用的是PUSH方法,而删除数据的操作称为POP方法。

将代码从人类可读的ASCII字符编译为机器可读的十六进制字符只是第一步。接下来,机器码必须执行所谓的操作码。操作码或操作代码告诉计算机要执行哪些操作。我们有操作数,操作的数据取决于操作符将执行的操作。整体结构层次如下图所示,最上层是软件层,有编写的合约代码,EVM虚拟机和EVM虚拟机嵌入的Geth或者是Parity客户端,最下层则是具体的硬件层。

如果把EVM虚拟机单独拿出来看,就可以看到EVM的具体体系结构,如下图所示。

其中EVM code就是编写的智能合约代码,PC是程序计数器,指向当前执行的指令,Stack和Memory暂存了合约在运行过程中的数据,storage存储了合约需要持久化的数据,对于EVM来说合约数据是不可变的,storage的数据是需要永久保存在链上的,其他数据都是合约中允许中产生的临时数据。

EVM体系结构图中各个结构是相对独立的,当智能合约的代码开始执行时,EVM体系结构中的各个组件就开始了协同工作,下图显示了EVM的不同部分如何协同工作以使以太坊发挥其神奇作用。

EVM code被翻译成操作码后被推入Stack中执行,在执行操作码时,PC时刻指向下一个要执行的操作码,确保操作码逐个执行,并且在执行的时候会统计执行的Gas消耗,确保执行过程是符合交易Gas限制的,执行过程中需要存储或者加载的临时数据保存在Memory中,当执行的数据需要持久化保存时就可以放入Storage中。

Hex编码

为了更加清晰的展示EVM栈中的数据,Remix等一些调试Solidity的工具都会将栈中的二进制数据进行Hex编码,转换为相对友好的字符串形式以便查看。Hex编码本身也很简单。

Hex编码就是把一个8位的字节数据用两个十六进制数展示出来,编码时,将8位二进制码重新分组成两个4位的字节,其中一个字节的低4位是原字节的高四位,另一个字节的低4位是原数据的低4位,高4位都补0,然后输出这两个字节对应十六进制数字作为编码。Hex编码后的长度是源数据的2倍。Hex编码的编码表为

 0 0     1 1     2 2     3 3
 4 4     5 5     6 6     7 7
 8 8     9 9    10 a    11 b
12 c    13 d    14 e    15 f

比如ASCII码A的Hex编码过程为

ASCII码:A (65)
二进制码:0100 0001
重新分组:0000 0100   0000 0001
十六进制:        4           1
Hex编码:41

智能合约的ABI

智能合约将被部署在区块链上,应用程序二进制接口(Application binary interface)简称ABI,提供一种标准方法来通过网络与智能交互的合同。它允许外部应用程序和合同到合同的交互。开发人员在其应用程序或分布式应用程序(DApp)中使用ABI 来访问智能合约中的方法和功能。EVM虚拟机会在解析合约的字节码时,依赖ABI的定义,从而去识别各个字段位于字节码的什么地方。

这是一个执行托管功能的简单智能合约。

pragma solidity ^0.5.1;
contract Escrow {
    address agent;
    mapping(address => uint256) public deposits;
    modifier onlyAgent() { 
        require(msg.sender == agent);
        _;
    }

    constructor () public {
        agent = msg.sender;
    }

    function deposit(address payee) public onlyAgent payable {
        uint256 amount = msg.value;
        deposits[payee] = deposits[payee] + amount;
    }

    function withdraw(address payable payee) public onlyAgent {
        uint256 payment = deposits[payee];
        deposits[payee] = 0;
        payee.transfer(payment);
    }
}

采用Solc编译后,省略一些无关紧要的细节后就可以看到如下Json格式的数据。

{
	"linkReferences": {},
	"object": "608060405234801  ......  f6c634300050b0032",
	"opcodes": "PUSH1 0x80 PUSH1 0x40 ...... STATICCALL 0xcd 0x2e 0xd4 PUSH31 0x9DAFE57C1B293C8318B01C0A92B343C29F3364736F6C634300050B00320000 ",
	"sourceMap": "25:594:0:-;;;218:57;8:9:- ...... 25:594;;;;;;"
}

object是合约代码编译后生成的字节码(bytecode),表示程序的字节序。操作码(opcodes)是程序的低级可读指令。比如操作码PUSH1的十六进制值也是0x60,操作码PUSH1 0x80 PUSH1 0x40转换成十六进制字节序后是60 80 60 40就是object开头的几位。

由于字节码对于开发者来说非常难以阅读理解,智能合约还增加了合约ABI的Json描述,包含了合约对象数据的名称,类型等,下面就是托管合约Escrow的ABI的Json描述。

[ 
    { 
        "constant": false,
        "inputs": [ 
            { 
                "name": "payee",
                "type": "address" 
            } 
        ],
        "name": "withdraw",
        "outputs": [],
        "payable" : false,
        "stateMutability": "nonpayable",
        "type": "function" 
    },
    { 
        "constant": false,
        "inputs": [ 
            { 
                "name": "payee",
                "type": "address"
            } 
        ],
        "ame": "deposit",
        "utputs": [],
        "payable": true,
        "stateMutability": "payable",
        "type": "function" 
    },
    { 
        "constant": true,
        "inputs": [
            { 
                "name": "",
                "type": "address" 
            } 
        ],
        "name": "deposits",
        "outputs": [ 
            { 
                "name": "",
                "type": "uint256"
            } 
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    { 
        "inputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "constructor"
    } 
]

将智能合约部署到区块链后,EVM将在字节码中执行编译后的代码的操作码。如果函数需要改变区块链的状态,则会向合约的所有者收取gas(以wei 为单位)。这些成本遵循以太坊协议,该协议要求在整个网络上花费计算成本,这是支付给矿工的费用,这些矿工将验证放入区块中的交易。简单的调用函数不需要更改区块链的状态,不会消耗汽油,因此是免费的。一个例子是对智能合约中的储值进行余额查询。

当智能合约由EVM部署并执行时,它现在位于区块链上。它将变得不可变,同时又透明,这意味着它不能直接更改,并且可以被公众看到。