整数溢出检查 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; } } // ... }