20,000 USDC
View results
Submission Details
Severity: gas
Valid

Lack of proper scaling for a small decimals token, which lead to precision loss and miscalculation of rewards earned

Summary

Lack of proper scaling for a small decimals token in the Staking#update(), which lead to precision loss and miscalculation of rewards earned.

Vulnerability Details

Within the Staking#constructor(), the owner (deployer) can assign any ERC20 token into the _token as a staking token (TKN) like this:
https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Staking.sol#L31-L32

constructor(address _token, address _weth) Ownable(msg.sender) { /// @audit
TKN = IERC20(_token); /// @audit
WETH = IERC20(_weth);
}

Within the Staking#update(), the _ratio would be calculated based on the _diff of WETH and the totalSupply of staking token (TKN) like this:
https://github.com/Cyfrin/2023-07-beedle/blob/main/src/Staking.sol#L68

/// @notice update the global index of earned rewards
function update() public {
uint256 totalSupply = TKN.balanceOf(address(this));
if (totalSupply > 0) {
uint256 _balance = WETH.balanceOf(address(this));
if (_balance > balance) {
uint256 _diff = _balance - balance;
if (_diff > 0) {
uint256 _ratio = _diff * 1e18 / totalSupply; /// @audit
if (_ratio > 0) {
index = index + _ratio;
balance = _balance;
}
}
}
}
}

However, within the _ratio calculation at the line of Staking.sol#L68 above, if the staking token (TKN) would be a token, which is a small decimals token (i.e. USDC, which is 1e6 decimals token), the _ratio (_diff * 1e18 / totalSupply) would be 1e12 times larger than the actual amount.
This lead to the precision loss.

Scenario).

  • Here is a scenario of the calculation of the _ratio when USDC, which is 1e6 decimals token, would be used as a staking token (TKN).

  • totalSupply of TKN would be 1000 USDC.

  • _diff of WETH would be 1 WETH.

uint256 _ratio = _diff * 1e18 / totalSupply;
uint256 _ratio = (1 * 1e18) * 1e18 / (1000 * 1e6);
uint256 _ratio = (1 * 1e18) * 1e18 / (1000 * 1e6);
uint256 _ratio = (1 * 1e36) / (1000 * 1e6);
uint256 _ratio = 1 * 1e27;

The calculation result of _ratio is supposed to be 1 * 1e15.
However, as you can see above, the calculation result of the _ratio (1 * 1e27) was 1e12 times larger than the expected-result (1 * 1e15).

This lead to miscalculation of rewards earned.

Impact

This vulnerability lead to precision loss and miscalculation of rewards earned due to precision loss.

Tools Used

  • Foundry

Recommendations

Within the Staking#update(), consider adding if-block and code to scale the staking token (TKN) properly (if the staking token (TKN) is not 1e18 decimals token) like this:

/// @notice update the global index of earned rewards
function update() public {
uint256 totalSupply = TKN.balanceOf(address(this));
if (totalSupply > 0) {
uint256 _balance = WETH.balanceOf(address(this));
if (_balance > balance) {
uint256 _diff = _balance - balance;
if (_diff > 0) {
+ if (TKN.decimals() < 18) {
+. uint256 _ratio = _diff * 1e18 / totalSupply * (10 ** (18 - TKN.decimals()));
+ }
- uint256 _ratio = _diff * 1e18 / totalSupply;
if (_ratio > 0) {
index = index + _ratio;
balance = _balance;
}
}
}
}
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.