Core Contracts

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

The veRAAC token can be DOSed if the current boost period passed

Summary

If the boost period passes a safety check in the TimeWeightedAverage, it can cause calls to the lock or increase functions to revert, leading to a DoS (Denial of Service) attack on the veRAAC token.

Vulnerability Details

Example Scenario:

Bob wants to lock an amount of RAAC tokens in the veRAAC contract to participate in governance. Bob is the first user to lock his RAAC tokens.

When a liquidity provider locks RAAC tokens or increases their locked amount, the protocol updates the boost state by calling the internal function _updateBoostState (L-584).

function _updateBoostState(address user, uint256 newAmount) internal {
...code
_boostState.updateBoostPeriod();
}

The function updateBoostPeriod (L-137) in the BoostCalculatorLibrary updates the time-weighted period of the boost state:

• If no period exists, it creates one.

• If a period has ended, it creates a new one.

• Otherwise, it calls updateValue.

function updateBoostPeriod(
BoostState storage state
) internal {
if (state.boostWindow == 0) revert InvalidBoostWindow();
if (state.maxBoost < state.minBoost) revert InvalidBoostBounds();
uint256 currentTime = block.timestamp;
uint256 periodStart = state.boostPeriod.startTime;
// If no period exists, create initial period starting from current block
if(periodStart > 0) {
// If current period has ended, create new period
if (currentTime >= periodStart + state.boostWindow) {
TimeWeightedAverage.createPeriod(
state.boostPeriod,
currentTime,
state.boostWindow,
state.votingPower,
state.maxBoost
);
return;
}
// Update existing period
state.boostPeriod.updateValue(state.votingPower, currentTime);
return;
}
// If no period exists, create initial period starting from current block
TimeWeightedAverage.createPeriod(
state.boostPeriod,
currentTime,
state.boostWindow,
state.votingPower,
state.maxBoost
);
}

Bob’s First Lock:

Since Bob is the first user, a new boost period is created, and totalDuration is initially set to 7 days (boostWindow), as seen here:

//In the BoostCalculator library
TimeWeightedAverage.createPeriod(
state.boostPeriod,
currentTime,
state.boostWindow,
state.votingPower,
state.maxBoost
);
//In the TimeWeightedAverage Library L-102
function createPeriod(
Period storage self,
uint256 startTime,
uint256 duration,
uint256 initialValue,
uint256 weight
) internal {
...code
self.totalDuration = duration;
...code
}

Bob Increases His Locked Amount (After 3 Days):

• The boost state is updated again.

• This time, the function updateValue is called instead of creating a new period.

• The function updateValue (L-134) in the TimeWeightedLibrary updates the value and adds the duration between lastUpdateTime and the current timestamp.

function updateValue(
Period storage self,
uint256 newValue,
uint256 timestamp
) internal {
if (timestamp < self.startTime || timestamp > self.endTime) {
revert InvalidTime();
}
unchecked {
uint256 duration = timestamp - self.lastUpdateTime;
if (duration > 0) {
uint256 timeWeightedValue = self.value * duration;
if (timeWeightedValue / duration != self.value) revert ValueOverflow();
self.weightedSum += timeWeightedValue;
self.totalDuration += duration;
..code
self.lastUpdateTime = timestamp;
}

• The new totalDuration becomes 7 days + 3 days = 10 days.

Alice Tries to Lock Her RAAC Tokens (After 5 More Days):

• Alice tries to lock her tokens 8 days after Bob’s initial period was created.

• The function createPeriod is called again because the elapsed time (8 days) exceeds the boostWindow (7 days).

• However, this call will revert due to the following check at the beginning of createPeriod in TimeWeightedLibrary:

//In the updateBoostPeriod function
if (currentTime >= periodStart + state.boostWindow) {
TimeWeightedAverage.createPeriod(
state.boostPeriod,
currentTime,
state.boostWindow,
state.votingPower,
state.maxBoost
);
return;
}
function createPeriod(
Period storage self,
uint256 startTime,
uint256 duration,
uint256 initialValue,
uint256 weight
) internal {
if (self.startTime != 0 && startTime < self.startTime + self.totalDuration) {
revert PeriodNotElapsed();
}

startTime = timestamp when Bob created the period + 8 days,

self.startTime + self.totalDuration =timestamp when Bob created the period + 10 days,

Even though the period should have elapsed, the call fails, preventing Alice from locking her tokens.

In order to run this coded POC you will have to add foundry to the project to do so you will have to follow 4 steps :

run npm install --save-dev @nomicfoundation/hardhat-foundry

add require("@nomicfoundation/hardhat-foundry"); to the hardhat.config file

run npx hardhat init-foundry

remove The ReserveLibraryMock from the test folder because this file countain errors that will make the compilation imposible for foundry.

you can now copy paste this code in a solidity file in the test folder and

run forge test --mt test_lockBug -vv.

// SPDX-License-Identifier: GPL-2.0
pragma solidity ^0.8.0;
import {Test, console,stdError} from "forge-std/Test.sol";
import {crvUSDToken} from "contracts/mocks/core/tokens/crvUSDToken.sol";
import {RAACToken} from "contracts/core/tokens/RAACToken.sol";
import {veRAACToken} from "contracts/core/tokens/veRAACToken.sol";
import {RAACReleaseOrchestrator} from "contracts/core/minters/RAACReleaseOrchestrator/RAACReleaseOrchestrator.sol";
import {RAACHousePrices} from "contracts/core/primitives/RAACHousePrices.sol";
import {RAACNFT} from "contracts/core/tokens/RAACNFT.sol";
import {RToken} from "contracts/core/tokens/RToken.sol";
import {DebtToken} from "contracts/core/tokens/DebtToken.sol";
import {LendingPool, ILendingPool} from "contracts/core/pools/LendingPool/LendingPool.sol";
import {Treasury} from "contracts/core/collectors/Treasury.sol";
import {FeeCollector} from "contracts/core/collectors/FeeCollector.sol";
import {DEToken} from "contracts/core/tokens/DEToken.sol";
import {StabilityPool} from "contracts/core/pools/StabilityPool/StabilityPool.sol";
import {RAACMinter} from "contracts/core/minters/RAACMinter/RAACMinter.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {Auction} from "contracts/zeno/Auction.sol";
import {ZENO} from "contracts/zeno/ZENO.sol";
import {ERC20Mock} from "contracts/mocks/core/tokens/ERC20Mock.sol";
import {TimelockController} from "contracts/core/governance/proposals//TimelockController.sol";
import {Governance} from "contracts/core/governance/proposals//Governance.sol";
import {RAACGauge} from "contracts/core/governance/gauges/RAACGauge.sol";
import {RWAGauge} from "contracts/core/governance/gauges/RWAGauge.sol";
import {BoostController} from "contracts/core/governance/boost/BoostController.sol";
import {GaugeController} from "contracts/core/governance/gauges/GaugeController.sol";
import {IGaugeController} from "contracts/interfaces/core/governance/gauges/IGaugeController.sol";
import {IGovernance} from "contracts/interfaces/core/governance/proposals/IGovernance.sol";
import {ReserveLibrary} from "contracts/libraries/pools/ReserveLibrary.sol";
import {LockManager} from "contracts/libraries/governance/LockManager.sol";
import {VotingPowerLib} from "contracts/libraries/governance/VotingPowerLib.sol";
import {TimeWeightedAverage} from "contracts/libraries/math/TimeWeightedAverage.sol";
import "contracts/libraries/math/WadRayMath.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract CodedPOC is Test{
using WadRayMath for uint256;
address constant BOB = address(0x10000);
address constant ALICE = address(0x20000);
address constant CHARLIE = address(0x30000);
address[] internal users;
address owner = makeAddr("Owner");
address[] public proposers = [makeAddr("Proposers")];
address[] public executors = [makeAddr("Executors")];
// protocol contracts
crvUSDToken crvUSD;
RAACToken raac;
veRAACToken veRAAC;
RAACReleaseOrchestrator raacReleaseOrchestrator;
RAACHousePrices raacHousePrices;
RAACNFT raacNFT;
RToken rToken;
DebtToken debtToken;
LendingPool lendingPool;
Treasury treasury;
FeeCollector feeCollector;
DEToken deToken;
StabilityPool stabilityPool;
RAACMinter raacMinter;
Treasury repairFund;
//ghost variables;
CurveMock curveMock;
ERC20Mock usdc;
ERC20Mock rewardToken;
ZENO currentZeno;
ZENO[] zenos;
Auction currentAuction;
Auction[] auctions;
TimelockController timelock;
Governance governance;
GaugeController gaugeController;
RAACGauge raacGauge;
RWAGauge rwaGauge;
BoostController boostController;
uint256[] proposalIds;
address[] gauges;
uint256 timestampBefore;
function setUp() public {
vm.warp(1524785992);
vm.roll(4370000);
users = [BOB, ALICE, CHARLIE];
vm.label(BOB, "Bob");
vm.label(ALICE, "Alice");
vm.label(CHARLIE, "Charlie");
vm.label(owner, "Owner");
vm.label(address(this), "Test");
crvUSD = new crvUSDToken(owner);
curveMock = new CurveMock(address(crvUSD));
vm.label(address(curveMock), "CurveMock");
vm.prank(owner);
crvUSD.setMinter(address(this));
raac = new RAACToken(owner, 200, 50);
vm.label(address(raac), "RAAC");
veRAAC = new veRAACToken(address(raac));
vm.label(address(veRAAC), "veRAAC");
raacReleaseOrchestrator = new RAACReleaseOrchestrator(owner);
vm.label(address(raacReleaseOrchestrator), "RAACReleaseOrchestrator");
raacHousePrices = new RAACHousePrices(owner);
raacNFT = new RAACNFT(address(crvUSD), address(raacHousePrices), owner);
rToken = new RToken("RToken", "RT", owner, address(crvUSD));
debtToken = new DebtToken("DebtToken", "DT", owner);
lendingPool = new LendingPool(
address(crvUSD), address(rToken), address(debtToken), address(raacNFT), address(raacHousePrices), 0.1e27
);
lendingPool.setPrimeRateOracle(address(owner));
//lendingPool.setCurveVault(address(curveMock));
treasury = new Treasury(address(owner));
repairFund = new Treasury(address(owner));
feeCollector =
new FeeCollector(address(raac), address(veRAAC), address(treasury), address(repairFund), address(owner));
deToken = new DEToken("DEToken", "DET", owner, address(rToken));
stabilityPool = new StabilityPool(address(owner));
raacMinter = new RAACMinter(address(raac), address(stabilityPool), address(lendingPool), address(treasury));
vm.prank(owner);
raac.setFeeCollector(address(feeCollector));
vm.prank(owner);
raac.manageWhitelist(address(feeCollector), true);
vm.prank(owner);
raac.manageWhitelist(address(veRAAC), true);
vm.prank(owner);
raac.manageWhitelist(address(owner), true);
vm.prank(owner);
raac.setMinter(owner);
// raac.mint(BOB, 1_000_000e18);
// raac.mint(ALICE, 1_000_000e18);
// raac.mint(CHARLIE, 1_000_000e18);
usdc = new ERC20Mock("USDC", "USDC");
vm.label(address(usdc), "USDC");
vm.prank(owner);
raac.setMinter(address(raacMinter));
bytes32 role = feeCollector.FEE_MANAGER_ROLE();
vm.prank(owner);
feeCollector.grantRole(role, owner);
role = feeCollector.EMERGENCY_ROLE();
vm.prank(owner);
feeCollector.grantRole(role, owner);
role = feeCollector.DISTRIBUTOR_ROLE();
vm.prank(owner);
feeCollector.grantRole(role, owner);
vm.prank(owner);
rToken.setReservePool(address(lendingPool));
vm.prank(owner);
debtToken.setReservePool(address(lendingPool));
vm.prank(owner);
deToken.setStabilityPool(address(stabilityPool));
vm.prank(owner);
// Set up minter configuration
raac.transferOwnership(address(raacMinter));
vm.prank(owner);
rToken.transferOwnership(address(lendingPool));
vm.prank(owner);
debtToken.transferOwnership(address(lendingPool));
vm.prank(owner);
stabilityPool.initialize(
address(rToken), address(deToken), address(raac), address(raacMinter), address(crvUSD), address(lendingPool)
);
// setValueContractAddress = address(new SetValueContract());
// Set up lending pool configuration
lendingPool.setStabilityPool(address(stabilityPool));
vm.prank(owner);
raacHousePrices.setOracle(owner);
vm.prank(owner);
stabilityPool.setRAACMinter(address(raacMinter));
_createAuction();
rewardToken = new ERC20Mock("RewardToken", "RT");
timelock = new TimelockController(2 days, proposers, executors, owner);
governance = new Governance(address(veRAAC), address(timelock));
role = timelock.PROPOSER_ROLE();
vm.prank(owner);
timelock.grantRole(role, address(governance));
role = timelock.EXECUTOR_ROLE();
vm.prank(owner);
timelock.grantRole(role, address(governance));
role = timelock.CANCELLER_ROLE();
vm.prank(owner);
timelock.grantRole(role, address(governance));
gaugeController = new GaugeController(address(veRAAC));
raacGauge = new RAACGauge(address(rewardToken), address(raac), address(gaugeController));
vm.label(address(raacGauge), "RAACGauge");
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, 10000);
raacGauge.grantRole(raacGauge.CONTROLLER_ROLE(), owner);
raacGauge.grantRole(raacGauge.FEE_ADMIN(), address(owner));
raacGauge.grantRole(raacGauge.EMERGENCY_ADMIN(), address(owner));
vm.prank(owner);
raacGauge.setBoostParameters(25000, 10000, 1 weeks);
vm.prank(owner);
raacGauge.setDistributionCap(10000000e18);
vm.prank(owner);
// Set initial weight after time alignment
raacGauge.setInitialWeight(5000); // 50% weight
rwaGauge = new RWAGauge(address(rewardToken), address(raac), address(gaugeController));
vm.label(address(rwaGauge), "RWAGauge");
gaugeController.addGauge(address(rwaGauge), IGaugeController.GaugeType.RWA, 10000);
rwaGauge.grantRole(rwaGauge.CONTROLLER_ROLE(), address(owner));
rwaGauge.grantRole(rwaGauge.FEE_ADMIN(), address(owner));
rwaGauge.grantRole(rwaGauge.EMERGENCY_ADMIN(), address(owner));
// rewardToken.mint(address(raacGauge), type(uint256).max / 2);
//rewardToken.mint(address(rwaGauge), type(uint256).max / 2);
// Initialize boost parameters
vm.prank(owner);
rwaGauge.setBoostParameters(
25000, // maxBoost (2.5x = 25000 basis points)
10000, // minBoost (1x = 10000 basis points)
7 * 24 * 3600 // boostWindow (7 days)
);
vm.prank(owner);
// Set distribution cap
rwaGauge.setDistributionCap(10000000e18);
vm.prank(owner);
// Set monthly emission
rwaGauge.setMonthlyEmission(100000e18);
boostController = new BoostController(address(veRAAC));
boostController.modifySupportedPool(address(lendingPool), true);
gauges = [address(raacGauge), address(rwaGauge)];
for (uint256 i; i < users.length; i++) {
crvUSD.mint(users[i], 1_000_000_000e18);
vm.prank(users[i]);
crvUSD.approve(address(raacNFT), type(uint256).max);
vm.prank(users[i]);
crvUSD.approve(address(lendingPool), type(uint256).max);
vm.prank(users[i]);
debtToken.approve(address(lendingPool), type(uint256).max);
vm.prank(users[i]);
rToken.approve(address(stabilityPool), type(uint256).max);
vm.prank(users[i]);
raac.approve(address(veRAAC), type(uint256).max);
usdc.mint(users[i], 1_000_000_000e18);
vm.prank(users[i]);
raac.approve(address(raacGauge), type(uint256).max);
vm.prank(users[i]);
raac.approve(address(rwaGauge), type(uint256).max);
}
timestampBefore = block.timestamp;
}
function _createAuction() internal {
currentZeno = new ZENO(address(usdc), 365 days, "ZENO", "ZN", owner);
currentAuction = new Auction(
address(currentZeno),
address(usdc),
address(currentZeno),
block.timestamp + 1 days,
block.timestamp + 11 days,
500,
10,
1000e18,
owner
);
vm.prank(owner);
currentZeno.transferOwnership(address(currentAuction));
for (uint256 i; i < users.length; i++) {
vm.prank(users[i]);
usdc.approve(address(currentAuction), type(uint256).max);
}
auctions.push(currentAuction);
zenos.push(currentZeno);
}
function test_lockTwoTimePOC() public {
vm.startPrank(CHARLIE);
//charlie deposit in the lending and stability pool
lendingPool.deposit(10_000e18);
stabilityPool.deposit(rToken.balanceOf(CHARLIE));
vm.warp(block.timestamp+ 6003);
vm.roll(block.number + uint256(6003)/15);
stabilityPool.withdraw(deToken.balanceOf(CHARLIE));
veRAAC.lock(raac.balanceOf(CHARLIE)/2,31554267);
uint256 balanceOfCharlie = veRAAC.balanceOf(CHARLIE);
uint256 charlieVotingPower = veRAAC.getVotingPower(CHARLIE);
assertEq(balanceOfCharlie, charlieVotingPower);
veRAAC.lock(raac.balanceOf(CHARLIE)/2,31537156);
vm.stopPrank();
balanceOfCharlie = veRAAC.balanceOf(CHARLIE);
charlieVotingPower = veRAAC.getVotingPower(CHARLIE);
assertEq(balanceOfCharlie, charlieVotingPower);
}
function test_UserHaveDebtAfterLiquidation() public {
// 350 days
vm.warp(block.timestamp+30299853);
vm.roll(block.number + 2271488);
vm.prank(owner);
raacHousePrices.setHousePrice(1, 653482530205154129803885);
vm.startPrank(BOB);
//house of 653482 dollars
raacNFT.mint(1, 653482530205154129803885);
raacNFT.approve(address(lendingPool),1);
// 69 days
vm.warp(block.timestamp+6027005);
vm.roll(block.number + 2058436);
lendingPool.deposit(425197589667951795675698);
// 288 days
vm.warp(block.timestamp+24960365);
vm.roll(block.number + 111874);
lendingPool.depositNFT(1);
// 13 days
vm.warp(block.timestamp+1148672);
vm.roll(block.number + 653211);
lendingPool.borrow(345941753031933799366395);
vm.stopPrank();
// 4 hours
vm.warp(block.timestamp+14784);
vm.roll(block.number + 1474);
lendingPool.setParameter(ILendingPool.OwnerParameter.LiquidationThreshold,1044);
// 62 days
vm.warp(block.timestamp+5437407);
vm.roll(block.number + 1619098);
vm.prank(ALICE);
lendingPool.initiateLiquidation(BOB);
// 140 days
vm.warp(block.timestamp+140 days);
vm.roll(block.number + uint(2961535)/140);
uint256 userDebt ;
uint256 scaledUserDebt;
uint256 beforeState = vm.snapshot();
lendingPool.updateState();
//We compute the debt after the call of update state
userDebt = lendingPool.getUserDebt(BOB);
console.log("User debt after the state update: %d",userDebt);
vm.revertTo(beforeState);
userDebt = lendingPool.getUserDebt(BOB);
console.log("User debt without updating the state: %d",userDebt);
scaledUserDebt = WadRayMath.rayMul(userDebt, lendingPool.getNormalizedDebt());
crvUSD.mint(address(stabilityPool), scaledUserDebt);
vm.expectRevert();
vm.prank(owner);
stabilityPool.liquidateBorrower(BOB);
}
function test_GaugeWeigthPOC() public {
vm.startPrank(BOB);
lendingPool.deposit(697106689045178731767167);
vm.warp(block.timestamp + 8801775);
vm.roll(block.number + 1931936);
stabilityPool.deposit(414308523880797109194139);
stabilityPool.withdraw(1702830198);
veRAAC.lock(15290, 31544587) ;
gaugeController.vote(gauges[0], 7174);
veRAAC.increase(43310635545591792929782);
vm.expectRevert(stdError.arithmeticError);
gaugeController.vote(gauges[0], 5362);
vm.stopPrank();
}
function test_increaseUnderflow() public {
vm.startPrank(ALICE);
lendingPool.deposit(14069761205393756697566);
stabilityPool.deposit(13624960859879652433378);
vm.warp(block.timestamp+ 7046960);
vm.roll(block.number + uint(7046960)/15);
stabilityPool.withdraw(5621714943215731129846);
veRAAC.lock(18407952740223189400, 115231135);
vm.warp(block.timestamp+ 2688);
vm.roll(block.number + uint(2688)/15);
vm.expectRevert(stdError.arithmeticError);
veRAAC.increase(6524);
vm.stopPrank();
}
function test_stuckFund() public {
vm.warp(block.timestamp + 20445256);
vm.roll(block.number + 134873);
vm.prank(owner);
raacHousePrices.setHousePrice(1, 104340533608757567757358);
vm.prank(ALICE);
raacNFT.mint(1, 104340533608757567757358);
vm.prank(ALICE);
raacNFT.approve(address(lendingPool), 1);
vm.warp(block.timestamp + 19516953);
vm.roll(block.number + 323395);
vm.startPrank(ALICE);
vm.warp(block.timestamp + 28940007);
vm.roll(block.number + 2464632);
lendingPool.deposit(99400641569938798721074);
vm.warp(block.timestamp + 2114289);
vm.roll(block.number + 1016112);
lendingPool.depositNFT(1);
vm.warp(block.timestamp + 13230446);
vm.roll(block.number + 1947131);
lendingPool.borrow(89049154235015082950118);
vm.warp(block.timestamp + 29353503);
vm.roll(block.number + 21);
lendingPool.repay(35083772923593810086738);
vm.warp(block.timestamp + 66);
vm.roll(block.number + 15);
stabilityPool.deposit(1e18);
vm.warp(block.timestamp + 28826820);
vm.roll(block.number + 856831);
stabilityPool.withdraw(1e18);
vm.stopPrank();
uint256 totalDeposit = stabilityPool.getTotalDeposits();
uint256 deTokenBalance = deToken.balanceOf(address(ALICE));
console.log("Total deposit: %d \n deToken balance of Alice : %d", totalDeposit, deTokenBalance);
assertGt(totalDeposit, 0, "deposit stuck");
assertEq(deTokenBalance, 0, "no deToken balance");
}
function test_lockBug() public {
vm.prank(BOB);
lendingPool.deposit(97688125191078638834935);
vm.prank(ALICE);
lendingPool.deposit(97688125191078638834935);
vm.prank(BOB);
stabilityPool.deposit(21645898045733887803708);
vm.prank(ALICE);
stabilityPool.deposit(21645898045733887803708);
vm.warp(block.timestamp + 20322000);
vm.roll(block.number + 18000);
vm.prank(BOB);
stabilityPool.withdraw(536065376621733750);
vm.prank(ALICE);
stabilityPool.withdraw(536065376621733750);
vm.prank(BOB);
veRAAC.lock(407669995645484585022, 116491286) ;
vm.warp(block.timestamp + 10456);
vm.roll(block.number + 1040385);
vm.startPrank(BOB);
veRAAC.increase(raac.balanceOf(BOB));
vm.stopPrank();
vm.warp(block.timestamp + 595398);
vm.roll(block.number + 12370);
// the call revert because the period is not elapsed
vm.expectRevert(TimeWeightedAverage.PeriodNotElapsed.selector);
vm.prank(ALICE);
veRAAC.lock(407669995645484585022, 116491286) ;
}
}
contract CurveMock is ERC4626 {
IERC20 curveToken;
constructor(address token) ERC4626(IERC20(token)) ERC20("curve vault", "CV") {}
function withdraw(uint256 assets, address receiver, address owner, uint256 maxLoss, address[] calldata strategies)
external
returns (uint256 shares) {
withdraw(assets, receiver, owner);
}
}

Impact

• The veRAAC token becomes temporarily DoSed, as new users cannot lock their tokens until the total duration elapsed.

• This could disrupt governance and prevent new liquidity providers from participating.

Tools Used

Foundry

Recommendations

The protocol should add a value to store the initial duration and use it fot the safety check of the createPeriod function.

Updates

Lead Judging Commences

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

veRAACToken can be DOSed when totalDuration exceeds boostWindow in TimeWeightedAverage, preventing new users from locking tokens until extended duration elapses

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

veRAACToken can be DOSed when totalDuration exceeds boostWindow in TimeWeightedAverage, preventing new users from locking tokens until extended duration elapses

Support

FAQs

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

Give us feedback!