整数溢出检查 unchecked
本章讲解在 Solidity 中,使用 unchecked 关闭整数溢出检查的方法。
Solidity 中的 unchecked 用于关闭作用域内的整数溢出检查。
使用 unchecked 关键字,有两个作用:
- 可以提高某些计算的执行效率。
- 可以节省
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。
上面的代码,如果不使用 unchecked,gas 消耗为 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。
比如:在 Openzepplin 的 ERC20 合约中,底层的转账函数就使用了 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; } } // ... }