The totalSupply is incremented directly without any overflow protection. If the value is sufficiently large, the addition can cause an overflow, resulting in an incorrect and misleading totalSupply
function mint(address to, uint256 value) external auth {
require(to != address(0) && to != address(this), "SDAO/invalid-address");
unchecked {
// Note: safe as the sum of all balances is equal to `totalSupply`;
// there is already an overvlow check below
balanceOf[to] = balanceOf[to] + value;
}
totalSupply = totalSupply + value;
emit Transfer(address(0), to, value);
}
The assumption is that at any given time, the sum of the balances of all accounts should exactly match the totalSupply.
The mint function is responsible for creating new tokens and adding them to a specific account (to). When minting new tokens, the totalSupply is increased by the amount minted.
The comment suggests that since the function directly increases both the balanceOf[to] and the totalSupply by the same amount, and assuming there are no other bugs or vulnerabilities, the sum of all balances should still match the totalSupply after the operation.
Let's break down a simple example to understand the significance of this invariant:
The sum of all balances (balanceOf[Alice] + balanceOf[Bob] + balanceOf[Charlie]) is now 1100, which matches the new totalSupply.
But all that happened will not happen because it's an assumption. Total supply was never checked for overflow, which we can see in the mint function and is highly likely to happen.
Now
totalSupply = 2^256 - 10 (very close to the maximum uint256 value)
balanceOf[Alice] = 500
balanceOf[Bob] = 500
balanceOf[Charlie] = 0
The sum of all balances is balanceOf[Alice] + balanceOf[Bob] + balanceOf[Charlie] = 500 + 500 + 0 = 1000, and totalSupply is 2^256 - 10.
An auth calls the mint function to mint 20 new tokens to Charlie
totalSupply becomes 2^256 - 10 + 20, which causes an overflow. Since 2^256 is the maximum value for a uint256, adding any number greater than 2^256 - totalSupply causes the value to wrap around.
the sum of all balances is balanceOf[Alice] + balanceOf[Bob] + balanceOf[Charlie] = 500 + 500 + 20 = 1020, but totalSupply is 10. This breaks the invariant that the sum of all balances should equal totalSupply.
Impact
Break the assumption that it can't overflow.
Code Snippet
function mint(address to, uint256 value) external auth {
require(to != address(0) && to != address(this), "SDAO/invalid-address");
unchecked {
// Note: safe as the sum of all balances is equal to `totalSupply`;
// there is already an overvlow check below
balanceOf[to] = balanceOf[to] + value;
}
totalSupply = totalSupply + value;
emit Transfer(address(0), to, value);
}
EFCCWEB3
Medium
Incorrect way of checking for overflow opened totalsupply to overflow breaking the invariant
Summary
The mint function in the provided Solidity code lacks proper overflow checks, which can lead to critical issues in the smart contract. It was noted that the was check for overflow, yes there was but for
balanceOf[to] = balanceOf[to] + value;
but not fortotalSupply = totalSupply + value;
https://github.com/sherlock-audit/2024-06-makerdao-endgame/blob/main/endgame-toolkit/src/SDAO.sol#L278Vulnerability Detail
The totalSupply is incremented directly without any overflow protection. If the value is sufficiently large, the addition can cause an overflow, resulting in an incorrect and misleading totalSupply
The assumption is that at any given time, the sum of the balances of all accounts should exactly match the
totalSupply.
The mint function is responsible for creating new tokens and adding them to a specific account (to). When minting new tokens, thetotalSupply
is increased by the amount minted. The comment suggests that since the function directly increases both thebalanceOf[to]
and thetotalSupply
by the same amount, and assuming there are no other bugs or vulnerabilities, the sum of all balances should still match the totalSupply after the operation.Let's break down a simple example to understand the significance of this invariant:
totalSupply =
1000balanceOf[Alice] =
500balanceOf[Bob] =
500The sum of all balances
(balanceOf[Alice] + balanceOf[Bob])
is 1000, which matches totalSupply as said in the inline comment:The contract
mints
100 new tokens to Charlie.totalSupply
is increased by 100balanceOf[Charlie]
is increased by 100totalSupply
= 1100balanceOf[Alice]
= 500balanceOf[Bob]
= 500balanceOf[Charlie]
= 100The sum of all balances
(balanceOf[Alice] + balanceOf[Bob] + balanceOf[Charlie])
is now 1100, which matches the new totalSupply.But all that happened will not happen because it's an assumption. Total supply was never checked for overflow, which we can see in the mint function and is highly likely to happen.
totalSupply
= 2^256 - 10 (very close to the maximum uint256 value)balanceOf[Alice]
= 500balanceOf[Bob]
= 500balanceOf[Charlie]
= 0 The sum of all balances isbalanceOf[Alice] + balanceOf[Bob] + balanceOf[Charlie]
= 500 + 500 + 0 = 1000, andtotalSupply
is 2^256 - 10.An
auth
calls the mint function to mint 20 new tokens to CharlietotalSupply
becomes 2^256 - 10 + 20, which causes an overflow. Since 2^256 is the maximum value for a uint256, adding any number greater than 2^256 -totalSupply
causes the value to wrap around.totalSupply
= 10 (due to overflow)balanceOf[Alice]
= 500balanceOf[Bob]
= 500balanceOf[Charlie]
= 20the sum of all balances is balanceOf[Alice] + balanceOf[Bob] + balanceOf[Charlie] = 500 + 500 + 20 = 1020, but totalSupply is 10. This breaks the invariant that the sum of all balances should equal totalSupply.
Impact
Break the assumption that it can't overflow.
Code Snippet
Tool used
Manual Review
Recommendation