Core Contracts

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

Potential failure in `depositRAACFromPool()` due to missing prior contract balance update

Summary

The depositRAACFromPool() function in the RAACMinter contract is susceptible to a race condition that can lead to incorrect balance checks and potential reverts. This occurs when an immediate call to any function that invokes _update() in the StabilityPool contract that can modify the balance of RAAC tokens between the time the balance is checked and the time the transfer is executed.

Vulnerability Details

The depositRAACFromPool() function does not update RAAC tokens balance in StabilityPool prior to adding more tokens:

function depositRAACFromPool(uint256 amount) external onlyLiquidityPool validAmount(amount) {
>> // @audit-info Missing prior update
uint256 preBalance = raacToken.balanceOf(address(this));
raacToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 postBalance = raacToken.balanceOf(address(this));
// @audit-info Potential failure here
>> if (postBalance != preBalance + amount) revert InvalidTransfer();
---SNIP---
}

The function simply takes a snapshot of the current raacToken balance (preBalance), proceeds to transfer more tokens to the contract and then performs a check between the final balance after transfer (postBalance) and the initial balance.

However, this will result in revert if another function that invokes _update() is called immediately and is executed fully before this one is completed.

Logic:

The _update() function calls _mintRAACRewards() which calls raacMinter.tick() that performs the following:

function tick() external nonReentrant whenNotPaused { //@audit-ok
if (emissionUpdateInterval == 0 || block.timestamp >= lastEmissionUpdateTimestamp + emissionUpdateInterval) {
updateEmissionRate();
}
uint256 currentBlock = block.number;
uint256 blocksSinceLastUpdate = currentBlock - lastUpdateBlock;
if (blocksSinceLastUpdate > 0) {
uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
// @audit-info If amount to mint exists,
>> if (amountToMint > 0) {
excessTokens += amountToMint;
lastUpdateBlock = currentBlock;
// @audit-info It mints these raacTokens to stabilityPool increasing its balance
>> raacToken.mint(address(stabilityPool), amountToMint);
emit RAACMinted(amountToMint);
}
}
}

Now, in ethereum blockchain, transactions are not always executed sequentially by the order of their initiation. This means that any transaction can be executed earlier than one that was initiated before it.

Issue Scenario:

  1. Initial State:

    • The StabilityPool has 1000 RAAC tokens.

    • depositRAACFromPool(200 RAAC tokens) is then called from liquidity pool.

    • Checking the balance, (preBalance = 1000 RAAC tokens).

  2. Another Action:

    • Immediately after, Bob calls deposit() on the StabilityPool, triggering _update() and minting 500 RAAC tokens.

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
>> _update(); // will mint raacTokens to stability pool if available
---SNIP---
}
  • This transaction is executed and the StabilityPool balance is now updated to 1500 RAAC tokens.

3.Initial Transfer proceeds:

  • The depositRAACFromPool() proceeds to transfer 200 RAAC tokens, resulting in a new balance of 1700 RAAC tokens (1500 + 200).

  • The check if (postBalance != preBalance + amount) evaluates to if (1700 != 1000 + 200), which therefore fails.

Impact

This race condition therefore leads to the depositRAACFromPool() function reverting due to an incorrect balance check, potentially disrupting the expected flow of token deposits and affecting user experience.

Tools Used

Manual Review

Recommendations

Invoke the _update() function within depositRAACFromPool() before performing the transfer.

function depositRAACFromPool(uint256 amount) external onlyLiquidityPool validAmount(amount) {
+ // @audit Ensure the latest state is reflected
+ _update(); //
uint256 preBalance = raacToken.balanceOf(address(this));
raacToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 postBalance = raacToken.balanceOf(address(this));
if (postBalance != preBalance + amount) revert InvalidTransfer();
---SNIP---
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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