In OpenZeppelin ERC20 implementation, there is a _transfer method:
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
}
Why do they use uncheked arithmetic for decreasing the balance? I know that in case of unchecked, 2-3 will return 2**256-1 and not case an exception. But why do we need this?
unchecked
produces smaller bytecode compared to regular arthimetic operations, as it doesn't contain the underflow/overflow validation.
So if you want to have a custom error message in case overflow would occur, this code costs less gas to run
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
// no validation here as it's already validated in the `require()` condition
_balances[sender] = senderBalance - amount;
}
compared to this one
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
// redundant validation here
_balances[sender] = senderBalance - amount;
Without the custom message, this would be the cheapest, but still safe, option:
// contains the check and fails without custom message in case of underflow
_balances[sender] -= amount;
And this would be even cheaper compared to the previous one. But it's unsafe as it doesn't check for undeflow:
unchecked {
// UNSAFE, DO NOT USE
_balances[sender] -= amount;
}