Backd Tokenomics contest - csanuragjain's results

Maximize the power of your assets and start earning yield

General Information

Platform: Code4rena

Start Date: 27/05/2022

Pot Size: $75,000 USDC

Total HM: 20

Participants: 58

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 15

Id: 131

League: ETH

Backd

Findings Distribution

Researcher Performance

Rank: 7/58

Findings: 3

Award: $3,532.99

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: csanuragjain

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

2732.5332 USDC - $2,732.53

External Links

Lines of code

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/RewardHandler.sol#L50

Vulnerability details

Impact

burnFees will fail if none of the pool tokens have underlying token as native ETH token. This is shown below. Since burnFees fails so no fees is deposited in BKDLocker

Proof of Concept

  1. Assume RewardHandler.sol has currently amount 5 as address(this).balance (ethBalance) (even attacker can send a small balance to this contract to do this dos attack)
  2. None of the pools have underlying as address(0) so no ETH tokens and only ERC20 tokens are present
  3. Now feeBurner.burnToTarget is called passing current ETH balance of amount 5 with all pool tokens
  4. feeBurner loops through all tokens and swap them to WETH. Since none of the token is ETH so burningEth_ variable is false
  5. Now the below require condition fails since burningEth_ is false
require(burningEth_ || msg.value == 0, Error.INVALID_VALUE);
  1. This fails the burnFees function.

ETH should not be sent if none of pool underlying token is ETH. Change it to something like below:

bool ethFound=false; for (uint256 i; i < pools.length; i = i.uncheckedInc()) { ILiquidityPool pool = ILiquidityPool(pools[i]); address underlying = pool.getUnderlying(); if (underlying != address(0)) { _approve(underlying, address(feeBurner)); } else { ethFound=true; } tokens[i] = underlying; } if(ethFound){ feeBurner.burnToTarget{value: ethBalance}(tokens, targetLpToken); } else { feeBurner.burnToTarget(tokens, targetLpToken); }

#0 - GalloDaSballo

2022-06-19T19:30:32Z

The warden has shown how, due to a flaw in the logic, if ETH is present in the contract and no pool is denominated in ETH, then the contract will revert.

This can be done as a DOS attack or for griefing.

However, remediation would simply require adding a pool denominated in ETH, to ensure that the logic goes through

For this reason, I believe Medium Severity to be more appropriate

Findings Information

🌟 Selected for report: hansfriese

Also found by: csanuragjain, unforgiven

Labels

bug
duplicate
2 (Med Risk)
sponsor disputed

Awards

737.784 USDC - $737.78

External Links

Lines of code

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/BkdLocker.sol#L70

Vulnerability details

Impact

If Governor mistakenly adds duplicate reward token then user will get more funds

Proof of Concept

  1. Assume rewardToken was X and _replacedRewardTokens was empty
  2. Governor calls migrate function and mistakenly again provides newRewardToken as X
  3. First X will be removed from _replacedRewardTokens, since it does not exist, nothing happens
_replacedRewardTokens.remove(newRewardToken);
  1. Now X is added to _replacedRewardTokens at current block timestamp
_replacedRewardTokens.set(rewardToken, block.timestamp);
  1. Now new rewardToken is also set as X

  2. Finally this becomes like below

_replacedRewardTokens=[X] rewardToken=X
  1. This is incorrect since this will give double rewards to user when _userCheckpoint function is called (once by increasing curRewardTokenData.userShares[user] and secondly by increasing prevRewardTokenData.userShares[user], both of which are same tokens)

Add a check to see that new reward token is not equal to existing reward token

require(newRewardToken!=rewardToken, "Migration not required");

#0 - pauljpritz-zz

2022-06-02T12:18:47Z

I don't think this is a valid bug. When the rewardToken is replaced, the feeIntegral for that reward token stops increasing. Therefore, even if the same reward token is present twice, it will have separate fee integrals and will therefore be treated entirely seperately. Also, there is no use of balanceOf() to compute the feeIntegral, therefore the two records won't be confused.

#1 - GalloDaSballo

2022-06-22T17:02:54Z

@pauljpritz I believe that RewardTokenData storage curRewardTokenData = rewardTokenData[rewardToken]; RewardTokenData storage prevRewardTokenData = rewardTokenData[token];

When prev and new token are same, will point to the same area in storage, causing a double count.

If rewards where identified by a code, then I'd have to agree, but because they use the address, then the data for old and new would be in the same storage slot.

For that reason, I believe the finding to be valid, and a Dup of #86

Awards

62.6774 USDC - $62.68

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

External Links

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/utils/Preparable.sol#L143

Function _setConfig: No need to set deadlines[key] = 0; as this was already done in _executeDeadline function

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/tokenomics/Minter.sol#L218

Function _mint: Check that amount is not equal to zero

amount!=0

Note: do same for mintNonInflationTokens

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/tokenomics/LpGauge.sol#L59

Function claimRewards: Since amount is uint type so amount cannot be <0. Change it to

if (amount == 0) return 0;

Note: Same need to be fixed for other Gauge like AMMGauge

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/BkdLocker.sol#L214

Function claimFees: If claimable is zero, no need to go further

require(claimable!=0);
AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter