Summary
The tick() function in the RAACMinter contract contains a bug where newly minted tokens are simultaneously recorded in excessTokens and directly minted to the StabilityPool, leading to double accounting of the same tokens.
Vulnerability Details
In the tick() function:
function tick() external nonReentrant whenNotPaused {
if (blocksSinceLastUpdate > 0) {
uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
if (amountToMint > 0) {
excessTokens += amountToMint;
lastUpdateBlock = currentBlock;
raacToken.mint(address(stabilityPool), amountToMint);
emit RAACMinted(amountToMint);
}
}
}
The bug arises when excessTokens += amountToMint records tokens as available for future distribution and when raacToken.mint(address(stabilityPool), amountToMint) immediately mints and distributes these same tokens.
This conflicts with the intended token distribution mechanism shown in mintRewards():
function mintRewards(address to, uint256 amount) external nonReentrant whenNotPaused {
if (msg.sender != address(stabilityPool)) revert OnlyStabilityPool();
uint256 toMint = excessTokens >= amount ? 0 : amount - excessTokens;
excessTokens = excessTokens >= amount ? excessTokens - amount : 0;
}
Impact
Incorrect accounting of available tokens
Potential over-minting of tokens beyond intended emission schedule
Tools Used
Recommendations
Remove direct minting to StabilityPool
function tick() external nonReentrant whenNotPaused {
if (blocksSinceLastUpdate > 0) {
uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
if (amountToMint > 0) {
excessTokens += amountToMint;
lastUpdateBlock = currentBlock;
emit RAACMinted(amountToMint);
}
}
}
or remove excess tokens accounting and maintain direct minting
function tick() external nonReentrant whenNotPaused {
if (blocksSinceLastUpdate > 0) {
uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
if (amountToMint > 0) {
lastUpdateBlock = currentBlock;
raacToken.mint(address(stabilityPool), amountToMint);
emit RAACMinted(amountToMint);
}
}
}