以太坊开发入门 - EVM

Posted by Vincent on Tuesday, July 5, 2022

简介

在以太坊上最重要的活动除了转账以外,就是编译、运行智能合约(Smart Contract)。

智能合约代表了一个以太坊世界里的独立管家。它按照自身代码指示进行以太币的收入,支出活动,也具有一定存储空间可以存储一些数据。

而智能合约就是运行于以太坊虚拟机 (Ethereum Virtual Machine, EVM) 之上.

虚拟机上执行的操作分读操作和写操作,读操作因为不更改区块的状态相对执行较快,写操作则是需要花费以太币的操作,因为它更改了某一个或者数个账户的存储空间。 存储空间是区块链的一部分,是要被全世界的计算机永久同步存储的,这个更改的过程代价昂贵: 例如将一个值从 0 变为非零值需要耗费 20000 单位的gas;修改一个 非0 值需要消耗 5000 单位gas; 将一个值从 非0 赋值为 0 可以回收 15000 单位的gas;读取一个变量值需要 200 gas。而相对比,从内存中读取变量值仅需 3 gas。

这样的环境才是程序员应该拥有的,代码都按操作都得算钱.写几个死循环公司就得破产.赞~

以太坊虚拟机在接收到一条交易包含的调用消息后,会将合约账户的代码和存储区的变量一起装载入内存,执行方法调用后,修改存储区的变量值并持久化保存。合约代码在整个过程中是只读的。

执行资源

EVM 没有寄存器,他的执行宽度都是 256bit ,包含 存储区,堆栈,内存三个存储机构

存储 (Storage)

存储区中的数据会被全世界的节点进行同步和存储,因此代价非常高昂.存储区的数据都是 256bit 的键和值配对存储,如果键不存在 则返回 0,存储区的读写操作都是以 256bit 为单位的,没有更小的操作空间。故而 uint8 和 uint256 在单值存储的情况下占用的空间相同,耗费的 Gas 也相同。 将 unit8 包裹进入 struct 结构体以后可以通过优化来节约空间位置,节省gas支出。

程序执行堆栈 (Stack)

和其他语言一样,程序执行时的存储区,EVM堆栈只有1024层,递归超过1024层则会执行失败.仅有高处的16层可以快速访问.堆栈的宽度也是 256bit,也就是 32byte,一个 word 的长度,任何读写操作都是 256bit 为一个单位进行的。

内存 (Memory)

内存的管理办法也是按照 256bit 为单位进行读取和写入。写入时候也可以选择 8bit 为单位写入,因为内存的宽度是 8bit。合约执行完成后内存中的数据会被清除.和其他语言一样.

合约调用合约

合约执行过程可以调用另外一个合约来完成部分工作,类似调用动态链接库.这里就有一个取舍:如果调用方在编译初期将库合约编译入自己的代码,则可以省去外部调用的步骤,节省每次调用的花费,但相应地自身代码的篇幅会变长。如果调用方在编译初期不将库合约编入自身代码,则保留了灵活性,将来改动库合约不会影响到调用方的代码。在实际应用中,往往将简单的库函数打包进入自己的合约代码,减少对外调用的次数,增加安全性。

输入输出

EVM输入包含

  • 另一个以太坊虚拟机传来的函数调用所携带的数据。
  • 外部账户调用合约账户,所携带的交易数据。

太坊虚拟机是“确定性”的–当输入确定的时候,它的输出无论重复多少次运行也是确定的输出。以太坊生成随机数会有一些容易被攻击的情况.

虚拟的输出也很简单,包含两种输出。

  • 运行过程中修改的区块链账户的存储区。
  • 日志。

每当虚拟机执行完毕后,更改存储区,并将执行结果发送归还给调用者就可以结束生命周期。但是在输出阶段其实有一个容易被忽略的输出对象,就是Log日志。以太坊虚拟机在执行过程中产生的Log日志将会永久记录在以太坊中,组织成交易的收据而存在,供日后查询。现在广泛流行的交易所,就采用监控日志的方式来确定某些虚拟货币的存取情况。

Gas消费

由于运行合约需要消耗Gas,我们可以发送交易时指定Gas上限,超过上限则报错,未使用完的Gas将会退回调用方账户.

有一类情况很特殊,就是Solidity智能合约代码的 assert() 函数与 require() 函数。

在执行时候这两个函数都是做真假条件判断的,但是 assert 函数感情更加强烈,往往判断的都是关键的安全性条件,例如 SafeMath 中利用 assert 函数判断是否位数溢出; 合约取款时判断调用方是否为合约所有者等。

require()函数则较为普通的条件判断,例如判断合约调用者的余额是否足够等。

assert() 函数判断一旦失败,则会扣完剩下所有的 gas 作为惩罚措施; require() 判断失败则仅仅停止目前的执行,收取执行到当前步骤相应的 gas 费用,再撤销发生的变更。

虚拟机指令集

助记符 Gas 花费 移出Stack花费 添入Stack花费 注释
0x00 STOP 0 0 0 停止执行.
0x01 ADD 3 2 1 加法
0x02 MUL 5 2 1 乘法
0x03 SUB 3 2 1 减法
0x04 DIV 5 2 1 整除.
0x06 MOD 5 2 1 Mod除法取余
0x10 LT 3 2 1 小于
0x11 GT 3 2 1 大于
0x14 EQ 3 2 1 是否相等.
0x15 ISZERO 3 1 1 取反
0x16 AND 3 2 1 按位与操作
0x17 OR 3 2 1 按位或操作
0x18 XOR 3 2 1 按位异或操作
0x19 NOT 3 1 1 按位取反操作
0x20 SHA3 FORMULA 2 1 计算Keccak-256 哈希.
0x30 ADDRESS 2 0 1 当前执行账户的地址
0x31 BALANCE 400 1 1 执行账户的余额
0x32 ORIGIN 2 0 1 执行源自哪个账户地址
0x33 CALLER 2 0 1 呼叫方地址
0x35 CALLDATALOAD 3 1 1 获取当前环境输入值
0x36 CALLDATASIZE 2 0 1 获取当前环境输入值大小
0x37 CALLDATACOPY FORMULA 3 0 当前环境输入值拷贝入内存
0x38 CODESIZE 2 0 1 当前环境代码体积大小
0x39 CODECOPY FORMULA 3 0 拷贝当前环境代码进入内存
0x3b EXTCODESIZE 700 1 1 合约代码占用体积
0x3c EXTCODECOPY FORMULA 4 0 合约代码存入内存
0x40 BLOCKHASH 20 1 1 上一出块的哈希值
0x41 COINBASE 2 0 1 挖矿奖励地址.
0x42 TIMESTAMP 2 0 1 Block timestamp.
0x43 NUMBER 2 0 1 Block number.
0x44 DIFFICULTY 2 0 1 Block difficulty.
0x45 GASLIMIT 2 0 1 Block gas limit.
0x50 POP 2 1 0 Stack 抛出最上面的值
0x51 MLOAD 3 1 1 将1word 从内存读取出
0x52 MSTORE 3 2 0 将1 word 存入内存
0x53 MSTORE8 3 2 0 将1byte 存入内存
0x54 SLOAD 200 1 1 存储区取回 1 word 宽度值
0x55 SSTORE FORMULA 1 1 1 word 宽度数据存入存储区.
0x56 JUMP 8 1 0 跳转
0x5a GAS 2 0 1 剩余 gas 值
0x60 – 0x7f PUSH* 3 0 1 将值推送入堆栈
0x80 – 0x8f DUP* 3 “*” “* + 1” 复制堆栈上某个值
0x90 – 0x9f SWAP* 3 “* + 1” “* + 1” 交换堆栈上两个值
0xa0 LOG0 FORMULA 2 0 创建一个log 日志
0xf0 CREATE 32000 3 1 创建合约(贵)
0xf1 CALL FORMULA 7 1 调用智能合约
0xf2 CALLCODE FORMULA 7 1 调用智能合约,重新设置调用方信息
0xf3 RETURN 0 2 0 返回
0xf4 DELEGATECALL FORMULA 6 1 调用智能合约,并允许被叫方更改自己的存储区(危险)
0xff SELFDESTRUCT FORMULA 1 0 摧毁合约,

MSTORE/MSTORE8 :按照 256bit 或者 8bit 执行内存的存入操作。 MLOAD : 按照 256bit 执行内存的读取操作。 SLOAD/STOR:按照 256bit 执行存储的读取、存入操作。 PUSH/SWAP : 按照 256bit 执行堆栈的读取,交换操作。

「真诚赞赏,手留余香」

我的乐与怒

真诚赞赏,手留余香

使用微信扫描二维码完成支付