The function MasterchefV2::claim can lead to an arithmetic underflow or overflow error.
Vulnerability Detail
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { Test, console } from "lib/forge-std/src/Test.sol";
import { SafeERC20, IERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import { MasterChef } from "src/MasterchefV2.sol";
import { ILum } from "src/interfaces/ILum.sol";
import { IVoter } from "src/interfaces/IVoter.sol";
import { IRewarderFactory, IBaseRewarder } from "src/interfaces/IRewarderFactory.sol";
import { ERC20Mock } from "test/mocks/ERC20.sol";
import { IMasterChefRewarder } from "src/interfaces/IMasterChefRewarder.sol";
import { VoterMock } from "test/mocks/VoterMock.sol";
import {Constants} from "src/libraries/Constants.sol";
import { Math } from "src/libraries/Math.sol";
import { OwnableUpgradeable } from "openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol";
import "../src/transparent/TransparentUpgradeableProxy2Step.sol";
import { MasterChefRewarder } from "src/rewarders/MasterChefRewarder.sol";
import { RewarderFactory } from "src/rewarders/RewarderFactory.sol";
import { LumMock } from "src/mocks/LumMock.sol";
import "src/interfaces/IRewarder.sol";
contract MasterchefV2Test is Test {
using SafeERC20 for IERC20;
using Math for uint256;
MasterChef masterchef;
ERC20Mock token;
ILum lum;
VoterMock _voterMock;
RewarderFactory rewarderFactory;
IMasterChefRewarder extraRewarder;
rewarderFactory = new RewarderFactory();
IERC20 extraRewardToken = new ERC20Mock("Extra Reward Token", "ERT", 18);
_voterMock = new VoterMock();
masterchef = new MasterChef(
lum,
_voterMock,
rewarderFactory,
address(this),
treasuryShare
);
/**
@notice Try a DOS a ttack on the claim function
@dev Compare gaz cost between first 30 pools and second 30 pools
@dev note : Following error :
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] testDenialOfServiceOnClaimFunction() (gas: 33784860)
*/
function testDenialOfServiceOnClaimFunction() public {
vm.txGasPrice(1);
// Creation des 30 premières pools
uint256 firstPools = 30;
uint256[] memory pids = new uint256;
for (uint256 i; i < firstPools; ++i) {
masterchef.add(IERC20(address(new ERC20Mock("Token", "TKN", 18))), IMasterChefRewarder(address(0)));
pids[i] = i;
}
uint256 gasStart = gasleft();
masterchef.claim(pids);
uint256 gasEnd = gasleft();
uint256 gasUsedFirst = (gasStart - gasEnd) * tx.gasprice;
console.log("Gas used for first 30 pools: ", gasUsedFirst);
// Creation des 30 secondes pools
for (uint256 i; i < firstPools; ++i) {
masterchef.add(IERC20(address(new ERC20Mock("Token", "TKN", 18))), IMasterChefRewarder(address(0)));
pids[i] = (i + firstPools);
}
uint256 gasStartSec = gasleft();
masterchef.claim(pids);
uint256 gasEndSec = gasleft();
uint256 gasUsedSec = (gasStartSec - gasEndSec) * tx.gasprice;
console.log("Gas used for second 30 pools: ", gasUsedSec);
// Difference gas cost between the first and second batch
console.log("Gas difference: ", gasUsedSec - gasUsedFirst);
assert(gasUsedFirst < gasUsedSec);
}
}
Lead to the following error:
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)]
Impact
Medium. A core functionality is broken but no loss of funds.
Pedro
Medium
Arithmetic underflow or overflow
Summary
The function
MasterchefV2::claim
can lead to an arithmetic underflow or overflow error.Vulnerability Detail
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20;
import { Test, console } from "lib/forge-std/src/Test.sol"; import { SafeERC20, IERC20 } from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import { MasterChef } from "src/MasterchefV2.sol"; import { ILum } from "src/interfaces/ILum.sol"; import { IVoter } from "src/interfaces/IVoter.sol"; import { IRewarderFactory, IBaseRewarder } from "src/interfaces/IRewarderFactory.sol"; import { ERC20Mock } from "test/mocks/ERC20.sol"; import { IMasterChefRewarder } from "src/interfaces/IMasterChefRewarder.sol"; import { VoterMock } from "test/mocks/VoterMock.sol"; import {Constants} from "src/libraries/Constants.sol"; import { Math } from "src/libraries/Math.sol"; import { OwnableUpgradeable } from "openzeppelin-contracts-upgradeable/access/OwnableUpgradeable.sol"; import "../src/transparent/TransparentUpgradeableProxy2Step.sol";
import { MasterChefRewarder } from "src/rewarders/MasterChefRewarder.sol"; import { RewarderFactory } from "src/rewarders/RewarderFactory.sol"; import { LumMock } from "src/mocks/LumMock.sol"; import "src/interfaces/IRewarder.sol";
contract MasterchefV2Test is Test { using SafeERC20 for IERC20; using Math for uint256; MasterChef masterchef; ERC20Mock token; ILum lum; VoterMock _voterMock; RewarderFactory rewarderFactory; IMasterChefRewarder extraRewarder;
uint256 treasuryShare = 1 * 10 ** 7; uint96 private _lumPerSecond; bool private _mintLUM;
IERC20 tokenA; IERC20 tokenB; IERC20 tokenC; IERC20 tokenD; IERC20 rewardToken; address payable immutable OWNER = payable(makeAddr("owner")); address payable immutable ALICE = payable(makeAddr("alice")); address payable immutable BOB = payable(makeAddr("bob")); address treasury = makeAddr("treasury");
function setUp() public { tokenA = IERC20(new ERC20Mock("Token A", "TA", 18)); tokenB = IERC20(new ERC20Mock("Token B", "TB", 18)); tokenC = IERC20(new ERC20Mock("Token C", "TC", 18)); tokenD = IERC20(new ERC20Mock("Token D", "TD", 18)); lum = ILum(new LumMock(address(this), address(this)));
/**
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] testDenialOfServiceOnClaimFunction() (gas: 33784860) */ function testDenialOfServiceOnClaimFunction() public { vm.txGasPrice(1); // Creation des 30 premières pools uint256 firstPools = 30; uint256[] memory pids = new uint256; for (uint256 i; i < firstPools; ++i) { masterchef.add(IERC20(address(new ERC20Mock("Token", "TKN", 18))), IMasterChefRewarder(address(0))); pids[i] = i; }
uint256 gasStart = gasleft(); masterchef.claim(pids); uint256 gasEnd = gasleft(); uint256 gasUsedFirst = (gasStart - gasEnd) * tx.gasprice; console.log("Gas used for first 30 pools: ", gasUsedFirst);
// Creation des 30 secondes pools for (uint256 i; i < firstPools; ++i) { masterchef.add(IERC20(address(new ERC20Mock("Token", "TKN", 18))), IMasterChefRewarder(address(0))); pids[i] = (i + firstPools); } uint256 gasStartSec = gasleft(); masterchef.claim(pids); uint256 gasEndSec = gasleft(); uint256 gasUsedSec = (gasStartSec - gasEndSec) * tx.gasprice; console.log("Gas used for second 30 pools: ", gasUsedSec);
// Difference gas cost between the first and second batch console.log("Gas difference: ", gasUsedSec - gasUsedFirst); assert(gasUsedFirst < gasUsedSec); }
}
Lead to the following error: [FAIL. Reason: panic: arithmetic underflow or overflow (0x11)]
Impact
Medium. A core functionality is broken but no loss of funds.
Code Snippet
https://github.com/sherlock-audit/2024-06-magicsea/blob/main/magicsea-staking/src/MasterchefV2.sol#L316
Tool used
Foundry IDE Manual Review
Recommendation