区块链知识系列 - 系统学习EVM(一)
特点
- EVM出于所谓运算速度和效率方面考虑,采用了非主流的256bit整数。
- 不支持浮点数
- 缺乏标准库支持,例如字符串拼接、切割、查找等等都需要开发者自己实现
- 给合约打补丁或是部分升级合约代码在EVM中是完全不可能的
存储
Code
code 部署合约时储存 data 字段也就是合约内容的空间,即专门存储智能合约的二进制源码的空间
Storage
Storage 是一个可以读写修改的持久存储的空间,也是每个合约持久化存储数据的地方。Storage 是一个巨大的 map,一共
2
256
2^{256}
2256 个插槽 (slot),每个插糟有 **32** bytes,合约中的“状态变量”会根据其具体类型分别保存到这些插槽中。
Stack
stack 即所谓的“运行栈",用来保存 EVM 指令的输入和输出数据。可以免费使用,没有 gas 消耗,用来保存函数的局部变量,数量被限制在 16 个。stack 的最大深度为 1024 ,其中每个单元是 32 byte。
Args
args 也叫 calldata,是一段只读的可寻址的保存函数调用参数的空间,与栈不同的地方的是,如果要使用 calldata 里面的数据,必须手动指定偏移量和读取的字节数。
Memory
Memory 一个简单的字节数组,主要是在运行期间存储数据,将参数传递给内部函数。基于 32 byte 进行寻址和扩展。
安全
溢出攻击
EVM 的 safeMath 库不是默认使用,例如开发者对 solidity 的 uint256 做计算的时候,如果最终结果大于 uint256 的最大值,就会产生溢出变为一个很小的数,这样就产生了溢出漏洞。诸如 BEC、SMT 等相关币种都遭受过溢出攻击,带来了极度严重都后果
对策: 利用一些第三方的safemath库来保证这个数字操作的安全
重入攻击
solidity 一大特性是可以调用外部其他合约,但在将 eth 发送给外部地址或者调用外部合约的时候, 需要合约提交外部调用。如果外部地址是恶意合约,攻击者可以在 Fallback 函数中加入恶意代码,当发生转账的时候,就会调用 Fallback 函数执行恶意代码,恶意代码会执行调用合约的有漏洞函数,导致转账重新提交。最严重的重入攻击发生在以太坊早期,即知名的 DAO 漏洞 - (先转账再扣钱,结果转账到一个恶意合约地址)。
对策: 在金额转移之前,一定要先扣钱
非预期函数执行
EVM 没有严格检查函数调用,如果合约地址作为传入参数可控,可能导致非预期行为发生。
权限问题,如果构造函数(0.4.20之前的版本)名和合约名写的不一致,就会被外部或者其他合约所调用,恶意的攻击者可能获取了我们当前智能合约的所有权
对策: 注意编码规范,保证合约名与构造函数名相同。如果现在使用构造函数,我们建议使用constractor来创建
时间戳依赖
时间戳就可以被矿工修改的
可预测的随机数
使用区块变量生成随机数,所有的区块变量都可以被矿工操纵.因为这些区块变量在同一区块上是共用的。攻击者通过其恶意合约调用受害者合约,那么此交易打包在同一区块中,其区块变量是一样的。
对策: 通过Oracle获取随机数
服务攻击
withdraw时,不要直接把币转到用户指定的某个地址(可能是个恶意合约), 而是让用户主动提取自己的押金
不要空投,而是让人主动来领取.
私有数据
不要以为声明为private的变量外部就无法访问到
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract DCA {
uint counter;
uint private age;
constructor(uint _c) {
counter = _c;
age = _c + 2;
}
}
通过api可直接按内存地址访问到private的数据
import web3
url = ""
w3 = web3.Web3(web3.HTTPProvider(url))
w3.eth.get_storage_at(address, 0x0)
#一个uint占256bit, 即32字节 #HexBytes('0x0000000000000000000000000000000000000000000000000000000000000005')
w3.eth.get_storage_at(address, 0x1)
# HexBytes('0x0000000000000000000000000000000000000000000000000000000000000007')
注意
tx.origin和msg.sender区别
msg.sender
(address
): 消息发送方 (当前调用)tx.origin
(address
): 交易发送方(完整调用链上的原始发送方)
用户A -> 合约1 -> 合约2
若在合约2中使用 msg.sender,会得到合约1的地址
若在合约2中使用 tx.origin,会得到用户A,即整个调用链的起点
合约间的调用方式call、delegatecall
调用方式修改storage调用者的msg.sender被调用者的msg.sender执行上下文件call修改被调用者的合约storage交易的发起者地址调用者地址在被调用者里delegatecall修改调用者的合约storage交易的发起者地址调用者地址在调用者里
receive 和 fallback 调用流程
接收以太功能函数
solidity 接收函数 receive 没有参数、没有返回值。
solidity 向合约转账,发送 Eth,就会执行
receive
函数。
如果没有定义接收函数 receive,就会执行
fallback
函数。
fallback函数,即 回退函数,没有名字,没有参数,没有返回值
function(){}
往期精彩回顾:
区块链知识系列
密码学系列
零知识证明系列
共识系列
公链调研系列
BTC系列
以太坊系列
EOS系列
Filecoin系列
联盟链系列
Fabric系列
智能合约系列
Token系列
版权归原作者 搬砖魁首 所有, 如有侵权,请联系我们删除。