Popcorn contest - SleepingBugs'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: 158/169

Findings: 1

Award: $35.48

🌟 Selected for report: 0

🚀 Solo Findings: 0

Low

initialize() function can be called anybody

initialize() function can be called anybody when the contract is not initialized. Therefore it can be front-runned.

The attacker can initialize the contract before the legitimate deployer, hoping that the victim continues to use the same contract.

In the best case for the victim, they notice it and have to redeploy their contract costing gas.

More importantly, if someone else runs this function, they will have full authority because of the __Ownable_init() function.

Code Snippets

initialize() function not mentioned to be called by factory:

Mentioned to be called by factory:

Recommendation

Add a control that makes initialize() only call by the Deployer Contract;

+ if (msg.sender != DEPLOYER_ADDRESS) {revert NotDeployer();}

Use the constructor to initialize non-proxied contracts.

For initializing proxy contracts deploy contracts using a factory contract that immediately calls initialize after deployment or make sure to call it immediately after deployment and verify the transaction succeeded.

Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions

Impact

For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments" (quote OpenZeppelin). Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts, potentially causing loss of user fund or cause the contract to malfunction completely. See.

For a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.

Vulnerability Details

In the following context of the upgradeable contracts they are expected to use gaps for avoiding collision:

  • ERC20Upgradeable
  • ERC4626Upgradeable
  • OwnedUpgradeable
  • PausableUpgradeable
  • ReentrancyGuardUpgradeable

However, none of these contracts contain storage gap. The storage gap is essential for upgradeable contract because "It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Refer to the bottom part of this article.

If a contract inheriting from a base contract contains additional variable, then the base contract cannot be upgraded to include any additional variable, because it would overwrite the variable declared in its child contract. This greatly limits contract upgradeability.

Code Snippet

  • MultiRewardStaking.sol#L26 contract MultiRewardStaking is ERC4626Upgradeable, OwnedUpgradeable {

  • Vault.sol#L27 ERC20Upgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable, OwnedUpgradeable

  • AdapterBase.sol#L28 ERC4626Upgradeable, PausableUpgradeable, OwnedUpgradeable, ReentrancyGuardUpgradeable,

Tools Used

Manual analysis

Recommendation

Recommend adding appropriate storage gap at the end of upgradeable contracts such as the below.

uint256[50] private __gap;

Reference OpenZeppelin upgradeable contract templates.

Return value not being checked

Details

Return values not being checked may lead into unexpected behaviors with functions. Not events/Error are being emitted if that fails, so functions would be called even of not being working as expect.

Code Snippet

deposit

redeem

withdraw

rejected

Recommendation

Check values and revert/emit events if needed

Informational

Missing inheritance

Vault should inherit from IPausable

Constants should be defined rather than using magic numbers

Use of 1e18, 1e17, 2e17... can be declared as constants

Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10 ** 18)

Recommendation

Consider replacing 10 ** value notation to scientific notation

Inconsistent spacing in comments

Some lines use // x and some use //x. Following the common style comment should be as follows:

-uint256 public genericDeclaration; //generic comment without space`
+uint256 public genericDeclaration; // generic comment with space`
  • IBeefy.sol#L17 //Returns total balance of underlying token in the vault and its strategies

No same value input control

This is done in other fragments of the code but not here

-Vault.sol#L553

-Vault.sol#L629-L636

-VaultController.sol#L751-L758

-VaultController.sol#L791-L798

-AdapterBase.sol#L500-L506

-AdapterBase.sol#L549-L556

Recommendation

Add code like this:

if (_newCoolAddress == newCoolAddress) revert ADDRESS_SAME();

Maximum line length exceeded

Usually lines in source code are limited to 80 characters. Today’s screens are much larger so it’s reasonable to stretch this in some cases. Solidity newer guidelines suggest 120 characters. Reference: Long lines should be wrapped to conform with Solidity Style guidelines

Following lines with more than 120:

#0 - c4-judge

2023-02-28T18:18:58Z

dmvt marked the issue as grade-b

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