SolidityBytecode文件结构
智能合约Demo
pragma solidity ^0.4.25;
contract Demo{
uint public value1 = 0;
uint public value2 = 0;
function A(uint a) public returns(uint){
value1 += a;
return value1;
}
function B(uint b) public{
value2 += A(b);
}
}
上面是一个智能合约DEMO,用REMIX部署DEMO合约,获得Bytecode文件。
点击右侧Details ,如下图:
Bytecode文件
Bytecode由两部分构成。第一部分的.code包含了一些smart contract初始化的代码,比如构造函数,state variable(全局变量)的赋值等操作。区块链上,这些都是EOA在部署合约时就执行完成的,在区块链浏览器,是无法看到这部分的代码的。从.data开始,是smart contract的runtime bytecode,也就是在区块链上保存的合约的bytecode。想要获得该部分的bytecode,可以安装solidity,通过命令 solc --bin-runtime demo.sol获得。
Remix的结构有点不太一样,是由若干个tag组成的,每个tag由若干个基本块组成。以JUMPDEST或者结束指令(RETURN,REVERT,STOP)划分。.code部分是Bytecode的入口,这部分的指令包含了所有能够被外部调用的函数的函数签名和跳转pc值。
上面的5个框分别是该合约的5个跳转函数。可能会奇怪合约就2个函数,为何会有5个可跳转函数。这5个跳转函数分别是:1. fallback(回退函数),2个public全局变量,2个public函数。
首先解释一下fallback函数,在EVM中,fallback函数是唯一一个未命名的函数,可以发现其他4个框前面都有一个函数签名,如第二个框的3033413B,只有fallback function没有函数签名。因此如果我们调用了一个合约中没有的函数,没有一个函数签名能满足,接下来的四个框都不会满足跳转条件,因此会通过fall to的形式执行tag1,tag1也就是fallback函数的开始位置。
接下来说一下什么是函数签名。函数签名是一个4byte的hash值,用来唯一标识smart contract中的函数。它是通过sha3(“functionName(type1, type2)”) 或者 keccak256(“functionName(type1, type2)”),取前4bytes得到的。也就是说该函数签名只与函数名,函数类型有关,与函数内容无关。
总结一下.code部分,该部分包含了合约能调用的所有函数的跳转地址,从上图中提现就是tag1-5。 tag 1-5分别是5个函数的起始位置。
Bytecode执行流程
下面用函数B为例,解释一下EVM的bytecode是如何跳转的:
- 要调用函数B,首先EVM会接受到函数签名(DAC0EB07),在.code部分中,跳转到tag 5
- tag 5是函数B的开始部分,tag 5中有一个JUMPI,假设跳转条件满足,EVM会跳转到tag 15,如果不满足条件,则会执行PUSH, DUP1, REVERT。 REVERT是终止指令。tag 15上通常是用来判断一个函数是否是payable的。比如CALLVALUE指令会得到transacation是否发了Ether,如果发了ether,ISZERO的结果就会是false,因此不会执行跳转。
- 执行tag 15, 执行到最后有一个JUMP指令,会从EVM stack读出一个值, 上一个push到stack的值是tag 17,因此跳转到tag 17
- 执行tag 17,同tag 15,tag17最后的tag 15会使pc跳转到tag14(tag 14也就是函数A的函数体部分)
- 执行tag 14,执行到最后有一个JUMP指令,这时JUMP指令读到的是tag 17中push的tag 20
- 执行tag 20, tag20最后的JUMP指令,执行的是tag15中的push tag 16, 因此会跳转到tag 16。
- 执行tag 16,执行到stop指令,程序终止。