Core Contracts

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

Front-running Vulnerability in Pool Support Modification

Summary

The modifySupportedPool function in BoostController.sol is vulnerable to front-running attacks, allowing malicious users to gain unfair advantages by monitoring and acting on pending pool support changes before they are executed.

Vulnerability Detail

The modifySupportedPool function allows managers to add or remove pool support without any time delay or protection mechanism:

function modifySupportedPool(
address pool,
bool isSupported
) external onlyRole(MANAGER_ROLE) {
if (pool == address(0)) revert InvalidPool();
if (supportedPools[pool] == isSupported) revert PoolNotSupported();
supportedPools[pool] = isSupported;
if (isSupported) {
emit PoolAdded(pool);
} else {
emit PoolRemoved(pool);
}
}

This implementation allows users to:

  1. Monitor the mempool for modifySupportedPool transactions

  2. Front-run these transactions to perform actions before the pool status changes

  3. Potentially gain unfair advantages or exploit the system

Impact

The vulnerability can be exploited in two scenarios:

Scenario 1: Front-running Pool Removal

  1. Manager submits transaction to remove pool support

  2. Attacker sees this pending transaction in mempool

  3. Attacker front-runs with their own transaction to:

    • Lock tokens

    • Claim rewards

    • Perform other privileged actions while pool is still supported

  4. Manager's transaction executes, removing pool support

  5. Attacker has gained benefits that should have been prevented

Scenario 2: Front-running Pool Addition

  1. Manager submits transaction to add new pool support

  2. Attacker sees this pending transaction in mempool

  3. Attacker prepares and front-runs with optimized transactions

  4. Attacker gains first-mover advantage in the newly supported pool

Proof of Concept

A test demonstrating this vulnerability has been created in test/BoostController.t.sol:

function testFrontRunningVulnerability() public {
// Setup: Add initial pool support
vm.startPrank(manager);
address testPool = address(0x123);
boostController.modifySupportedPool(testPool, true);
vm.stopPrank();
// Simulate mempool monitoring
bytes memory managerCalldata = abi.encodeWithSelector(
BoostController.modifySupportedPool.selector,
testPool,
false
);
// Setup next block
vm.roll(block.number + 1);
// Attacker front-runs with their transaction
vm.startPrank(user1);
assertTrue(boostController.supportedPools(testPool));
// Attacker can perform privileged actions here
vm.stopPrank();
// Manager's transaction executes after
vm.startPrank(manager);
boostController.modifySupportedPool(testPool, false);
vm.stopPrank();
}

Tools Used

  • Manual review

  • Foundry for POC testing

Recommended Mitigation Steps

Implement one of the following protection mechanisms:

1. Timelock Mechanism (Recommended)

uint256 public constant TIMELOCK_DELAY = 2 days;
mapping(address => uint256) public poolStatusChangeTime;
function modifySupportedPool(address pool, bool isSupported) external onlyRole(MANAGER_ROLE) {
if (pool == address(0)) revert InvalidPool();
if (supportedPools[pool] == isSupported) revert PoolNotSupported();
poolStatusChangeTime[pool] = block.timestamp + TIMELOCK_DELAY;
emit PoolStatusChangeScheduled(pool, isSupported, poolStatusChangeTime[pool]);
}
function executePoolStatusChange(address pool, bool isSupported) external onlyRole(MANAGER_ROLE) {
if (poolStatusChangeTime[pool] == 0) revert NoChangeScheduled();
if (block.timestamp < poolStatusChangeTime[pool]) revert TimelockNotExpired();
supportedPools[pool] = isSupported;
delete poolStatusChangeTime[pool];
if (isSupported) {
emit PoolAdded(pool);
} else {
emit PoolRemoved(pool);
}
}

2. OpenZeppelin TimelockController

Integrate with OpenZeppelin's TimelockController for a battle-tested solution:

contract BoostController is IBoostController, TimelockController {
function modifySupportedPool(address pool, bool isSupported)
external
onlyRole(TIMELOCK_PROPOSER_ROLE)
{
bytes memory data = abi.encodeWithSelector(
this.executePoolChange.selector,
pool,
isSupported
);
schedule(
address(this),
0,
data,
bytes32(0),
bytes32(0),
TIMELOCK_DELAY
);
}
}

3. Commit-Reveal Scheme

struct PoolChange {
bytes32 commitment;
uint256 revealTime;
bool revealed;
}
mapping(address => PoolChange) public poolChanges;
function commitPoolChange(bytes32 commitment) external onlyRole(MANAGER_ROLE) {
poolChanges[msg.sender] = PoolChange({
commitment: commitment,
revealTime: block.timestamp + TIMELOCK_DELAY,
revealed: false
});
}
function revealPoolChange(address pool, bool isSupported, bytes32 salt)
external
onlyRole(MANAGER_ROLE)
{
PoolChange storage change = poolChanges[msg.sender];
require(block.timestamp >= change.revealTime, "Too early");
require(!change.revealed, "Already revealed");
require(
keccak256(abi.encodePacked(pool, isSupported, salt)) == change.commitment,
"Invalid reveal"
);
supportedPools[pool] = isSupported;
change.revealed = true;
if (isSupported) {
emit PoolAdded(pool);
} else {
emit PoolRemoved(pool);
}
}

Risk Rating

  • Impact: High (Allows unfair advantages and potential exploitation)

  • Likelihood: High (Easy to monitor mempool and front-run)

  • Overall: High

References

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Too generic

Support

FAQs

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