以太坊的智能合约Solidity的指令并不多,所以这里不展开合约的指令的介绍,资料有很多。给出一个测试的实例:
pragma solidity ^0.4.25;
contract Example {
function OfTest(uint8 x,uint8 y) public returns (uint8){
return x + y;
}
}
合约非常简单,目的就是为了更清晰的看到跳转的情况,就是一个简单的8位无符号的整型相加。然后一个相同的类型(有溢出的风险)。
上面的合约放到Remix后进行编译,得到如下的类汇编代码:
.code
PUSH 80 contract Example {\n\n func...
PUSH 40 contract Example {\n\n func...
MSTORE contract Example {\n\n func...
CALLVALUE contract Example {\n\n func...
DUP1 olidity ^
ISZERO a
PUSH [tag] 1 a
JUMPI a
PUSH 0 a
DUP1 n
REVERT .25;\ncontrac
tag 1 a
JUMPDEST a
POP contract Example {\n\n func...
PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000 contract Example {\n\n func...
DUP1 contract Example {\n\n func...
PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000 contract Example {\n\n func...
PUSH 0 contract Example {\n\n func...
CODECOPY contract Example {\n\n func...
PUSH 0 contract Example {\n\n func...
RETURN contract Example {\n\n func...
.data
0:
.code
PUSH 80 contract Example {\n\n func...
PUSH 40 contract Example {\n\n func...
MSTORE contract Example {\n\n func...
PUSH 4 contract Example {\n\n func...
CALLDATASIZE contract Example {\n\n func...
LT contract Example {\n\n func...
PUSH [tag] 1 contract Example {\n\n func...
JUMPI contract Example {\n\n func...
PUSH 0 contract Example {\n\n func...
CALLDATALOAD contract Example {\n\n func...
PUSH 100000000000000000000000000000000000000000000000000000000 contract Example {\n\n func...
SWAP1 contract Example {\n\n func...
DIV contract Example {\n\n func...
PUSH FFFFFFFF contract Example {\n\n func...
AND contract Example {\n\n func...
DUP1 contract Example {\n\n func...
PUSH CDEAD58 contract Example {\n\n func...
EQ contract Example {\n\n func...
PUSH [tag] 2 contract Example {\n\n func...
JUMPI contract Example {\n\n func...
tag 1 contract Example {\n\n func...
JUMPDEST contract Example {\n\n func...
PUSH 0 contract Example {\n\n func...
DUP1 contract Example {\n\n func...
REVERT contract Example {\n\n func...
tag 2 function OfTest(uint8 x,uint8 ...
JUMPDEST function OfTest(uint8 x,uint8 ...
CALLVALUE function OfTest(uint8 x,uint8 ...
DUP1 olidity ^
ISZERO a
PUSH [tag] 3 a
JUMPI a
PUSH 0 a
DUP1 n
REVERT .25;\ncontrac
tag 3 a
JUMPDEST a
POP function OfTest(uint8 x,uint8 ...
PUSH [tag] 4 function OfTest(uint8 x,uint8 ...
PUSH 4 function OfTest(uint8 x,uint8 ...
DUP1 function OfTest(uint8 x,uint8 ...
CALLDATASIZE function OfTest(uint8 x,uint8 ...
SUB function OfTest(uint8 x,uint8 ...
DUP2 function OfTest(uint8 x,uint8 ...
ADD function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
DUP1 function OfTest(uint8 x,uint8 ...
DUP1 function OfTest(uint8 x,uint8 ...
CALLDATALOAD function OfTest(uint8 x,uint8 ...
PUSH FF function OfTest(uint8 x,uint8 ...
AND function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
PUSH 20 function OfTest(uint8 x,uint8 ...
ADD function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
SWAP3 function OfTest(uint8 x,uint8 ...
SWAP2 function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
DUP1 function OfTest(uint8 x,uint8 ...
CALLDATALOAD function OfTest(uint8 x,uint8 ...
PUSH FF function OfTest(uint8 x,uint8 ...
AND function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
PUSH 20 function OfTest(uint8 x,uint8 ...
ADD function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
SWAP3 function OfTest(uint8 x,uint8 ...
SWAP2 function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
PUSH [tag] 5 function OfTest(uint8 x,uint8 ...
JUMP function OfTest(uint8 x,uint8 ...
tag 4 function OfTest(uint8 x,uint8 ...
JUMPDEST function OfTest(uint8 x,uint8 ...
PUSH 40 function OfTest(uint8 x,uint8 ...
MLOAD function OfTest(uint8 x,uint8 ...
DUP1 function OfTest(uint8 x,uint8 ...
DUP3 function OfTest(uint8 x,uint8 ...
PUSH FF function OfTest(uint8 x,uint8 ...
AND function OfTest(uint8 x,uint8 ...
PUSH FF function OfTest(uint8 x,uint8 ...
AND function OfTest(uint8 x,uint8 ...
DUP2 function OfTest(uint8 x,uint8 ...
MSTORE function OfTest(uint8 x,uint8 ...
PUSH 20 function OfTest(uint8 x,uint8 ...
ADD function OfTest(uint8 x,uint8 ...
SWAP2 function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
PUSH 40 function OfTest(uint8 x,uint8 ...
MLOAD function OfTest(uint8 x,uint8 ...
DUP1 function OfTest(uint8 x,uint8 ...
SWAP2 function OfTest(uint8 x,uint8 ...
SUB function OfTest(uint8 x,uint8 ...
SWAP1 function OfTest(uint8 x,uint8 ...
RETURN function OfTest(uint8 x,uint8 ...
tag 5 function OfTest(uint8 x,uint8 ...
JUMPDEST function OfTest(uint8 x,uint8 ...
PUSH 0 uint8
DUP2 y
DUP4 x
ADD x + y
SWAP1 return x + y
POP return x + y
SWAP3 function OfTest(uint8 x,uint8 ...
SWAP2 function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
POP function OfTest(uint8 x,uint8 ...
JUMP [out] function OfTest(uint8 x,uint8 ...
.data
在汇编的代码里可以清楚的看到来回跳转的过程,通过tag来标示一段代码的过程,这和c和c++中的汇编代码保持了一致。 智能的汇编代码的开始的80和40是指空闲的内存地址和内存地址的指针的位置。这些汇编代码和下面的opcodes以及执行器执行的代码过程是保持一致的。
其ABI和相关的函数接口如下:
ABI:
[
{
"constant": false,
"inputs": [
{
"name": "x",
"type": "uint8"
},
{
"name": "y",
"type": "uint8"
}
],
"name": "OfTest",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]
FUNCTION:
{
"0cdead58": "OfTest(uint8,uint8)"
}
在Go版本的以太坊的源码中,提供了ABI的处理包,可以通过Pack等相关的函数来动态取得整个函数调用的二进制数据。不过其有一个前提,必须得提供ABI的字符串,也就是上面的这串字符串。需要说明的是,在GO版本的ABI字符串处理中,不能使用普通的JOSN处理库,无法解析,只能使用本身提供的相关的解析代码,有兴趣可以看一下源码中的abi包中的相关部分。
通过编译可以得到相关的二进制的字节码RUNTIMECODE(注意和BYTECODE的不同):
"608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630cdead58146044575b600080fd5b348015604f57600080fd5b50607c
600480360381019080803560ff169060200190929190803560ff1690602001909291905050506098565b604051808260ff1660ff16815260200191505060405180910390f35b60008183019050929150505600a
165627a7a723058203dac5a701e6b6285a27cce8bfe10b3c06af990d564e269625b825172db61e9b40029",
"opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT ..."
将上面的字节部分拷贝出来就可以把这个合约部署到链上了。下面的“opcodes”是对应的翻译好的编译指令,而在执行器中就是按照这个顺序来进行执行的,下面分析一下执行的流程(注意和上面的汇编代码的指令流程相匹配):
000 PUSH1 80 ;压入栈
002 PUSH1 40
004 MSTORE ;开辟内存空间
005 PUSH1 04
007 CALLDATASIZE
008 LT ;比较4和CALLDATASIZE是否后者小
009 PUSH1 3f ;压入回滚地址
011 JUMPI ;上面的比较如果小,则跳到回滚地址
012 PUSH1 00
014 CALLDATALOAD ;从00处开始取得数据
015 PUSH29 0100000000000000000000000000000000000000000000000000000000
045 SWAP1
046 DIV ;DIV和上面的PUSH29表示左移28字节,一共32字节(256位),余下四字节为函数哈希
047 PUSH4 ffffffff
052 AND ; 保留低四字节
053 DUP1
054 PUSH4 0cdead58 ;OfTest函数的keccak256
059 EQ ;比较是否相等
060 PUSH1 44
062 JUMPI ;相等则到到44处,注意十六进制:0x44
063 JUMPDEST ;空指令
064 PUSH1 00
066 DUP1
067 REVERT ;回滚,说明函数匹配没有成功
068 JUMPDEST ;匹配成功进行下列操作
069 CALLVALUE ;取出交易额
070 DUP1
071 ISZERO ;是否为0
072 PUSH1 4f
074 JUMPI ;不为0跳到4f
075 PUSH1 00
077 DUP1
078 REVERT ;回滚并退出
079 JUMPDEST
080 POP
081 PUSH1 7c
083 PUSH1 04
085 DUP1 ;复制栈顶
086 CALLDATASIZE
087 SUB
088 DUP2
089 ADD
090 SWAP1
091 DUP1
092 DUP1
093 CALLDATALOAD ;加载数据
094 PUSH1 ff
096 AND
097 SWAP1
098 PUSH1 20
100 ADD
101 SWAP1
102 SWAP3
103 SWAP2
104 SWAP1
105 DUP1
106 CALLDATALOAD
107 PUSH1 ff
109 AND
110 SWAP1
111 PUSH1 20
113 ADD
114 SWAP1
115 SWAP3
116 SWAP2
117 SWAP1
118 POP
119 POP
120 POP
121 PUSH1 98
123 JUMP ;跳到0x98,执行函数
124 JUMPDEST ;函数调用完成后用来处理堆栈
125 PUSH1 40
127 MLOAD
128 DUP1
129 DUP3
130 PUSH1 ff
132 AND ;uint8,只取八位(ff),溢出的风险就在这。它使用AND截取
133 PUSH1 ff
135 AND
136 DUP2
137 MSTORE
138 PUSH1 20
140 ADD
141 SWAP2
142 POP
143 POP
144 PUSH1 40
146 MLOAD
147 DUP1
148 SWAP2
149 SUB
150 SWAP1
151 RETURN ;返回
152 JUMPDEST
153 PUSH1 00 ;此处开始函数内部执行即x+y
155 DUP2
156 DUP4
157 ADD
158 SWAP1
159 POP
160 SWAP3
161 SWAP2
162 POP
163 POP
164 JUMP ;此处跳转到124处开始处理返回堆栈
165 STOP
-----------------END-----------------------
166 LOG1
167 PUSH6 627a7a723058
174 SHA3
175 PUSH7 629659995f4ebe
183 INVALID
184 PUSH10 b64b24559e73396aa826
195 INVALID
196 INVALID
197 CALLDATASIZE
198 SWAP8
199 INVALID
200 PUSH22 61c781c1e8fa0029
上面的代码注释很多,就不再详述,只说一下CALLDATALOAD这个指令,看一下源码中如何实现的:
func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
stack.push(interpreter.intPool.get().SetBytes(getDataBig(contract.Input, stack.pop(), big32)))
return nil, nil
}
在虚拟机执行调用函数前,会把对应的参数存储到contract.Input,而此时则把它加载到堆栈中。把执行过程的代码和上面的汇编代码对比,可以发现调用的过程和汇编实现的过程完全相同的,只是在对比时,注意代码的原始过程和执行流程是不同的(执行过程中会有JUMP)。但是,整体的静态状态,是没有区别的。
以太坊的Solidity会不会退出历史舞台,得看EWASM的开发进度和人们的接受程度。只要弄明白了Solidity的虚拟机流程,那么在EWASM中也不会有革命的变化,只不过是换了一种比较安全和普及的手段罢了,设计的思想是没有太大的变化的。