Popcorn contest - 0xNazgul's results

A multi-chain regenerative yield-optimizing protocol.

General Information

Platform: Code4rena

Start Date: 31/01/2023

Pot Size: $90,500 USDC

Total HM: 47

Participants: 169

Period: 7 days

Judge: LSDan

Total Solo HM: 9

Id: 211

League: ETH

Popcorn

Findings Distribution

Researcher Performance

Rank: 6/169

Findings: 6

Award: $2,246.26

🌟 Selected for report: 2

πŸš€ Solo Findings: 1

Awards

4.6115 USDC - $4.61

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-402

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol#L170

Vulnerability details

Impact

The claimRewards() function is used to claim rewards for a user in any amount of rewardTokens. Any ERC20 can be added meaning, any ERC777 token can be to since they are backwards-compatible.

Proof of Concept

This introduces a Reentracy vulnerability allowing anyone to get more accruedRewards draining the contract. Looking at The claimRewards() function:

function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) {
    for (uint8 i; i < _rewardTokens.length; i++) {
        uint256 rewardAmount = accruedRewards[user][_rewardTokens[i]];

        if (rewardAmount == 0) revert ZeroRewards(_rewardTokens[i]);

        EscrowInfo memory escrowInfo = escrowInfos[_rewardTokens[i]];

        if (escrowInfo.escrowPercentage > 0) {
            _lockToken(user, _rewardTokens[i], rewardAmount, escrowInfo);	//@audit if rewardToken were an erc777, it can reenter draining 
            emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, true);
        } else {
            _rewardTokens[i].transfer(user, rewardAmount);			//@audit if rewardToken were an erc777, it can reenter draining 
            emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, false);
        }

	accruedRewards[user][_rewardTokens[i]] = 0;
    }
}

Specifically line #L186 which is the point where the users accruedRewards is updated. Because it is done after both _lockToken() and transfer() Reentracy is possible with the use of ERC777.

Tools Used

Manual Review

Consider ensuring that no ERC777 tokens are able to be used as a reward token. Could also use a Reentrancy modifier and move line #L186 before like so:

function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) {
    for (uint8 i; i < _rewardTokens.length; i++) {
        uint256 rewardAmount = accruedRewards[user][_rewardTokens[i]];

        if (rewardAmount == 0) revert ZeroRewards(_rewardTokens[i]);

        EscrowInfo memory escrowInfo = escrowInfos[_rewardTokens[i]];

        accruedRewards[user][_rewardTokens[i]] = 0; 		//@audit move accruedRewards here to prevent Reentrancy

        if (escrowInfo.escrowPercentage > 0) {
            _lockToken(user, _rewardTokens[i], rewardAmount, escrowInfo);//@audit if rewardToken were an erc777, it can reenter draining 
            emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, true);
        } else {
            _rewardTokens[i].transfer(user, rewardAmount);
            emit RewardsClaimed(user, _rewardTokens[i], rewardAmount, false);
        }
    }
}

#0 - c4-judge

2023-02-16T07:39:43Z

dmvt marked the issue as duplicate of #54

#1 - c4-sponsor

2023-02-18T12:11:07Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T00:49:32Z

dmvt marked the issue as partial-50

#3 - c4-judge

2023-03-01T00:36:43Z

dmvt marked the issue as full credit

#4 - c4-judge

2023-03-01T00:37:29Z

dmvt marked the issue as satisfactory

Awards

14.2839 USDC - $14.28

Labels

bug
3 (High Risk)
satisfactory
sponsor confirmed
duplicate-243

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/beefy/BeefyAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L88 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L154 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol#L26 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L26

Vulnerability details

Impact

Vault tokens can be stolen from depositors in all EIP4626 Implementations vaults by manipulating the price of a share.

Proof of Concept

ERC4626 vaults are subject to a share price manipulation attack that allows an attacker to steal underlying tokens from other depositors (this is a known issue of ERC4626 implementation). Consider this scenario for Vault.sol:

  1. Malaclypse is the first depositor of the vault;
  2. Malaclypse deposits 1 wei of tokens;
  3. In the deposit function (Vault.sol#L134), the amount of shares is calculated using the convertToShares() function:
function convertToShares(uint256 assets) public view returns (uint256) {
    uint256 supply = totalSupply(); // Saves an extra SLOAD if totalSupply is non-zero.

    return
        supply == 0
            ? assets
            : assets.mulDiv(supply, totalAssets(), Math.Rounding.Down);
}
  1. Since Malaclypse is the first depositor (totalSupply is 0), she gets 1 share (1 wei);
  2. Malaclypse then sends 9999999999999999999 tokens (10e18 - 1) to the vault;
  3. The price of 1 share is 10 tokens now: Malaclypse is the only depositor in the vault, she's holding 1 wei of shares, and the balance of the pool is 10 tokens;
  4. Alice deposits 19 tokens and gets only 1 share due to the rounding in the convertToShares() function: 19e18 * 1 / 10e18 == 1;
  5. Malaclypse redeems her share and gets a half of the deposited assets, 14.5 tokens (less the withdrawal fee);
  6. Alice redeems her share and gets only 14.5 (less the withdrawal fee), instead of the 19 he deposited.

Tools Used

Manual Review

  1. In the deposit() functions, consider requiring a reasonably high minimal amount of assets during first deposit. The amount needs to be high enough to mint many shares to reduce the rounding error and low enough to be affordable to users.
  2. On the first deposit, consider minting a fixed and high amount of shares, irrespective of the deposited amount.
  3. Consider seeding the pools during deployment. This needs to be done in the deployment transactions to avoiding front-running attacks. The amount needs to be high enough to reduce the rounding error.
  4. Consider sending first 1000 wei of shares to the zero address. This will significantly increase the cost of the attack by forcing an attacker to pay 1000 times of the share price they want to set. For a well-intended user, 1000 wei of shares is a negligible amount that won't diminish their share significantly.

#0 - c4-judge

2023-02-16T03:30:55Z

dmvt marked the issue as duplicate of #15

#1 - c4-sponsor

2023-02-18T11:54:49Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T00:11:01Z

dmvt marked the issue as satisfactory

Findings Information

🌟 Selected for report: 0xNazgul

Also found by: Deivitto

Labels

bug
2 (Med Risk)
disagree with severity
primary issue
selected for report
sponsor confirmed
M-11

Awards

678.8223 USDC - $678.82

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/VaultController.sol#L238

Vulnerability details

Impact

Across the VaultController.sol there are many external calls to the AdminProxy.sol via execute(). Looking at the execute() function in AdminProxy.sol:

function execute(address target, bytes calldata callData)
    external
    onlyOwner 
    returns (bool success, bytes memory returndata) 
{
    return target.call(callData);
}

As one can see it does a call to the target contract with the provided callData. Going back to the VaultController.sol the success of the call is check and reverts if unsuccessful. However, in one instance this check is missed and could cause issues.

Proof of Concept

Looking at that specific instance:

function __deployAdapter(
    DeploymentArgs memory adapterData,
    bytes memory baseAdapterData,
    IDeploymentController _deploymentController
  ) internal returns (address adapter) {
    (bool success, bytes memory returnData) = adminProxy.execute(
      address(_deploymentController),
      abi.encodeWithSelector(DEPLOY_SIG, ADAPTER, adapterData.id, _encodeAdapterData(adapterData, baseAdapterData))
    );
    if (!success) revert UnderlyingError(returnData);

    adapter = abi.decode(returnData, (address));

    adminProxy.execute(adapter, abi.encodeWithSelector(IAdapter.setPerformanceFee.selector, performanceFee));//@audit unchecked like the others are
}

It is clear that the last call to AdminProxy.sol's execute is not checked.

Tools Used

Manual Review

Consider adding a check similar to how it is done in the rest of the contract.

#0 - c4-judge

2023-02-16T07:50:07Z

dmvt marked the issue as primary issue

#1 - c4-sponsor

2023-02-17T10:14:52Z

RedVeil marked the issue as disagree with severity

#2 - c4-sponsor

2023-02-17T10:14:58Z

RedVeil marked the issue as sponsor confirmed

#3 - c4-judge

2023-02-25T23:34:36Z

dmvt marked the issue as selected for report

Findings Information

🌟 Selected for report: 0xNazgul

Labels

bug
2 (Med Risk)
primary issue
selected for report
sponsor acknowledged
M-12

Awards

1508.4941 USDC - $1,508.49

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L525

Vulnerability details

Impact

Currently in Vault.sol there are four different fee types:

  1. Deposit
  2. Withdrawal
  3. Management
  4. Performance

There is a proper check to make sure that individually none of them are >= 1e18. However, they can total to more than 1e18 and cause unsuspecting users to pay more than they may want to.

Proof of Concept

Just taking the deposit and withdrawal fees into account say both of them are set to 5e17, totaling to 1e18. If a user were to than deposit and withdraw that would be 100%. Add in the other two fees and this situation gets even worse.

Tools Used

Manual Review

Consider making sure that the total of all four fee types to be less than 1e18 instead of individually.

#0 - c4-sponsor

2023-02-17T13:47:11Z

RedVeil marked the issue as sponsor acknowledged

#1 - c4-judge

2023-02-25T23:33:18Z

dmvt marked the issue as selected for report

#2 - c4-judge

2023-03-01T01:08:19Z

dmvt marked the issue as primary issue

Awards

4.5833 USDC - $4.58

Labels

bug
2 (Med Risk)
partial-50
sponsor confirmed
duplicate-503

External Links

Lines of code

https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/beefy/BeefyAdapter.sol#L17 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L88 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/adapter/abstracts/AdapterBase.sol#L154 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/utils/MultiRewardStaking.sol#L26 https://github.com/code-423n4/2023-01-popcorn//blob/main/src/vault/Vault.sol#L26

Vulnerability details

Impact

There are ERC20 tokens that may make certain customizations to their ERC20 contracts. One type of these tokens is deflationary tokens that charge a certain fee for every transfer() or transferFrom(). Others are rebasing tokens that increase in value over time like Aave's aTokens (balanceOf changes over time).

Proof of Concept

Across a multiple different functions in all of the contracts will will store the entire amount but with fee-on-transfer tokens, fewer tokens will be transferred which leads to inconsistencies.

Tools Used

Manual Review

Consider checking actual balance of the contract or ensure that the protocol never uses rebasing or tokens with fee-on transfer.

#0 - c4-judge

2023-02-16T07:07:26Z

dmvt marked the issue as duplicate of #44

#1 - c4-sponsor

2023-02-18T11:48:57Z

RedVeil marked the issue as sponsor confirmed

#2 - c4-judge

2023-02-23T00:45:30Z

dmvt marked the issue as partial-50

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