Unchecked Block with Subtraction
Overview
- Severity: Medium
- Confidence: High
- Affected Versions: All
What is the Unchecked Block with Subtraction vulnerability?
In modern versions of Solidity, arithmetic operations that would underflow or overflow will now revert by default. However, it is possible to obtain the previous behavior where such operations will wrap rather than reverting, by using an unchecked
block. While arithmetic operations inside such blocks may incur lower gas costs, these blocks should be used carefully, as unintended wrapping behavior may lead to program errors if the developer erroneously believes an operation to be safe.
Further reading: Solidity Documentation: Checked or Unchecked Arithmetic
Technical example of vulnerable code
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleTokenWallet {
mapping(address => uint256) public balances;
// Functionality for interfacing with a token standard, e.g. ERC-20, omitted for brevity
// Allow users to deposit tokens to their wallet
function deposit(uint256 _amount) external {
require(_amount > 0, "Deposit amount must be greater than 0");
balances[msg.sender] = balances[msg.sender] + _amount;
// Logic to manage token transfer omitted for brevity
}
// Unsafe withdrawal function with potential for underflow
function withdraw(uint256 _amount) external {
// Dangerous: unchecked subtraction can underflow
unchecked {
balances[msg.sender] = balances[msg.sender] - _amount;
}
// Logic to manage token transfer omitted for brevity
}
}
In the example above, contract SimpleTokenWallet
represents a simplified means of managing user tokens, e.g. ERC-20 tokens. The function withdraw()
uses unchecked arithmetic to reduce a user's token balance when tokens are withdrawn from the wallet, which could potentially underflow and cause the balances
mapping to incorrectly credit an extremely high balance for a user. Depending on how the contract managed tokens overall, this could potentially allow a user to withdraw tokens deposited by others, counter to intended smart contract logic.
Technical example of how to fix the vulnerability
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract SimpleTokenWalletUpdated {
mapping(address => uint256) public balances;
// Functionality for interfacing with a token standard, e.g. ERC-20, omitted for brevity
// Allow users to deposit tokens to their wallet
function deposit(uint256 _amount) external {
require(_amount > 0, "Deposit amount must be greater than 0");
balances[msg.sender] = balances[msg.sender] + _amount;
// Logic to manage token transfer omitted for brevity
}
function withdraw(uint256 _amount) external {
balances[msg.sender] = balances[msg.sender] - _amount;
// Logic to manage token transfer omitted for brevity
}
}
In the updated example above, contract SimpleTokenWalletUpdated
uses a modern version of Solidity where underflow will revert, and does not use unchecked arithmetic in the withdraw()
function. It is important to note that if using an earlier versions of Solidity for some reason, the developer would be required to check the arithmetic themselves to ensure it could not underflow in practice.