The veRAACToken contract fails to properly clean up voting power decay parameters (slope changes) when users withdraw funds via both normal and emergency withdrawal functions. This leaves corrupted voting power decay calculations in the system, leading to permanently inaccurate total voting power tracking.
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("veRAACToken Emergency Withdraw Slope Cleanup PoC", function () {
let ERC20Mock, raacToken, VeRAACTokenTestHelper, veRAACToken;
let owner, user;
const lockAmount = ethers.utils.parseEther("1000");
const oneYear = 365 * 24 * 60 * 60;
const fourYears = 1460 * 24 * 60 * 60;
beforeEach(async function () {
[owner, user] = await ethers.getSigners();
ERC20Mock = await ethers.getContractFactory("ERC20Mock");
raacToken = await ERC20Mock.deploy("RAAC Token", "RAAC", owner.address, ethers.utils.parseEther("1000000"));
await raacToken.deployed();
await raacToken.transfer(user.address, ethers.utils.parseEther("10000"));
VeRAACTokenTestHelper = await ethers.getContractFactory("VeRAACTokenTestHelper");
veRAACToken = await VeRAACTokenTestHelper.connect(owner).deploy(raacToken.address);
await veRAACToken.deployed();
await raacToken.connect(user).approve(veRAACToken.address, ethers.utils.parseEther("10000"));
});
it("should leave residual slope changes after emergencyWithdraw", async function () {
await veRAACToken.connect(user).lock(lockAmount, fourYears);
const lockPosition = await veRAACToken.getLockPosition(user.address);
const unlockTime = lockPosition.end;
const initialSlope = await veRAACToken.getSlopeChange(unlockTime);
expect(initialSlope).to.be.gt(0);
const EMERGENCY_WITHDRAW_ACTION = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("enableEmergencyWithdraw"));
await veRAACToken.connect(owner).scheduleEmergencyAction(EMERGENCY_WITHDRAW_ACTION);
await ethers.provider.send("evm_increaseTime", [3 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine");
await veRAACToken.connect(owner).enableEmergencyWithdraw();
await veRAACToken.connect(user).emergencyWithdraw();
const residualSlope = await veRAACToken.getSlopeChange(unlockTime);
expect(residualSlope).to.not.eq(0);
console.log("Initial slope:", initialSlope.toString());
console.log("Residual slope after emergencyWithdraw:", residualSlope.toString());
});
});
function withdraw() external nonReentrant {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
int128 oldSlope = _votingState.points[msg.sender].slope;
_votingState.slopeChanges[userLock.end] -= oldSlope;
delete _lockState.locks[msg.sender];
delete _votingState.points[msg.sender];
_boostState.updateBoostPeriod();
}
function emergencyWithdraw() external nonReentrant {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
int128 oldSlope = _votingState.points[msg.sender].slope;
_votingState.slopeChanges[userLock.end] -= oldSlope;
delete _lockState.locks[msg.sender];
delete _votingState.points[msg.sender];
_boostState.updateBoostPeriod();
}