整数溢出检查 unchecked

本章讲解在 Solidity 中,使用 unchecked 关闭整数溢出检查的方法。

官网binschool.app
推特@BinSchool    DiscordBinDAO   微信:bkra50 

Solidity 中的 unchecked 用于关闭作用域内的整数溢出检查。

使用 unchecked 关键字,有两个作用:

  1. 可以提高某些计算的执行效率。
  2. 可以节省 gas 消耗。

unchecked 的用法,可以参照下面的合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Unchecked {
  function sum() external pure returns (uint) {
    uint result = 0;
    for(uint i=0; i<1000; i++) {
      unchecked {
        result += i;
      }
    }
    return result;
  }
}

unchecked 对于大括号 {} 中的代码计算,不再检查整数是否溢出,从而提高计算效率,节省 gas

上面的代码,如果不使用 uncheckedgas 消耗为 381360

如果使用了 unchecked,那么 gas 消耗为 193360,节省了差不多一半的 gas 消耗。

unchecked 通常在大量计算的情况下使用,效果才会明显。如果只是执行一两次的计算,就无需使用了。

需要注意是,unchecked 不可以滥用,因为它屏蔽掉了整数溢出检查,会带来一定的安全风险。未经检查的整数运算可能会导致不可预料的结果,甚至可能导致合约漏洞或攻击。因此,在使用 unchecked 时,需要确保你完全理解代码的上下文,并能够确保在这些情况下不会发生溢出。

下面是一个整数溢出的示例合约,我们可以使用 unchecked 试一下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UncheckedOverflow {
  function sum() external pure returns (uint) {
    uint result = type(uint).max - 1;
    result += 100;
    return result;
  }
}

这个合约中没有使用 unchecked,调用这个合约的 sum 函数,结果是交易被 revert

Solidity 中,对整数溢出进行检查是默认行为。这意味着,如果你尝试执行可能导致溢出的整数操作,Solidity 会抛出异常并中止函数执行。

如果在这个合约中,加入 unchecked,关闭整数溢出检查:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract UncheckedOverflow {
  function sum() external pure returns (uint) {
    uint result = type(uint).max - 1;
    unchecked {
      result += 100;
    }
    return result;
  }
}

这个合约中使用了 unchecked,调用这个合约的 sum 函数,结果正常,返回值为 98。但这并不是我们期望的结果。

所以,在一些场景下,你如果确切地知道不会产生溢出,就可以使用 unchecked 关键字来关闭这些检查,但要时刻注意风险。

使用范例

在一些知名合约中,经常会看到 unchecked 的使用,它的作用就是节省 gas。

比如:在 OpenzepplinERC20 合约中,底层的转账函数就使用了 unchecked

function _update(address from, address to, uint256 value) internal virtual {
    if (from == address(0)) {
        _totalSupply += value;
    } else {
        uint256 fromBalance = _balances[from];
        if (fromBalance < value) {
            revert ERC20InsufficientBalance(from, fromBalance, value);
        }
        unchecked {
            // Overflow not possible: value <= fromBalance <= totalSupply.
            _balances[from] = fromBalance - value;
        }
    }
    // ...
}