简介
在以太坊上最重要的活动除了转账以外,就是编译、运行智能合约(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 执行堆栈的读取,交换操作。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付