Skip to content

Latest commit

 

History

History
340 lines (331 loc) · 12.1 KB

智能合约的bytecode分析.md

File metadata and controls

340 lines (331 loc) · 12.1 KB

以太坊智能合约的bytecode分析

一、测试合约

以太坊的智能合约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和相关的函数接口如下:

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中也不会有革命的变化,只不过是换了一种比较安全和普及的手段罢了,设计的思想是没有太大的变化的。