code-423n4 / 2024-01-salty-findings

11 stars 6 forks source link

It does not check if the pool has ever been whitelisted before when whitelisting it #702

Closed c4-bot-8 closed 9 months ago

c4-bot-8 commented 9 months ago

Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/dao/DAO.sol#L235-L273

Vulnerability details

Impact

A pool can receive far more than 200k SALT if it is whitelisted/unwhitelisted multiple times

Proof of Concept

When a pool is ready to be whitelisted, it will receive bootstrapping rewards of 200K SALT. However, it doesn't check if the pool has ever been whitelisted before. If somehow one pool has chance be whitelisted second time, it will receive 200K more SALT bootstrapping rewards, which should not be allowed.

Copy below codes to DAO.t.sol and run COVERAGE="yes" NETWORK="sep" forge test -vv --rpc-url RPC_URL --match-test testGetDoubleRewardDistributionAfterSecondWhitelisting

  function testGetDoubleRewardDistributionAfterSecondWhitelisting() public
  {
    // Alice stakes her SALT to get voting power
    IERC20 test = new TestERC20( "TEST", 18 );

    vm.startPrank(address(daoVestingWallet));
    salt.transfer(alice, 1000000 ether);                // for staking and voting
    salt.transfer(address(dao), 1000000 ether); // bootstrapping rewards
    vm.stopPrank();

    vm.startPrank(alice);
    staking.stakeSALT(500000 ether);

    // Propose a whitelisting ballot
    uint256 ballotID  = proposals.proposeTokenWhitelisting(test, "test", "test");
    proposals.castVote(ballotID, Vote.YES);

    // Increase block time to finalize the ballot
    vm.warp(block.timestamp + daoConfig.ballotMinimumDuration());

    uint256 bootstrapRewards = daoConfig.bootstrappingRewards();
    uint256 initialDaoBalance = salt.balanceOf(address(dao));

    dao.finalizeBallot(ballotID);

    // Assert that the token has been whitelisted
    assertTrue(poolsConfig.tokenHasBeenWhitelisted(test, wbtc, weth), "Token was not whitelisted");

    // Assert the correct amount of bootstrapping rewards have been deducted
    uint256 finalDaoBalance = salt.balanceOf(address(dao));
    assertEq(finalDaoBalance, initialDaoBalance - (bootstrapRewards * 2), "Bootstrapping rewards were not correctly deducted");

    ballotID = proposals.proposeTokenUnwhitelisting(test, "test", "test");
    proposals.castVote(ballotID, Vote.YES);
    vm.warp(block.timestamp + daoConfig.ballotMinimumDuration());
    dao.finalizeBallot(ballotID);
    assertTrue(!poolsConfig.tokenHasBeenWhitelisted(test, wbtc, weth), "Token was not unwhitelisted");

    ballotID = proposals.proposeTokenWhitelisting(test, "test", "test");
    proposals.castVote(ballotID, Vote.YES);
    vm.warp(block.timestamp + daoConfig.ballotMinimumDuration());
    dao.finalizeBallot(ballotID);
    assertTrue(poolsConfig.tokenHasBeenWhitelisted(test, wbtc, weth), "Token was not whitelisted");
    //@audit-info totaly double bootstrapping rewards was sent to whitelisted pool
    finalDaoBalance = salt.balanceOf(address(dao));
    assertEq(finalDaoBalance, initialDaoBalance - (bootstrapRewards * 4));

    vm.stopPrank();
  }

Tools Used

Manual review

Recommended Mitigation Steps

Assessed type

Other

c4-judge commented 9 months ago

Picodes marked the issue as duplicate of #823

c4-judge commented 8 months ago

Picodes marked the issue as unsatisfactory: Invalid

piken commented 8 months ago

This issue is not the dup of #823

823 assumes that one token can be proposed multi times before it is whitelisted, which has been disputed by the sponsor.

However, the root cause in #702 is that a token can be whitelisted again after it was unwhitelisted, and this could cause the problem. Below is a simple attack scenario:

The pools of [WBTC, TokenA] and [WETH, TokenA] got unfair bootstrapping rewards.

The PoC in #702 can prove that the issue exists.

Picodes commented 8 months ago

@piken Assuming the pool was unwhitelisted, and LPs are left, why not send bootstrapping rewards a second time to attract initial liquidity a second time? It would be a suitable design as well.

piken commented 8 months ago

@Picodes Thanks for replying my comment.

I have seen nowhere that it is an intentional design to send bootstrapping rewards a second time when a pool is second whitelisted. On the contrary, it's clearly stated that each pool should only receive 200k SALT as Bootstrapping Rewards. in Salty doc:

Bootstrapping Rewards (200k SALT per pool*) - established when a pool is whitelisted.

If such a design were intentional, it could allow liquidity providers to earn double or more rewards from a pool which is whitelisted two or more times, potentially disadvantaging providers in other pools. This scenario might incentivize a SALT holder to repeatedly whitelist a single pool, maximizing their rewards and draining the DAO of Bootstrapping Rewards unfairly.

Could we please involve @othernet-global to review this issue? Based on the comment they left on #823, it seems they may share the view that allowing one pool to receive multiple Bootstrapping Rewards should not be an intentional design choice.

Thank you a lot!

othernet-global commented 8 months ago

It will be up to the DAO to whitelist the pool for the second time. The DAO will be aware that whitelisting will provide bootstrapping rewards again and allowing them to choose so is by design.

Picodes commented 8 months ago

@piken " established when a pool is whitelisted" doesn't clearly say that this should happen only once. This is at most an instance of "function incorrect as to spec" so of Low severity.

c4-judge commented 8 months ago

Picodes marked the issue as unsatisfactory: Invalid