以太坊和比特币最大的区别就是,以太坊拥有智能合约可以写入代码,代码会被放在一个地址中永久保存且不能修改。而编写智能合约的solidity语言作为高级语言,不能直接执行,需要解释器来解释。因此soldity编写的合约在编译后会产生字节流(bytecode),产生的字节流会在以太坊中基于栈的虚拟机EVM来进行解释。EVM的主要功能有:
取值:获取下一条指令(通过IP获取)
译码:对指令进行翻译,将要做何种操作
执行:执行命令
上图为EVM的架构,在EVM虚拟机中存在易失和非易失两个存储区域:
易失区域
堆栈(stack):解释字节码时使用,每个堆栈顶的大小为256比特,堆栈的最大的大小为1024字。其中保存函数的局部变量数量限制在16个,当合约中声明的局部变量超过16个时,编译合约会报错
内存(memory):易失性的可以读写修改的空间,主要是在运行期间存储数据,江参数传递给内部函数。内存可以在字节级别寻址,一次可以读取32字节。
当合约大于这个深度的时候就无法发布
非易失区域
代码(code):写代码的地方(字节码)。该存储区域跟memory不一样,不能直接被EVM执行
存储(storge):非易失性的可以读写修改存储的空间,也是每个合约持久化存储数据的地方,一共有2^256个插槽,一个插槽有32byte
EVM作为一个堆栈虚拟机运行,为了方便进行密码学计算(如 Keccak-256 哈希或 secp256k1 签名)。EVM栈以字为单位进行操作,EVM采用32字节(256比特)的字长,最多可容纳1024个字。如果超过1024的上限,外部函数调用会失败,这种情况下,Solidity会抛出异常。
EVM和JVM一样,也是执行字节码。由于操作码被限制在一个字节以内,所以EVM指令集最多只能容纳256条指令。目前EVM已经定义了约142条指令,还有100多条指令可供以后扩展。这142条指令包括算数运算指令,比较操作指令,按位运算指令,密码学计算指令,栈、memory、storage操作指令,跳转指令,区块、智能合约相关指令等。
主要有POP、PUSHx、DUPx和SWAPx(其中x为指令后跟随的元素)四种指令对栈进行简单操作:
1、POP指令
POP指令(操作码0x50)从栈顶弹出一个元素。
2、PUSHx指令
PUSH系列指令把紧跟在指令后面的x(1~32)字节元素推入栈定。PUSH系列指令一共有32条,PUSH1(操作码0x60)~PUSH32(操作码0x7A)。
3、DUPx指令
DUP系列指令复制从栈顶开始数的第x(1~26)个元素,并把复制后的元素推入栈顶。DUP系列之灵一共有16条,DUP1(操作码0x80)~DUP16(操作吗0x8A)。
4、SWAPx指令
SWAP系列指令把栈顶元素和从栈顶开始数的第x(1~16)+1个元素进行交换。SWAP系列之灵一共有16条,从SWAP1(操作码0x90)~SWAP16(操作码0x9A)。
stack是临时存储,当智能合约运行时有效,当运行结束后回收。如果stack容量过大且未及时收回存储空间会影响整个以太坊的运行,又因为以太坊是图灵完备的,允许循环操作,该机制能预防死循环导致计算和存储资源以及gas的浪费。
防止利用固定成本发起DOS(拒绝服务)攻击,任何对合约的调用从gas费上来说都是相对便宜的,但是根据被调用合约代码的大小(从磁盘读取代码、预处理代码、将数据添加到Merkle证明),合约调用对以太坊节点的影响会不成比例地增加。攻击者就会通过很少的资源给别人造成大量的工作,普通用户就可能会遭受到DOS攻击。
方法 | 优点 | 缺点 |
---|---|---|
通过拆分合约 | 可无限拆分扩容 | 当跨合约调用之后,gas费用会增大 |
使用库 | 库文件不需要声明为内部函数,会在编译过程中直接被添加到合约 | 会在后台使用DELEGATECALL,如果使用公共函数,这些函数事实上将在一个单独的库合约中 |
使用代理 | 只是用调用合约的状态执行另一个合约的函数 | 增加了很多复杂性 |
方法 | 优点 | 缺点 |
---|---|---|
通过压缩减少代码量 | 节省gas | 实现难度较大,优化的空间取决于技术本身对存储空间的理解深度 |
缩短错误信息 | 简化合约,节省gas | 报错信息不完整,解决问题时可能找不到问题所在 |
在优化器中考虑一个低运行值 | 针对每个函数只运行一次的情况进行优化 | 增加运行函数的gas成本 |
方法 | 优点 | 缺点 |
---|---|---|
避免将结构体传递给函数 | 简化合约大小,节省gas | 结构体不清晰,容易混淆 |
声明函数和变量的正确可见性 | 节省gas | 对开发人员要求较高,能准确使用可见性 |
移除修改器 | 再密集使用的情况下,可能会对合约大小产生重大影响 | 函数的验证减少,出错率增加 |