🖼️Vulnerabilities Caused by LP Tokens Being the Same as Reward Tokens

Vulnerability Overview

In staking or farming contracts, rewards are typically calculated based on the amount of liquidity that users have provided (represented by LP tokens) and the allocation of rewards based on time and pool participation. However, when the staked LP token and the reward token are the same, the contract may improperly calculate the total LP supply, which leads to incorrect reward distribution.

How the Exploit Occurs

  1. No Validation Check: The staking contract does not validate whether the LP token and reward token are different.

  2. Incorrect Reward Calculation: Since the reward is minted in the same token as the LP token, every time a reward is minted, the contract's balance of the LP token increases. This inflated balance affects the reward calculations, leading to smaller-than-expected reward distributions to liquidity providers.

  3. Rewards are Disproportionately Reduced: Because the total LP supply includes the minted rewards (which should not be counted), the calculation mistakenly divides the total reward by an inflated supply, reducing the actual reward per user.

Example 1: LP Farming Contract

In the first example, consider a staking contract where liquidity providers can stake their LP tokens and receive rewards. The staking contract does not check whether the LP token and the reward token are the same, leading to distorted reward calculations:

function add(uint256 _allocPoint, IERC20 _lpToken) external onlyOwner {
    _massUpdatePools();

    uint256 lastRewardBlock = _blockNumber();
    totalAllocPoint = totalAllocPoint + _allocPoint;
    poolInfo.push(
        PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardBlock: lastRewardBlock,
            accRewardPerShare: 0
        })
    );
}

In this case:

  • No Check for LP vs. Reward Token: The contract allows the same token to be used as both the staking token (_lpToken) and the reward token.

  • Incorrect Reward Calculation: The _updatePool() function incorrectly uses the balance of the _lpToken to calculate rewards. Since the _lpToken is the same as the reward token, the balance inflates every time rewards are minted, reducing the actual rewards distributed to users.

function _updatePool(uint256 _pid) internal {
    PoolInfo storage pool = poolInfo[_pid];
    uint256 lpSupply = pool.lpToken.balanceOf(address(this));
    if (lpSupply == 0) {
        return;
    }
    uint256 reward = calculateReward(pool);
    pool.accRewardPerShare = pool.accRewardPerShare + reward / lpSupply;
    pool.lastRewardBlock = _blockNumber();
}

Since the reward is paid in the same token as the LP token, the lpSupply increases with every reward minting, resulting in incorrect reward calculations.

Example 2: Token Vaults

In this second example, the protocol allows users to create vaults for staking tokens. However, no validation prevents the protocol's own token from being used to create a vault, which can lead to unusable vaults and trapped user funds:

function createVault(address token, uint256 tokenIdOrAmount) external {
    // Vulnerability: No check to ensure the vault token is not the same as the protocol's own token
    uint256 vaultId = _mintVault(msg.sender, token, tokenIdOrAmount);
}

In this case, if the protocol's own token is used to create a vault, it leads to a circular dependency where the protocol becomes the owner of the vault, resulting in the vault becoming unusable. Funds, such as ETH, deposited by users into the vault can become permanently locked.

Example 3: MasterChef Contract

Another example involves a MasterChef staking contract where LP tokens are staked to earn rewards. If the LP token and the reward token are the same, the reward calculations are similarly distorted:

function add(
    uint256 _allocPoint,
    IERC20 _lpToken,
    bool _withUpdate
) public onlyOwner {
    if (_withUpdate) {
        massUpdatePools();
    }
    poolInfo.push(
        PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardBlock: block.number,
            accRewardPerShare: 0
        })
    );
}

In this scenario, if the _lpToken is the same as the reward token, every time rewards are minted to the contract, the contract's lpToken balance inflates, reducing the per-user reward share inappropriately.

Impact

  1. Reduced Rewards: Users receive fewer rewards than expected, which discourages participation in the protocol.

  2. Locked Funds: In the case of token vaults, using the same token as both the staking and reward token can lead to locked funds that cannot be redeemed or withdrawn.

  3. Liquidity Drain: The distorted reward calculation mechanism can cause an imbalance in the reward distribution, leading to a potential drain on liquidity or a significant loss of rewards for users.

Conclusion

The issue of using the same token as both the LP token and reward token can cause significant problems in reward calculation, liquidity management, and overall protocol functionality. By implementing appropriate checks to ensure these tokens are different, protocols can avoid these vulnerabilities, ensuring fair and accurate reward distributions to their users.

Last updated