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:
Monitor the mempool for modifySupportedPool transactions
 
Front-run these transactions to perform actions before the pool status changes
 
Potentially gain unfair advantages or exploit the system
 
Impact
The vulnerability can be exploited in two scenarios:
Scenario 1: Front-running Pool Removal
Manager submits transaction to remove pool support
 
Attacker sees this pending transaction in mempool
 
Attacker front-runs with their own transaction to:
 
Manager's transaction executes, removing pool support
 
Attacker has gained benefits that should have been prevented
 
Scenario 2: Front-running Pool Addition
Manager submits transaction to add new pool support
 
Attacker sees this pending transaction in mempool
 
Attacker prepares and front-runs with optimized transactions
 
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 {
    
    vm.startPrank(manager);
    address testPool = address(0x123);
    boostController.modifySupportedPool(testPool, true);
    vm.stopPrank();
    
    
    bytes memory managerCalldata = abi.encodeWithSelector(
        BoostController.modifySupportedPool.selector,
        testPool,
        false
    );
    
    vm.roll(block.number + 1);
    
    
    vm.startPrank(user1);
    assertTrue(boostController.supportedPools(testPool));
    
    vm.stopPrank();
    
    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
References