code-423n4 / 2023-06-lybra-findings

8 stars 7 forks source link

Invalid implementation of prioritized token rewards distribution #828

Open code423n4 opened 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L190-L218 https://github.com/code-423n4/2023-06-lybra/blob/7b73ef2fbb542b569e182d9abf79be643ca883ee/contracts/lybra/miner/ProtocolRewardsPool.sol#L209

Vulnerability details

Vulnerability Details

The getReward external function can't calculate and distribute rewards correctly for an account because of the reasons below:

Impact

Users can't get rewards and rewards freezes.

Proof of Concept

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {Test, console} from "forge-std/Test.sol";
import {GovernanceTimelock} from "contracts/lybra/governance/GovernanceTimelock.sol";
import {mockCurve} from "contracts/mocks/mockCurve.sol";
import {Configurator} from "contracts/lybra/configuration/LybraConfigurator.sol";
import {LybraWBETHVault} from "contracts/lybra/pools/LybraWbETHVault.sol";
import {PeUSDMainnet} from "contracts/lybra/token/PeUSDMainnetStableVision.sol";
import {ProtocolRewardsPool} from "contracts/lybra/miner/ProtocolRewardsPool.sol";
import {EUSDMock} from "contracts/mocks/MockEUSD.sol";
import {LBR} from "contracts/lybra/token/LBR.sol";
import {esLBR} from "contracts/lybra/token/esLBR.sol";
import {esLBRBoost} from "contracts/lybra/miner/esLBRBoost.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// 6 decimal USDC mock
contract mockUSDC is ERC20 {
    constructor() ERC20("USDC", "USDC") {
        _mint(msg.sender, 1000000 * 1e6);
    }

    function claim() external returns (uint256) {
        _mint(msg.sender, 10000 * 1e6);
        return 10000 * 1e6;
    }

    function decimals() public view virtual override returns (uint8) {
        return 6;
    }
}

contract ProtocolRewardsPoolTest is Test {
    address goerliEndPoint = 0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23;
    address wbETH = 0xbfD2135BFfbb0B5378b56643c2Df8a87552Bfa23;
    address deployer;
    address attacker;
    address alice;

    address[] proposers;
    address[] executors;
    address[] minerContracts;
    bool[] minerContractsBools;

    GovernanceTimelock governance;
    mockCurve curvePool;
    Configurator configurator;
    PeUSDMainnet peUsdMainnet;
    EUSDMock eUSD;
    mockUSDC usdc;
    LBR lbr;
    esLBR eslbr;
    esLBRBoost boost;
    LybraWBETHVault wbETHVault;
    ProtocolRewardsPool rewardsPool;

    function setUp() public {
        deployer = makeAddr("deployer");
        attacker = makeAddr("attacker");
        alice = makeAddr("alice");
        vm.startPrank(deployer);
        proposers.push(deployer);
        executors.push(deployer);
        governance = new GovernanceTimelock(2, proposers, executors, deployer);
        curvePool = new mockCurve();
        configurator = new Configurator(address(governance), address(curvePool));
        peUsdMainnet = new PeUSDMainnet(
            address(configurator),
            8,
            goerliEndPoint
        );
        eUSD = new EUSDMock(address(configurator));
        // 6 decimal USDC token
        usdc = new mockUSDC();
        lbr = new LBR(address(configurator), 8, goerliEndPoint);
        eslbr = new esLBR(address(configurator));
        boost = new esLBRBoost();
        rewardsPool = new ProtocolRewardsPool(address(configurator));
        rewardsPool.setTokenAddress(address(eslbr), address(lbr), address(boost));
        // Ether oracle has no impact on this test
        wbETHVault =
        new LybraWBETHVault(address(peUsdMainnet), makeAddr("NonImportantMockForEtherOracle"), wbETH, address(configurator));
        configurator.setMintVault(deployer, true);
        configurator.initToken(address(eUSD), address(peUsdMainnet));
        configurator.setProtocolRewardsPool(address(rewardsPool));
        configurator.setProtocolRewardsToken(address(usdc));
        curvePool.setToken(address(eUSD), address(usdc));

        // Set minters
        minerContracts.push(address(deployer));
        minerContracts.push(address(rewardsPool));
        minerContractsBools.push(true);
        minerContractsBools.push(true);
        configurator.setTokenMiner(minerContracts, minerContractsBools);

        // Fund curve pool eusd/usdc
        eUSD.mint(address(curvePool), 10000 * 1e18);
        usdc.transfer(address(curvePool), 10000 * 1e6);

        // Fund ALice
        lbr.mint(address(alice), 100 * 1e18);
        vm.stopPrank();
    }

    function test_canGetReward() public {
        // Alice stake LBR
        vm.startPrank(alice);
        rewardsPool.stake(100 * 1e18);
        assertEq(eslbr.balanceOf(alice), 100 * 1e18);
        vm.stopPrank();

        // Notify reward amount
        vm.startPrank(deployer);
        eUSD.mint(address(configurator), 3000 * 1e18);
        configurator.setPremiumTradingEnabled(true);

        uint256 eusdPreBalance = eUSD.balanceOf(address(configurator));
        configurator.distributeRewards();
        curvePool.setPrice(1010000);
        uint256 price = curvePool.get_dy_underlying(0, 2, 1e18);
        uint256 outUSDC = eusdPreBalance * price * 998 / 1e21;
        assertEq(eUSD.sharesOf(address(rewardsPool)), 0);
        assertEq(usdc.balanceOf(address(rewardsPool)), outUSDC);

        configurator.distributeRewards();
        vm.stopPrank();

        uint256 newRewardPerTokenstored =
            outUSDC * 1e36 / (10 ** ERC20(configurator.stableToken()).decimals()) / eslbr.totalSupply();

        assertEq(rewardsPool.rewardPerTokenStored(), newRewardPerTokenstored);
        assertEq(rewardsPool.earned(alice), eslbr.balanceOf(alice) * (newRewardPerTokenstored - 0) / 1e18);
        uint256 earnedUSDC = rewardsPool.earned(alice);

        vm.startPrank(alice);
        rewardsPool.getReward();
        // earnedUSDC / 1e12 -> Because rewardsPool.earned output has 18 decimal and USDC has 6 decimals
        assertEq(usdc.balanceOf(alice), earnedUSDC / 1e12);
        vm.stopPrank();
    }
}

Tools Used

Assessed type

Math

c4-pre-sort commented 1 year ago

JeffCX marked the issue as duplicate of #501

c4-judge commented 1 year ago

0xean marked the issue as satisfactory

c4-judge commented 1 year ago

0xean changed the severity to 2 (Med Risk)

c4-judge commented 1 year ago

0xean marked the issue as selected for report

c4-sponsor commented 1 year ago

LybraFinance marked the issue as sponsor confirmed