Popcorn contest - IllIllI'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: 38/169

Findings: 2

Award: $467.36

QA:
grade-a
Gas:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

Summary

Low Risk Issues

IssueInstances
[L‑01]Unchecked return value of low level call()/delegatecall()2
[L‑02]Upgradeable contract not initialized2
[L‑03]Loss of precision2
[L‑04]Signatures vulnerable to malleability attacks3
[L‑05]Owner can renounce while system is paused2
[L‑06]abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()1
[L‑07]Open TODOs1

Total: 13 instances over 7 issues

Non-critical Issues

IssueInstances
[N‑01]Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions2
[N‑02]Missing initializer modifier on constructor1
[N‑03]Unused file1
[N‑04]constants should be defined rather than using magic numbers36
[N‑05]Events that mark critical parameter changes should contain both the old and the new value2
[N‑06]Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18)1
[N‑07]Lines are too long3
[N‑08]Variable names that consist of all capital letters should be reserved for constant/immutable variables9
[N‑09]Non-library/interface files should use fixed compiler versions, not floating ones16
[N‑10]Typos8
[N‑11]File is missing NatSpec14
[N‑12]NatSpec is incomplete18
[N‑13]Not using the named return variables anywhere in the function is confusing3
[N‑14]Consider using delete rather than assigning zero to clear values1
[N‑15]Contracts should have full test coverage1
[N‑16]Large or complicated code bases should implement fuzzing tests1
[N‑17]Function ordering does not follow the Solidity style guide33
[N‑18]Contract does not follow the Solidity style guide's suggested layout ordering35
[N‑19]Interfaces should be indicated with an I prefix in the contract name1

Total: 186 instances over 19 issues

Low Risk Issues

[L‑01] Unchecked return value of low level call()/delegatecall()

There are 2 instances of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

444               address(strategy).delegatecall(
445                   abi.encodeWithSignature("harvest()")
446:              );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L444-L446

File: src/vault/AdminProxy.sol

24:       return target.call(callData);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/AdminProxy.sol#L24

[L‑02] Upgradeable contract not initialized

Upgradeable contracts are initialized via an initializer function rather than by a constructor. Leaving such a contract uninitialized may lead to it being taken over by a malicious user

There are 2 instances of this issue:

File: src/vault/Vault.sol

/// @audit missing __ReentrancyGuard_init()
/// @audit missing __Pausable_init()
26:   contract Vault is

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

[L‑03] Loss of precision

Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator

There are 2 instances of this issue:

File: src/utils/MultiRewardStaking.sol

359:      return (block.timestamp + (amount / uint256(rewardsPerSecond))).safeCastTo32();

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

File: src/vault/Vault.sol

433                   ? managementFee.mulDiv(
434                       totalAssets() * (block.timestamp - feesUpdatedAt),
435                       SECONDS_PER_YEAR,
436                       Math.Rounding.Down
437:                  ) / 1e18

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L433-L437

[L‑04] Signatures vulnerable to malleability attacks

ecrecover() accepts as valid, two versions of signatures, meaning an attacker can use the same signature twice. Consider adding checks for signature malleability, or using OpenZeppelin's ECDSA library to perform the extra checks necessary in order to prevent this attack.

There are 3 instances of this issue:

File: src/utils/MultiRewardStaking.sol

459         address recoveredAddress = ecrecover(
460           keccak256(
461             abi.encodePacked(
462               "\x19\x01",
463               DOMAIN_SEPARATOR(),
464               keccak256(
465                 abi.encode(
466                   keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
467                   owner,
468                   spender,
469                   value,
470                   nonces[owner]++,
471                   deadline
472                 )
473               )
474             )
475           ),
476           v,
477           r,
478           s
479:        );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L459-L479

File: src/vault/adapter/abstracts/AdapterBase.sol

646               address recoveredAddress = ecrecover(
647                   keccak256(
648                       abi.encodePacked(
649                           "\x19\x01",
650                           DOMAIN_SEPARATOR(),
651                           keccak256(
652                               abi.encode(
653                                   keccak256(
654                                       "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
655                                   ),
656                                   owner,
657                                   spender,
658                                   value,
659                                   nonces[owner]++,
660                                   deadline
661                               )
662                           )
663                       )
664                   ),
665                   v,
666                   r,
667                   s
668:              );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L646-L668

File: src/vault/Vault.sol

678               address recoveredAddress = ecrecover(
679                   keccak256(
680                       abi.encodePacked(
681                           "\x19\x01",
682                           DOMAIN_SEPARATOR(),
683                           keccak256(
684                               abi.encode(
685                                   keccak256(
686                                       "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
687                                   ),
688                                   owner,
689                                   spender,
690                                   value,
691                                   nonces[owner]++,
692                                   deadline
693                               )
694                           )
695                       )
696                   ),
697                   v,
698                   r,
699                   s
700:              );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L678-L700

[L‑05] Owner can renounce while system is paused

The contract owner or single user with a role is not prevented from renouncing the role/ownership while the contract is paused, which would cause any user assets stored in the protocol, to be locked indefinitely

There are 2 instances of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

574       function pause() external onlyOwner {
575           _protocolWithdraw(totalAssets(), totalSupply());
576           // Update the underlying balance to prevent inflation attacks
577           underlyingBalance = 0;
578           _pause();
579:      }

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L574-L579

File: src/vault/Vault.sol

643       function pause() external onlyOwner {
644           _pause();
645:      }

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L643-L645

[L‑06] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()

Use abi.encode() instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456) => 0x123456 => abi.encodePacked(0x1,0x23456), but abi.encode(0x123,0x456) => 0x0...1230...456). "Unless there is a compelling reason, abi.encode should be preferred". If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead.

If all arguments are strings and or bytes, bytes.concat() should be used instead

There is 1 instance of this issue:

File: src/vault/Vault.sol

93            contractName = keccak256(
94                abi.encodePacked("Popcorn", name(), block.timestamp, "Vault")
95:           );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L93-L95

[L‑07] Open TODOs

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment

There is 1 instance of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

516:      // TODO use deterministic fee recipient proxy

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L516

Non-critical Issues

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

See this link 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.

There are 2 instances of this issue:

File: src/utils/MultiRewardStaking.sol

26:   contract MultiRewardStaking is ERC4626Upgradeable, OwnedUpgradeable {

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

File: src/vault/Vault.sol

26    contract Vault is
27        ERC20Upgradeable,
28        ReentrancyGuardUpgradeable,
29        PausableUpgradeable,
30:       OwnedUpgradeable

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L26-L30

[N‑02] Missing initializer modifier on constructor

OpenZeppelin recommends that the initializer modifier be applied to constructors in order to avoid potential griefs, social engineering, or exploits. Ensure that the modifier is applied to the implementation contract. If the default constructor is currently being used, it should be changed to be an explicit one with the modifier applied.

There is 1 instance of this issue:

File: src/vault/Vault.sol

28:       ReentrancyGuardUpgradeable,

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

[N‑03] Unused file

The file is never imported by any other source file. If the file is needed for tests, it should be moved to a test directory

There is 1 instance of this issue:

File: src/interfaces/IEIP165.sol

1:    // SPDX-License-Identifier: AGPL-3.0-only

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/IEIP165.sol#L1

[N‑04] constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

There are 36 instances of this issue:

File: src/interfaces/vault/IStrategy.sol

/// @audit 8
9:      function verifyAdapterSelectorCompatibility(bytes4[8] memory sigs) external;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IStrategy.sol#L9

File: src/interfaces/vault/ITemplateRegistry.sol

/// @audit 8
20:     bytes4[8] requiredSigs;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/ITemplateRegistry.sol#L20

File: src/interfaces/vault/IVaultRegistry.sol

/// @audit 8
17:     address[8] swapTokenAddresses;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IVaultRegistry.sol#L17

File: src/utils/MultiRewardEscrow.sol

/// @audit 1e18
108:        uint256 fee = Math.mulDiv(amount, feePerc, 1e18);

/// @audit 1e17
211:        if (tokenFees[i] >= 1e17) revert DontGetGreedy(tokenFees[i]);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L108

File: src/utils/MultiRewardStaking.sol

/// @audit 1e18
197:      uint256 escrowed = rewardAmount.mulDiv(uint256(escrowInfo.escrowPercentage), 1e18, Math.Rounding.Down);

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

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit 8
64:               bytes4[8] memory _requiredSigs,

/// @audit 8
68:                   (address, address, address, uint256, bytes4[8], bytes)

/// @audit 1e18
85:           highWaterMark = 1e18;

/// @audit 8
479:      function _verifyAndSetupStrategy(bytes4[8] memory requiredSigs) internal {

/// @audit 1e18
531:          uint256 shareValue = convertToAssets(1e18);

/// @audit 1e36
538:                      1e36,

/// @audit 2e17
551:          if (newFee > 2e17) revert InvalidPerformanceFee(newFee);

/// @audit 1e18
565:          uint256 shareValue = convertToAssets(1e18);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L64

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit 8
41:               (address, address, address, uint256, bytes4[8], bytes)

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L41

File: src/vault/VaultController.sol

/// @audit 8
210:      bytes4[8] memory requiredSigs;

/// @audit 2e17
753:      if (newFee > 2e17) revert InvalidPerformanceFee(newFee);

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

File: src/vault/Vault.sol

/// @audit 1e18
144:              assets.mulDiv(uint256(fees.deposit), 1e18, Math.Rounding.Down)

/// @audit 1e18
183:              1e18 - depositFee,

/// @audit 1e18
224:              1e18 - withdrawalFee,

/// @audit 1e18
265:              1e18,

/// @audit 1e18
330:                  assets.mulDiv(uint256(fees.deposit), 1e18, Math.Rounding.Down)

/// @audit 1e18
345:              1e18 - depositFee,

/// @audit 1e18
367:              1e18 - withdrawalFee,

/// @audit 1e18
389:              1e18,

/// @audit 1e18
437:                  ) / 1e18

/// @audit 1e18
449:          uint256 shareValue = convertToAssets(1e18);

/// @audit 1e36
456:                      1e36,

/// @audit 1e18
484:          uint256 shareValue = convertToAssets(1e18);

/// @audit 1e18
498:          highWaterMark = convertToAssets(1e18);

/// @audit 1e18
527:              newFees.deposit >= 1e18 ||

/// @audit 1e18
528:              newFees.withdrawal >= 1e18 ||

/// @audit 1e18
529:              newFees.management >= 1e18 ||

/// @audit 1e18
530:              newFees.performance >= 1e18

/// @audit 3
619:      uint256 public quitPeriod = 3 days;

/// @audit 7
630:          if (_quitPeriod < 1 days || _quitPeriod > 7 days)

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

[N‑05] Events that mark critical parameter changes should contain both the old and the new value

This should especially be done if the new value is not required to be different from the old value

There are 2 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit setFees()
214:        emit FeeSet(tokens[i], tokenFees[i]);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L214

File: src/vault/Vault.sol

/// @audit setQuitPeriod()
635:          emit QuitPeriodSet(quitPeriod);

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

[N‑06] Use scientific notation (e.g. 1e18) rather than exponentiation (e.g. 10**18)

While the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist

There is 1 instance of this issue:

File: src/vault/adapter/yearn/YearnAdapter.sol

25:       uint256 constant DEGRADATION_COEFFICIENT = 10**18;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L25

[N‑07] Lines are too long

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. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

There are 3 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

49:      * @dev there is no check to ensure that all escrows are owned by the same account. Make sure to account for this either by only sending ids for a specific account or by filtering the Escrows by account later on.

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L49

File: src/utils/MultiRewardStaking.sol

7:    import { ERC4626Upgradeable, ERC20Upgradeable, IERC20Upgradeable as IERC20, IERC20MetadataUpgradeable as IERC20Metadata } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";

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

File: src/vault/adapter/abstracts/AdapterBase.sol

6:    import {ERC4626Upgradeable, IERC20Upgradeable as IERC20, IERC20MetadataUpgradeable as IERC20Metadata, ERC20Upgradeable as ERC20} from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L6

[N‑08] Variable names that consist of all capital letters should be reserved for constant/immutable variables

If the variable needs to be different based on which class it comes from, a view/pure function should be used instead (e.g. like this).

There are 9 instances of this issue:

File: src/interfaces/IMultiRewardStaking.sol

16:     uint64 ONE;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/IMultiRewardStaking.sol#L16

File: src/utils/MultiRewardStaking.sol

274:      uint64 ONE = (10**IERC20Metadata(address(rewardToken)).decimals()).safeCastTo64();

438:    uint256 internal INITIAL_CHAIN_ID;

439:    bytes32 internal INITIAL_DOMAIN_SEPARATOR;

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

File: src/vault/adapter/abstracts/AdapterBase.sol

517:      address FEE_RECIPIENT = address(0x4444);

625:      uint256 internal INITIAL_CHAIN_ID;

626:      bytes32 internal INITIAL_DOMAIN_SEPARATOR;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L517

File: src/vault/Vault.sol

657:      uint256 internal INITIAL_CHAIN_ID;

658:      bytes32 internal INITIAL_DOMAIN_SEPARATOR;

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

[N‑09] Non-library/interface files should use fixed compiler versions, not floating ones

There are 16 instances of this issue:

File: src/utils/EIP165.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/EIP165.sol#L4

File: src/utils/MultiRewardEscrow.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L4

File: src/utils/MultiRewardStaking.sol

4:    pragma solidity ^0.8.15;

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

File: src/vault/adapter/abstracts/OnlyStrategy.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/OnlyStrategy.sol#L4

File: src/vault/adapter/abstracts/WithRewards.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/WithRewards.sol#L4

File: src/vault/adapter/beefy/BeefyAdapter.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/BeefyAdapter.sol#L4

File: src/vault/adapter/yearn/YearnAdapter.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L4

File: src/vault/AdminProxy.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/AdminProxy.sol#L4

File: src/vault/CloneFactory.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneFactory.sol#L4

File: src/vault/CloneRegistry.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L4

File: src/vault/DeploymentController.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L4

File: src/vault/PermissionRegistry.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L4

File: src/vault/TemplateRegistry.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L4

File: src/vault/VaultController.sol

3:    pragma solidity ^0.8.15;

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

File: src/vault/VaultRegistry.sol

4:    pragma solidity ^0.8.15;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L4

File: src/vault/Vault.sol

4:    pragma solidity ^0.8.15;

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

[N‑10] Typos

There are 8 instances of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit overriden
24:    * All specific interactions for the underlying protocol need to be overriden in the actual implementation.

/// @audit aftwards
477:       * @dev It aftwards sets up anything required by the strategy to call `harvest()` like approvals etc.

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L24

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit profts
100:      /// @notice The amount of assets that are free to be withdrawn from the yVault after locked profts.

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L100

File: src/vault/AdminProxy.sol

/// @audit Ownes
11:    * @notice  Ownes contracts in the vault ecosystem to allow for easy ownership changes.

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/AdminProxy.sol#L11

File: src/vault/VaultController.sol

/// @audit ownes
47:      * @param _adminProxy `AdminProxy` ownes contracts in the vault ecosystem.

/// @audit auxiliery
87:      * @dev This function is the one stop solution to create a new vault with all necessary admin functions or auxiliery contracts.

/// @audit DEPLYOMENT
816:                        DEPLYOMENT CONTROLLER LOGIC

/// @audit auxilary
829:     * @notice Sets a new `DeploymentController` and saves its auxilary contracts. Caller must be owner.

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

[N‑11] File is missing NatSpec

There are 14 instances of this issue:

File: src/interfaces/IEIP165.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/IEIP165.sol

File: src/interfaces/vault/IAdapter.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IAdapter.sol

File: src/interfaces/vault/IAdminProxy.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IAdminProxy.sol

File: src/interfaces/vault/ICloneFactory.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/ICloneFactory.sol

File: src/interfaces/vault/ICloneRegistry.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/ICloneRegistry.sol

File: src/interfaces/vault/IDeploymentController.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IDeploymentController.sol

File: src/interfaces/vault/IERC4626.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IERC4626.sol

File: src/interfaces/vault/IPermissionRegistry.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IPermissionRegistry.sol

File: src/interfaces/vault/IStrategy.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IStrategy.sol

File: src/interfaces/vault/IVaultController.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IVaultController.sol

File: src/interfaces/vault/IWithRewards.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IWithRewards.sol

File: src/utils/EIP165.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/EIP165.sol

File: src/vault/adapter/beefy/IBeefy.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/IBeefy.sol

File: src/vault/adapter/yearn/IYearn.sol

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/IYearn.sol

[N‑12] NatSpec is incomplete

There are 18 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit Missing: '@return'
49       * @dev there is no check to ensure that all escrows are owned by the same account. Make sure to account for this either by only sending ids for a specific account or by filtering the Escrows by account later on.
50       */
51:     function getEscrows(bytes32[] calldata escrowIds) external view returns (Escrow[] memory) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L49-L51

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit Missing: '@return'
108        * @param receiver Receiver of the shares.
109        */
110       function deposit(uint256 assets, address receiver)
111           public
112           virtual
113           override
114:          returns (uint256)

/// @audit Missing: '@return'
127        * @param receiver Receiver of the shares.
128        */
129       function mint(uint256 shares, address receiver)
130           public
131           virtual
132           override
133:          returns (uint256)

/// @audit Missing: '@return'
171        * @param owner Owner of the shares.
172        */
173       function withdraw(
174           uint256 assets,
175           address receiver,
176           address owner
177:      ) public virtual override returns (uint256) {

/// @audit Missing: '@return'
191        * @param owner Owner of the shares.
192        */
193       function redeem(
194           uint256 shares,
195           address receiver,
196           address owner
197:      ) public virtual override returns (uint256) {

/// @audit Missing: '@param address'
399       /**
400        * @return Maximum amount of vault shares that may be minted to given address. Delegates to adapter.
401        * @dev Return 0 if paused since no further deposits are allowed.
402        * @dev Override this function if the underlying protocol has a unique deposit logic and/or deposit fees.
403        */
404       function maxDeposit(address)
405           public
406           view
407           virtual
408           override
409:          returns (uint256)

/// @audit Missing: '@param address'
414       /**
415        * @return Maximum amount of vault shares that may be minted to given address. Delegates to adapter.
416        * @dev Return 0 if paused since no further deposits are allowed.
417        * @dev Override this function if the underlying protocol has a unique deposit logic and/or deposit fees.
418        */
419:      function maxMint(address) public view virtual override returns (uint256) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L108-L114

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit Missing: '@param bytes'
27        /**
28         * @notice Initialize a new Yearn Adapter.
29         * @param adapterInitData Encoded data for the base adapter initialization.
30         * @param externalRegistry Yearn registry address.
31         * @dev This function is called by the factory contract when deploying a new vault.
32         * @dev The yearn registry will be used given the `asset` from `adapterInitData` to find the latest yVault.
33         */
34        function initialize(
35            bytes memory adapterInitData,
36            address externalRegistry,
37            bytes memory
38:       ) external initializer {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L27-L38

File: src/vault/CloneFactory.sol

/// @audit Missing: '@return'
37       * @param data The data to pass to the clone's initializer.
38       */
39:     function deploy(Template calldata template, bytes calldata data) external onlyOwner returns (address clone) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneFactory.sol#L37-L39

File: src/vault/DeploymentController.sol

/// @audit Missing: '@return'
97       * @dev Registers the clone in `CloneRegistry`.
98       */
99      function deploy(
100       bytes32 templateCategory,
101       bytes32 templateId,
102       bytes calldata data
103:    ) external onlyOwner returns (address clone) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L97-L103

File: src/vault/VaultController.sol

/// @audit Missing: '@return'
87       * @dev This function is the one stop solution to create a new vault with all necessary admin functions or auxiliery contracts.
88       */
89      function deployVault(
90        VaultInitParams memory vaultData,
91        DeploymentArgs memory adapterData,
92        DeploymentArgs memory strategyData,
93        address staking,
94        bytes memory rewardsData,
95        VaultMetadata memory metadata,
96        uint256 initialDeposit
97:     ) external canCreate returns (address vault) {

/// @audit Missing: '@param initialDeposit'
/// @audit Missing: '@return'
180     /**
181      * @notice Deploy a new Adapter with our without a strategy. Caller must be owner.
182      * @param asset Asset which will be used by the adapter.
183      * @param adapterData Encoded adapter init data.
184      * @param strategyData Encoded strategy init data.
185      */
186     function deployAdapter(
187       IERC20 asset,
188       DeploymentArgs memory adapterData,
189       DeploymentArgs memory strategyData,
190       uint256 initialDeposit
191:    ) external canCreate returns (address adapter) {

/// @audit Missing: '@return'
276      * @dev Deploys `MultiRewardsStaking` based on the latest templateTemplateKey.
277      */
278:    function deployStaking(IERC20 asset) external canCreate returns (address) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L87-L97

File: src/vault/Vault.sol

/// @audit Missing: '@param caller'
398       /// @return Maximum amount of underlying `asset` token that may be deposited for a given address. Delegates to adapter.
399:      function maxDeposit(address caller) public view returns (uint256) {

/// @audit Missing: '@param caller'
403       /// @return Maximum amount of vault shares that may be minted to given address. Delegates to adapter.
404:      function maxMint(address caller) external view returns (uint256) {

/// @audit Missing: '@param caller'
408       /// @return Maximum amount of underlying `asset` token that can be withdrawn by `caller` address. Delegates to adapter.
409:      function maxWithdraw(address caller) external view returns (uint256) {

/// @audit Missing: '@param caller'
413       /// @return Maximum amount of shares that may be redeemed by `caller` address. Delegates to adapter.
414:      function maxRedeem(address caller) external view returns (uint256) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L398-L399

[N‑13] Not using the named return variables anywhere in the function is confusing

Consider changing the variable to be an unnamed one

There are 3 instances of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit shares
380       function _convertToShares(uint256 assets, Math.Rounding rounding)
381           internal
382           view
383           virtual
384           override
385:          returns (uint256 shares)

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L380-L385

File: src/vault/AdminProxy.sol

/// @audit success
/// @audit returndata
19      function execute(address target, bytes calldata callData)
20        external
21        onlyOwner
22:       returns (bool success, bytes memory returndata)

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/AdminProxy.sol#L19-L22

[N‑14] Consider using delete rather than assigning zero to clear values

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic

There is 1 instance of this issue:

File: src/utils/MultiRewardStaking.sol

186:        accruedRewards[user][_rewardTokens[i]] = 0;

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

[N‑15] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.

There is 1 instance of this issue:

File: Various Files

[N‑16] Large or complicated code bases should implement fuzzing tests

Large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts, should implement fuzzing tests. Fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and fuzzers, with properly and extensively-written invariants, can close this testing gap significantly.

There is 1 instance of this issue:

File: Various Files

[N‑17] Function ordering does not follow the Solidity style guide

According to the Solidity style guide, functions should be laid out in the following order :constructor(), receive(), fallback(), external, public, internal, private, but the cases below do not follow this pattern

There are 33 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit _getClaimableAmount() came earlier
207:    function setFees(IERC20[] memory tokens, uint256[] memory tokenFees) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L207

File: src/utils/MultiRewardStaking.sol

/// @audit decimals() came earlier
75:     function deposit(uint256 _amount) external returns (uint256) {

/// @audit _transfer() came earlier
170:    function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) {

/// @audit _lockToken() came earlier
243     function addRewardToken(
244       IERC20 rewardToken,
245       uint160 rewardsPerSecond,
246       uint256 amount,
247       bool useEscrow,
248       uint192 escrowPercentage,
249       uint32 escrowDuration,
250       uint32 offset
251:    ) external onlyOwner {

/// @audit _calcRewardsEnd() came earlier
362:    function getAllRewardsTokens() external view returns (IERC20[] memory) {

/// @audit _accrueUser() came earlier
445     function permit(
446       address owner,
447       address spender,
448       uint256 value,
449       uint256 deadline,
450       uint8 v,
451       bytes32 r,
452:      bytes32 s

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

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit __AdapterBase_init() came earlier
89        function decimals()
90            public
91            view
92            override(IERC20Metadata, ERC20)
93:           returns (uint8)

/// @audit _deposit() came earlier
173       function withdraw(
174           uint256 assets,
175           address receiver,
176           address owner
177:      ) public virtual override returns (uint256) {

/// @audit _withdraw() came earlier
247:      function totalAssets() public view override returns (uint256) {

/// @audit _underlyingBalance() came earlier
271       function convertToUnderlyingShares(uint256 assets, uint256 shares)
272           public
273           view
274           virtual
275:          returns (uint256)

/// @audit _previewDeposit() came earlier
304       function previewMint(uint256 shares)
305           public
306           view
307           virtual
308           override
309:          returns (uint256)

/// @audit _previewMint() came earlier
329       function previewWithdraw(uint256 assets)
330           public
331           view
332           virtual
333           override
334:          returns (uint256)

/// @audit _previewWithdraw() came earlier
353       function previewRedeem(uint256 shares)
354           public
355           view
356           virtual
357           override
358:          returns (uint256)

/// @audit _convertToShares() came earlier
404       function maxDeposit(address)
405           public
406           view
407           virtual
408           override
409:          returns (uint256)

/// @audit _verifyAndSetupStrategy() came earlier
500:      function setHarvestCooldown(uint256 newCooldown) external onlyOwner {

/// @audit setPerformanceFee() came earlier
574:      function pause() external onlyOwner {

/// @audit _protocolWithdraw() came earlier
610       function supportsInterface(bytes4 interfaceId)
611           public
612           view
613           virtual
614           override
615:          returns (bool)

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L89-L93

File: src/vault/adapter/beefy/BeefyAdapter.sol

/// @audit _underlyingBalance() came earlier
122       function convertToUnderlyingShares(uint256, uint256 shares)
123           public
124           view
125           override
126:          returns (uint256)

/// @audit convertToUnderlyingShares() came earlier
136:      function rewardTokens() external view override returns (address[] memory) {

/// @audit _protocolWithdraw() came earlier
221:      function claim() public override onlyStrategy {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/BeefyAdapter.sol#L122-L126

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit _calculateLockedProfit() came earlier
129       function convertToUnderlyingShares(uint256, uint256 shares)
130           public
131           view
132           override
133:          returns (uint256)

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L129-L133

File: src/vault/VaultController.sol

/// @audit _handleInitialDeposit() came earlier
186     function deployAdapter(
187       IERC20 asset,
188       DeploymentArgs memory adapterData,
189       DeploymentArgs memory strategyData,
190       uint256 initialDeposit
191:    ) external canCreate returns (address adapter) {

/// @audit _deployStrategy() came earlier
278:    function deployStaking(IERC20 asset) external canCreate returns (address) {

/// @audit _deployStaking() came earlier
313:    function proposeVaultAdapters(address[] calldata vaults, IERC4626[] calldata newAdapter) external {

/// @audit _registerVault() came earlier
408:    function setPermissions(address[] calldata targets, Permission[] calldata newPermissions) external onlyOwner {

/// @audit addStakingRewardsTokens() came earlier
483     function changeStakingRewardsSpeeds(
484       address[] calldata vaults,
485       IERC20[] calldata rewardTokens,
486:      uint160[] calldata rewardsSpeeds

/// @audit _verifyEqualArrayLength() came earlier
723:    function nominateNewAdminProxyOwner(address newOwner) external onlyOwner {

/// @audit _setDeploymentController() came earlier
864:    function setActiveTemplateId(bytes32 templateCategory, bytes32 templateId) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L186-L191

File: src/vault/Vault.sol

/// @audit deposit() came earlier
160:      function mint(uint256 shares) external returns (uint256) {

/// @audit withdraw() came earlier
242:      function redeem(uint256 shares) external returns (uint256) {

/// @audit previewMint() came earlier
358       function previewWithdraw(uint256 assets)
359           external
360           view
361:          returns (uint256 shares)

/// @audit maxDeposit() came earlier
404:      function maxMint(address caller) external view returns (uint256) {

/// @audit accruedPerformanceFee() came earlier
473       function takeManagementAndPerformanceFees()
474           external
475           nonReentrant
476:          takeFees

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

[N‑18] Contract does not follow the Solidity style guide's suggested layout ordering

The style guide says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering

There are 35 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit function getEscrows came earlier
64      mapping(bytes32 => Escrow) public escrows;
65    
66      // User => Escrows
67      mapping(address => bytes32[]) public userEscrowIds;
68      // User => RewardsToken => Escrows
69:     mapping(address => mapping(IERC20 => bytes32[])) public userEscrowIdsByToken;

/// @audit function lock came earlier
136:    event RewardsClaimed(IERC20 indexed token, address indexed account, uint256 amount);

/// @audit function _getClaimableAmount came earlier
191:    address public feeRecipient;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L64-L69

File: src/utils/MultiRewardStaking.sol

/// @audit function _transfer came earlier
157:    IMultiRewardEscrow public escrow;

/// @audit function _lockToken came earlier
208:    IERC20[] public rewardTokens;

/// @audit function getAllRewardsTokens came earlier
371     modifier accrueRewards(address _caller, address _receiver) {
372       IERC20[] memory _rewardTokens = rewardTokens;
373       for (uint8 i; i < _rewardTokens.length; i++) {
374         IERC20 rewardToken = _rewardTokens[i];
375         RewardInfo memory rewards = rewardInfos[rewardToken];
376   
377         if (rewards.rewardsPerSecond > 0) _accrueRewards(rewardToken, _accrueStatic(rewards));
378         _accrueUser(_receiver, rewardToken);
379   
380         // If a deposit/withdraw operation gets called for another user we should accrue for both of them to avoid potential issues like in the Convex-Vulnerability
381         if (_receiver != _caller) _accrueUser(_caller, rewardToken);
382       }
383       _;
384:    }

/// @audit function _accrueUser came earlier
438:    uint256 internal INITIAL_CHAIN_ID;

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

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit function _withdraw came earlier
241:      uint256 internal underlyingBalance;

/// @audit function maxMint came earlier
427:      IStrategy public strategy;

/// @audit function _verifyAndSetupStrategy came earlier
489:      uint256 public harvestCooldown;

/// @audit function setHarvestCooldown came earlier
513:      uint256 public performanceFee;

/// @audit function setPerformanceFee came earlier
559       modifier takeFees() {
560           _;
561   
562           uint256 fee = accruedPerformanceFee();
563           if (fee > 0) _mint(FEE_RECIPIENT, convertToShares(fee));
564   
565           uint256 shareValue = convertToAssets(1e18);
566           if (shareValue > highWaterMark) highWaterMark = shareValue;
567:      }

/// @audit function supportsInterface came earlier
625:      uint256 internal INITIAL_CHAIN_ID;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L241

File: src/vault/CloneFactory.sol

/// @audit function constructor came earlier
29:     event Deployment(address indexed clone);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneFactory.sol#L29

File: src/vault/CloneRegistry.sol

/// @audit function constructor came earlier
28:     mapping(address => bool) public cloneExists;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L28

File: src/vault/PermissionRegistry.sol

/// @audit function constructor came earlier
26:     mapping(address => Permission) public permissions;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L26

File: src/vault/TemplateRegistry.sol

/// @audit function constructor came earlier
31      mapping(bytes32 => mapping(bytes32 => Template)) public templates;
32      mapping(bytes32 => bytes32[]) public templateIds;
33      mapping(bytes32 => bool) public templateExists;
34    
35:     mapping(bytes32 => bool) public templateCategoryExists;

/// @audit function addTemplate came earlier
88      event TemplateEndorsementToggled(
89        bytes32 templateCategory,
90        bytes32 templateId,
91        bool oldEndorsement,
92        bool newEndorsement
93:     );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L31-L35

File: src/vault/VaultController.sol

/// @audit function constructor came earlier
76:     event VaultDeployed(address indexed vault, address indexed staking, address indexed adapter);

/// @audit function changeVaultFees came earlier
387:    IVaultRegistry public vaultRegistry;

/// @audit function fundStakingRewards came earlier
535:    IMultiRewardEscrow public escrow;

/// @audit function _verifyEqualArrayLength came earlier
704     modifier canCreate() {
705       if (
706         permissionRegistry.endorsed(address(1))
707           ? !permissionRegistry.endorsed(msg.sender)
708           : permissionRegistry.rejected(msg.sender)
709       ) revert NotAllowed(msg.sender);
710       _;
711:    }

/// @audit modifier canCreate came earlier
717:    IAdminProxy public adminProxy;

/// @audit function acceptAdminProxyOwnership came earlier
739:    uint256 public performanceFee;

/// @audit function setAdapterPerformanceFees came earlier
779:    uint256 public harvestCooldown;

/// @audit function setAdapterHarvestCooldowns came earlier
819:    IDeploymentController public deploymentController;

/// @audit function _setDeploymentController came earlier
851:    mapping(bytes32 => bytes32) public activeTemplateId;

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

File: src/vault/VaultRegistry.sol

/// @audit function constructor came earlier
28      mapping(address => VaultMetadata) public metadata;
29    
30      // asset to vault addresses
31:     mapping(address => address[]) public vaultsByAsset;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L28-L31

File: src/vault/Vault.sol

/// @audit function decimals came earlier
108       event Deposit(
109           address indexed caller,
110           address indexed owner,
111           uint256 assets,
112           uint256 shares
113:      );

/// @audit function accruedPerformanceFee came earlier
466:      uint256 public highWaterMark = 1e18;

/// @audit function takeManagementAndPerformanceFees came earlier
480       modifier takeFees() {
481           uint256 managementFee = accruedManagementFee();
482           uint256 totalFee = managementFee + accruedPerformanceFee();
483           uint256 currentAssets = totalAssets();
484           uint256 shareValue = convertToAssets(1e18);
485   
486           if (shareValue > highWaterMark) highWaterMark = shareValue;
487   
488           if (managementFee > 0) feesUpdatedAt = block.timestamp;
489   
490           if (totalFee > 0 && currentAssets > 0)
491               _mint(feeRecipient, convertToShares(totalFee));
492   
493           _;
494:      }

/// @audit modifier syncFeeCheckpoint came earlier
505:      VaultFees public fees;

/// @audit function setFeeRecipient came earlier
565:      IERC4626 public adapter;

/// @audit function changeAdapter came earlier
619:      uint256 public quitPeriod = 3 days;

/// @audit function unpause came earlier
657:      uint256 internal INITIAL_CHAIN_ID;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L108-L113

[N‑19] Interfaces should be indicated with an I prefix in the contract name

There is 1 instance of this issue:

File: src/vault/adapter/yearn/IYearn.sol

8:    interface VaultAPI is IERC20 {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/IYearn.sol#L8


Excluded findings

These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness

Summary

Low Risk Issues

IssueInstances
[L‑01]Unsafe use of transfer()/transferFrom() with IERC201
[L‑02]Return values of transfer()/transferFrom() not checked1
[L‑03]safeApprove() is deprecated1
[L‑04]Missing checks for address(0x0) when assigning values to address state variables1

Total: 4 instances over 4 issues

Non-critical Issues

IssueInstances
[N‑01]Return values of approve() not checked5
[N‑02]public functions not called by the contract should be declared external instead4
[N‑03]Event is missing indexed fields27

Total: 36 instances over 3 issues

Low Risk Issues

[L‑01] Unsafe use of transfer()/transferFrom() with IERC20

Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer() and transferFrom() functions on L1 do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20, their function signatures do not match and therefore the calls made, revert (see this link for a test case). Use OpenZeppelin’s SafeERC20's safeTransfer()/safeTransferFrom() instead

There is 1 instance of this issue:

File: src/vault/VaultController.sol

/// @audit (valid but excluded finding)
457:        IERC20(rewardsToken).transferFrom(msg.sender, address(adminProxy), amount);

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

[L‑02] Return values of transfer()/transferFrom() not checked

Not all IERC20 implementations revert() when there's a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment

There is 1 instance of this issue:

File: src/vault/VaultController.sol

/// @audit (valid but excluded finding)
457:        IERC20(rewardsToken).transferFrom(msg.sender, address(adminProxy), amount);

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

[L‑03] safeApprove() is deprecated

Deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance(). If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead. The function may currently work, but if a bug is found in this version of OpenZeppelin, and the version that you're forced to upgrade to no longer has this function, you'll encounter unnecessary delays in porting and testing replacement contracts.

There is 1 instance of this issue:

File: src/utils/MultiRewardStaking.sol

/// @audit (valid but excluded finding)
271:        rewardToken.safeApprove(address(escrow), type(uint256).max);

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

[L‑04] Missing checks for address(0x0) when assigning values to address state variables

There is 1 instance of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit (valid but excluded finding)
31:       feeRecipient = _feeRecipient;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L31

Non-critical Issues

[N‑01] Return values of approve() not checked

Not all IERC20 implementations revert() when there's a failure in approve(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything

There are 5 instances of this issue:

File: src/vault/adapter/beefy/BeefyAdapter.sol

/// @audit (valid but excluded finding)
80:           IERC20(asset()).approve(_beefyVault, type(uint256).max);

/// @audit (valid but excluded finding)
83:               IERC20(_beefyVault).approve(_beefyBooster, type(uint256).max);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/BeefyAdapter.sol#L80

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit (valid but excluded finding)
54:           IERC20(_asset).approve(address(yVault), type(uint256).max);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L54

File: src/vault/VaultController.sol

/// @audit (valid but excluded finding)
171:        asset.approve(address(target), initialDeposit);

/// @audit (valid but excluded finding)
456:        IERC20(rewardsToken).approve(staking, type(uint256).max);

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

[N‑02] public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There are 4 instances of this issue:

File: src/vault/Vault.sol

/// @audit (valid but excluded finding)
323       function previewDeposit(uint256 assets)
324           public
325           view
326:          returns (uint256 shares)

/// @audit (valid but excluded finding)
340:      function previewMint(uint256 shares) public view returns (uint256 assets) {

/// @audit (valid but excluded finding)
380       function previewRedeem(uint256 shares)
381           public
382           view
383:          returns (uint256 assets)

/// @audit (valid but excluded finding)
399:      function maxDeposit(address caller) public view returns (uint256) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L323-L326

[N‑03] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

There are 27 instances of this issue:

File: src/interfaces/vault/IERC4626.sol

/// @audit (valid but excluded finding)
8:      event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IERC4626.sol#L8

File: src/utils/MultiRewardEscrow.sol

/// @audit (valid but excluded finding)
73:     event Locked(IERC20 indexed token, address indexed account, uint256 amount, uint32 duration, uint32 offset);

/// @audit (valid but excluded finding)
136:    event RewardsClaimed(IERC20 indexed token, address indexed account, uint256 amount);

/// @audit (valid but excluded finding)
196:    event FeeSet(IERC20 indexed token, uint256 amount);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L73

File: src/utils/MultiRewardStaking.sol

/// @audit (valid but excluded finding)
159:    event RewardsClaimed(address indexed user, IERC20 rewardToken, uint256 amount, bool escrowed);

/// @audit (valid but excluded finding)
220:    event RewardInfoUpdate(IERC20 rewardToken, uint160 rewardsPerSecond, uint32 rewardsEndTimestamp);

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

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit (valid but excluded finding)
491:      event HarvestCooldownChanged(uint256 oldCooldown, uint256 newCooldown);

/// @audit (valid but excluded finding)
519:      event PerformanceFeeChanged(uint256 oldFee, uint256 newFee);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L491

File: src/vault/CloneRegistry.sol

/// @audit (valid but excluded finding)
33:     event CloneAdded(address clone);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L33

File: src/vault/PermissionRegistry.sol

/// @audit (valid but excluded finding)
28:     event PermissionSet(address target, bool newEndorsement, bool newRejection);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L28

File: src/vault/TemplateRegistry.sol

/// @audit (valid but excluded finding)
38:     event TemplateCategoryAdded(bytes32 templateCategory);

/// @audit (valid but excluded finding)
39:     event TemplateAdded(bytes32 templateCategory, bytes32 templateId, address implementation);

/// @audit (valid but excluded finding)
40:     event TemplateUpdated(bytes32 templateCategory, bytes32 templateId);

/// @audit (valid but excluded finding)
88      event TemplateEndorsementToggled(
89        bytes32 templateCategory,
90        bytes32 templateId,
91        bool oldEndorsement,
92        bool newEndorsement
93:     );

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L38

File: src/vault/VaultController.sol

/// @audit (valid but excluded finding)
741:    event PerformanceFeeChanged(uint256 oldFee, uint256 newFee);

/// @audit (valid but excluded finding)
781:    event HarvestCooldownChanged(uint256 oldCooldown, uint256 newCooldown);

/// @audit (valid but excluded finding)
824:    event DeploymentControllerChanged(address oldController, address newController);

/// @audit (valid but excluded finding)
853:    event ActiveTemplateIdChanged(bytes32 oldKey, bytes32 newKey);

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

File: src/vault/VaultRegistry.sol

/// @audit (valid but excluded finding)
36:     event VaultAdded(address vaultAddress, string metadataCID);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L36

File: src/vault/Vault.sol

/// @audit (valid but excluded finding)
42:       event VaultInitialized(bytes32 contractName, address indexed asset);

/// @audit (valid but excluded finding)
108       event Deposit(
109           address indexed caller,
110           address indexed owner,
111           uint256 assets,
112           uint256 shares
113:      );

/// @audit (valid but excluded finding)
512:      event NewFeesProposed(VaultFees newFees, uint256 timestamp);

/// @audit (valid but excluded finding)
513:      event ChangedFees(VaultFees oldFees, VaultFees newFees);

/// @audit (valid but excluded finding)
514:      event FeeRecipientUpdated(address oldFeeRecipient, address newFeeRecipient);

/// @audit (valid but excluded finding)
569:      event NewAdapterProposed(IERC4626 newAdapter, uint256 timestamp);

/// @audit (valid but excluded finding)
570:      event ChangedAdapter(IERC4626 oldAdapter, IERC4626 newAdapter);

/// @audit (valid but excluded finding)
621:      event QuitPeriodSet(uint256 quitPeriod);

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

#0 - c4-judge

2023-02-28T17:40:37Z

dmvt marked the issue as grade-a

#1 - c4-judge

2023-03-01T22:34:36Z

dmvt marked the issue as selected for report

#2 - dmvt

2023-03-07T12:46:56Z

The report is complete and accurate with the following exceptions: L-06 - Out of Scope (automated finding) N-01 - Should be Low Risk N-02 - Should be Low Risk

Awards

69.8247 USDC - $69.82

Labels

bug
G (Gas Optimization)
grade-b
G-06

External Links

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Remove or replace unused state variables1-
[G‑02]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate3-
[G‑03]State variables only set in the constructor should be declared immutable816776
[G‑04]State variables can be packed into fewer storage slots1-
[G‑05]State variables should be cached in stack variables rather than re-reading them from storage212037
[G‑06]Multiple accesses of a mapping/array should use a local variable cache5210
[G‑07]The result of function calls should be cached rather than re-calling the function1-
[G‑08]<x> += <y> costs more gas than <x> = <x> + <y> for state variables2226
[G‑09]internal functions only called once can be inlined to save gas13260
[G‑10]Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement2170
[G‑11]++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops211260
[G‑12]keccak256() should only need to be called on a specific string literal once9378
[G‑13]Optimize names to save gas26572
[G‑14]Remove unused local variable1-
[G‑15]Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead2-
[G‑16]Using private rather than public for constants, saves gas4-
[G‑17]Superfluous event fields2-
[G‑18]Functions guaranteed to revert when called by normal users can be marked payable9189
[G‑19]Constructors can be marked payable9189
[G‑20]Don't use _msgSender() if not supporting EIP-2771464

Total: 144 instances over 20 issues with 22331 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Remove or replace unused state variables

Saves a storage slot. If the variable is assigned a non-zero value, saves Gsset (20000 gas). If it's assigned a zero value, saves Gsreset (2900 gas). If the variable remains unassigned, there is no gas savings unless the variable is public, in which case the compiler-generated non-payable getter deployment cost is saved. If the state variable is overriding an interface's public function, mark the variable as constant or immutable so that it does not use a storage slot

There is 1 instance of this issue:

File: src/vault/Vault.sol

467:      uint256 public assetsCheckpoint;

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

[G‑02] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 3 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

64      mapping(bytes32 => Escrow) public escrows;
65    
66      // User => Escrows
67      mapping(address => bytes32[]) public userEscrowIds;
68      // User => RewardsToken => Escrows
69:     mapping(address => mapping(IERC20 => bytes32[])) public userEscrowIdsByToken;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L64-L69

File: src/utils/MultiRewardStaking.sol

216     mapping(address => mapping(IERC20 => uint256)) public userIndex;
217     // user => rewardToken -> accruedRewards
218:    mapping(address => mapping(IERC20 => uint256)) public accruedRewards;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L216-L218

File: src/vault/TemplateRegistry.sol

31      mapping(bytes32 => mapping(bytes32 => Template)) public templates;
32      mapping(bytes32 => bytes32[]) public templateIds;
33      mapping(bytes32 => bool) public templateExists;
34    
35:     mapping(bytes32 => bool) public templateCategoryExists;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L31-L35

[G‑03] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values.

There are 8 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit feeRecipient (constructor)
31:       feeRecipient = _feeRecipient;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L31

File: src/vault/DeploymentController.sol

/// @audit cloneFactory (constructor)
41:       cloneFactory = _cloneFactory;

/// @audit cloneRegistry (constructor)
42:       cloneRegistry = _cloneRegistry;

/// @audit templateRegistry (constructor)
43:       templateRegistry = _templateRegistry;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L41

File: src/vault/VaultController.sol

/// @audit vaultRegistry (constructor)
62:       vaultRegistry = _vaultRegistry;

/// @audit escrow (constructor)
64:       escrow = _escrow;

/// @audit adminProxy (constructor)
61:       adminProxy = _adminProxy;

/// @audit permissionRegistry (constructor)
63:       permissionRegistry = _permissionRegistry;

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

[G‑04] State variables can be packed into fewer storage slots

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper

There is 1 instance of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit Variable ordering with 11 slots instead of the current 12:
///           uint256(32):underlyingBalance, bytes(32):strategyConfig, uint256(32):lastHarvest, uint256(32):harvestCooldown, uint256(32):performanceFee, uint256(32):highWaterMark, uint256(32):INITIAL_CHAIN_ID, bytes32(32):INITIAL_DOMAIN_SEPARATOR, mapping(32):nonces, address(20):strategy, uint8(1):_decimals, address(20):FEE_RECIPIENT
38:       uint8 internal _decimals;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L38

[G‑05] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There are 21 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit nonce on line 102
104:      bytes32 id = keccak256(abi.encodePacked(token, account, amount, nonce));

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L104

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit strategy on line 440
444:              address(strategy).delegatecall(

/// @audit strategy on line 480
481:          strategy.verifyAdapterCompatibility(strategyConfig);

/// @audit strategy on line 481
/// @audit strategyConfig on line 481
482:          strategy.setUp(strategyConfig);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L444

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit yVault on line 45
54:           IERC20(_asset).approve(address(yVault), type(uint256).max);

/// @audit yVault on line 90
95:                   yVault.totalSupply(),

/// @audit yVault on line 110
110:          return IERC20(asset()).balanceOf(address(yVault)) + yVault.totalDebt();

/// @audit yVault on line 115
116:              yVault.lockedProfitDegradation();

/// @audit yVault on line 116
119:              uint256 lockedProfit = yVault.lockedProfit();

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L54

File: src/vault/VaultController.sol

/// @audit adminProxy on line 124
126:      (bool success, bytes memory returnData) = adminProxy.execute(

/// @audit adminProxy on line 230
238:      adminProxy.execute(adapter, abi.encodeWithSelector(IAdapter.setPerformanceFee.selector, performanceFee));

/// @audit adminProxy on line 457
459:        (success, returnData) = adminProxy.execute(

/// @audit permissionRegistry on line 682
683:            ? !permissionRegistry.endorsed(token)

/// @audit permissionRegistry on line 683
684:            : permissionRegistry.rejected(token)

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

File: src/vault/Vault.sol

/// @audit highWaterMark on line 448
453:              performanceFee > 0 && shareValue > highWaterMark

/// @audit highWaterMark on line 453
455:                      (shareValue - highWaterMark) * totalSupply(),

/// @audit adapter on line 286
286:          return adapter.convertToAssets(adapter.balanceOf(address(this)));

/// @audit adapter on line 598
599:              adapter.balanceOf(address(this)),

/// @audit adapter on line 608
610:          asset.approve(address(adapter), type(uint256).max);

/// @audit adapter on line 610
612:          adapter.deposit(asset.balanceOf(address(this)), address(this));

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

[G‑06] Multiple accesses of a mapping/array should use a local variable cache

The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata

There are 5 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit escrows[escrowId] on line 141
141:      return escrows[escrowId].lastUpdateTime != 0 && escrows[escrowId].balance > 0;

/// @audit escrows[escrowId] on line 162
163:        escrows[escrowId].lastUpdateTime = block.timestamp.safeCastTo32();

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L141

File: src/utils/MultiRewardStaking.sol

/// @audit rewardInfos[rewardToken] on line 313
314:      rewardInfos[rewardToken].rewardsEndTimestamp = rewardsEndTimestamp;

/// @audit rewardInfos[rewardToken] on line 343
346:      rewardInfos[rewardToken].lastUpdatedTimestamp = block.timestamp.safeCastTo32();

/// @audit rewardInfos[_rewardToken] on line 408
409:      rewardInfos[_rewardToken].lastUpdatedTimestamp = block.timestamp.safeCastTo32();

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

[G‑07] The result of function calls should be cached rather than re-calling the function

The instances below point to the second+ call of the function within a single function

There is 1 instance of this issue:

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit yVault.totalSupply() on line 90
95:                   yVault.totalSupply(),

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L95

[G‑08] <x> += <y> costs more gas than <x> = <x> + <y> for state variables

Using the addition operator instead of plus-equals saves 113 gas

There are 2 instances of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

158:          underlyingBalance += _underlyingBalance() - underlyingBalance_;

225:              underlyingBalance -= underlyingBalance_ - _underlyingBalance();

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L158

[G‑09] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

There are 13 instances of this issue:

File: src/utils/MultiRewardStaking.sol

191     function _lockToken(
192       address user,
193       IERC20 rewardToken,
194       uint256 rewardAmount,
195:      EscrowInfo memory escrowInfo

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L191-L195

File: src/vault/adapter/yearn/YearnAdapter.sol

89:       function _shareValue(uint256 yShares) internal view returns (uint256) {

101:      function _freeFunds() internal view returns (uint256) {

109:      function _yTotalAssets() internal view returns (uint256) {

114:      function _calculateLockedProfit() internal view returns (uint256) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L89

File: src/vault/VaultController.sol

120     function _deployVault(VaultInitParams memory vaultData, IDeploymentController _deploymentController)
121       internal
122:      returns (address vault)

141     function _registerCreatedVault(
142       address vault,
143       address staking,
144:      VaultMetadata memory metadata

154:    function _handleVaultStakingRewards(address vault, bytes memory rewardsData) internal {

225     function __deployAdapter(
226       DeploymentArgs memory adapterData,
227       bytes memory baseAdapterData,
228       IDeploymentController _deploymentController
229:    ) internal returns (address adapter) {

242     function _encodeAdapterData(DeploymentArgs memory adapterData, bytes memory baseAdapterData)
243       internal
244:      returns (bytes memory)

256     function _deployStrategy(DeploymentArgs memory strategyData, IDeploymentController _deploymentController)
257       internal
258:      returns (address strategy)

390:    function _registerVault(address vault, VaultMetadata memory metadata) internal {

692:    function _verifyAdapterConfiguration(address adapter, bytes32 adapterId) internal view {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L120-L122

[G‑10] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require() or if-statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a }

There are 2 instances of this issue:

File: src/utils/MultiRewardStaking.sol

/// @audit if-condition on line 356
357:        amount += uint256(rewardsPerSecond) * (rewardsEndTimestamp - block.timestamp);

/// @audit if-condition on line 394
395:        elapsed = rewards.rewardsEndTimestamp - rewards.lastUpdatedTimestamp;

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

[G‑11] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

There are 21 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

53:       for (uint256 i = 0; i < escrowIds.length; i++) {

155:      for (uint256 i = 0; i < escrowIds.length; i++) {

210:      for (uint256 i = 0; i < tokens.length; i++) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L53

File: src/utils/MultiRewardStaking.sol

171:      for (uint8 i; i < _rewardTokens.length; i++) {

373:      for (uint8 i; i < _rewardTokens.length; i++) {

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

File: src/vault/PermissionRegistry.sol

42:       for (uint256 i = 0; i < len; i++) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L42

File: src/vault/VaultController.sol

319:      for (uint8 i = 0; i < len; i++) {

337:      for (uint8 i = 0; i < len; i++) {

357:      for (uint8 i = 0; i < len; i++) {

374:      for (uint8 i = 0; i < len; i++) {

437:      for (uint256 i = 0; i < len; i++) {

494:      for (uint256 i = 0; i < len; i++) {

523:      for (uint256 i = 0; i < len; i++) {

564:      for (uint256 i = 0; i < len; i++) {

587:      for (uint256 i = 0; i < len; i++) {

607:      for (uint256 i = 0; i < len; i++) {

620:      for (uint256 i = 0; i < len; i++) {

633:      for (uint256 i = 0; i < len; i++) {

646:      for (uint256 i = 0; i < len; i++) {

766:      for (uint256 i = 0; i < len; i++) {

806:      for (uint256 i = 0; i < len; i++) {

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

[G‑12] keccak256() should only need to be called on a specific string literal once

It should be saved to an immutable variable, and the variable used instead. If the hash is being used as a part of a function selector, the cast to bytes4 should also only be done once

There are 9 instances of this issue:

File: src/utils/MultiRewardStaking.sol

466:                  keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),

495:            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),

497:            keccak256("1"),

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

File: src/vault/adapter/abstracts/AdapterBase.sol

653                                   keccak256(
654                                       "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
655:                                  ),

688                       keccak256(
689                           "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
690:                      ),

692:                      keccak256("1"),

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L653-L655

File: src/vault/Vault.sol

685                                   keccak256(
686                                       "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
687:                                  ),

720                       keccak256(
721                           "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
722:                      ),

724:                      keccak256("1"),

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/Vault.sol#L685-L687

[G‑13] Optimize names to save gas

public/external function names and public member variable names can be optimized to save gas. See this link for an example of how it works. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted

There are 26 instances of this issue:

File: src/interfaces/IMultiRewardEscrow.sol

/// @audit lock(), setFees(), fees()
31:   interface IMultiRewardEscrow {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/IMultiRewardEscrow.sol#L31

File: src/interfaces/IMultiRewardStaking.sol

/// @audit addRewardToken(), changeRewardSpeed(), fundReward(), initialize(), rewardInfos(), escrowInfos()
37:   interface IMultiRewardStaking is IERC4626, IOwned, IPermit, IPausable {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/IMultiRewardStaking.sol#L37

File: src/interfaces/vault/IAdapter.sol

/// @audit strategy(), strategyConfig(), strategyDeposit(), strategyWithdraw(), setPerformanceFee(), performanceFee(), highWaterMark(), harvest(), harvestCooldown(), setHarvestCooldown(), initialize()
11:   interface IAdapter is IERC4626, IOwned, IPermit, IPausable {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IAdapter.sol#L11

File: src/interfaces/vault/ICloneRegistry.sol

/// @audit cloneExists(), addClone()
8:    interface ICloneRegistry is IOwned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/ICloneRegistry.sol#L8

File: src/interfaces/vault/IDeploymentController.sol

/// @audit templateCategoryExists(), templateExists(), addTemplate(), addTemplateCategory(), toggleTemplateEndorsement(), getTemplate(), nominateNewDependencyOwner(), acceptDependencyOwnership(), cloneFactory(), cloneRegistry(), templateRegistry(), PermissionRegistry(), addClone()
11:   interface IDeploymentController is ICloneFactory, ICloneRegistry {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IDeploymentController.sol#L11

File: src/interfaces/vault/IPermissionRegistry.sol

/// @audit setPermissions(), endorsed(), rejected()
13:   interface IPermissionRegistry is IOwned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IPermissionRegistry.sol#L13

File: src/interfaces/vault/IStrategy.sol

/// @audit harvest(), verifyAdapterSelectorCompatibility(), verifyAdapterCompatibility(), setUp()
6:    interface IStrategy {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IStrategy.sol#L6

File: src/interfaces/vault/ITemplateRegistry.sol

/// @audit templates(), templateCategoryExists(), templateExists(), getTemplateCategories(), getTemplate(), getTemplateIds(), addTemplate(), addTemplateCategory(), toggleTemplateEndorsement()
23:   interface ITemplateRegistry is IOwned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/ITemplateRegistry.sol#L23

File: src/interfaces/vault/IVaultController.sol

/// @audit deployVault(), deployAdapter(), deployStaking(), proposeVaultAdapters(), changeVaultAdapters(), proposeVaultFees(), changeVaultFees(), registerVaults(), addClones(), toggleEndorsements(), toggleRejections(), addStakingRewardsTokens(), changeStakingRewardsSpeeds(), fundStakingRewards(), setEscrowTokenFees(), addTemplateCategories(), toggleTemplateEndorsements(), pauseAdapters(), pauseVaults(), unpauseAdapters(), unpauseVaults(), nominateNewAdminProxyOwner(), acceptAdminProxyOwnership(), setPerformanceFee(), setAdapterPerformanceFees(), performanceFee(), setHarvestCooldown(), setAdapterHarvestCooldowns(), harvestCooldown(), setDeploymentController(), setActiveTemplateId(), activeTemplateId()
19:   interface IVaultController {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IVaultController.sol#L19

File: src/interfaces/vault/IVaultRegistry.sol

/// @audit getVault(), getSubmitter(), registerVault()
24:   interface IVaultRegistry is IOwned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IVaultRegistry.sol#L24

File: src/interfaces/vault/IVault.sol

/// @audit accruedManagementFee(), accruedPerformanceFee(), highWaterMark(), assetsCheckpoint(), feesUpdatedAt(), feeRecipient(), deposit(), takeManagementAndPerformanceFees(), adapter(), proposedAdapter(), proposedAdapterTime(), proposeAdapter(), changeAdapter(), fees(), proposedFees(), proposedFeeTime(), proposeFees(), changeFees(), setFeeRecipient(), quitPeriod(), setQuitPeriod(), initialize()
29:   interface IVault is IERC4626 {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IVault.sol#L29

File: src/interfaces/vault/IWithRewards.sol

/// @audit claim(), rewardTokens()
6:    interface IWithRewards {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/interfaces/vault/IWithRewards.sol#L6

File: src/utils/MultiRewardEscrow.sol

/// @audit getEscrowIdsByUser(), getEscrowIdsByUserAndToken(), getEscrows(), lock(), isClaimable(), getClaimableAmount(), claimRewards(), setFees()
21:   contract MultiRewardEscrow is Owned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L21

File: src/utils/MultiRewardStaking.sol

/// @audit initialize(), deposit(), claimRewards(), addRewardToken(), changeRewardSpeed(), fundReward(), getAllRewardsTokens()
26:   contract MultiRewardStaking is ERC4626Upgradeable, OwnedUpgradeable {

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

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit convertToUnderlyingShares(), harvest(), strategyDeposit(), strategyWithdraw(), setHarvestCooldown(), accruedPerformanceFee(), setPerformanceFee()
27:   abstract contract AdapterBase is

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L27

File: src/vault/adapter/abstracts/WithRewards.sol

/// @audit rewardTokens(), claim()
12:   contract WithRewards is EIP165, OnlyStrategy {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/WithRewards.sol#L12

File: src/vault/adapter/beefy/IBeefy.sol

/// @audit want(), deposit(), withdrawAll(), balance(), earn(), getPricePerFullShare(), strategy()
6:    interface IBeefyVault {

/// @audit earned(), stakedToken(), rewardToken(), periodFinish(), rewardPerToken(), stake(), exit(), getReward()
29:   interface IBeefyBooster {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/IBeefy.sol#L6

File: src/vault/adapter/yearn/IYearn.sol

/// @audit deposit(), pricePerShare(), depositLimit(), token(), lastReport(), lockedProfit(), lockedProfitDegradation(), totalDebt()
8:    interface VaultAPI is IERC20 {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/IYearn.sol#L8

File: src/vault/CloneRegistry.sol

/// @audit addClone(), getClonesByCategoryAndId(), getAllClones()
16:   contract CloneRegistry is Owned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L16

File: src/vault/DeploymentController.sol

/// @audit addTemplateCategory(), addTemplate(), toggleTemplateEndorsement(), deploy(), nominateNewDependencyOwner(), acceptDependencyOwnership()
18:   contract DeploymentController is Owned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L18

File: src/vault/PermissionRegistry.sol

/// @audit setPermissions(), endorsed(), rejected()
14:   contract PermissionRegistry is Owned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L14

File: src/vault/TemplateRegistry.sol

/// @audit addTemplateCategory(), addTemplate(), toggleTemplateEndorsement(), getTemplateCategories(), getTemplateIds(), getTemplate()
18:   contract TemplateRegistry is Owned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L18

File: src/vault/VaultController.sol

/// @audit deployVault(), deployAdapter(), deployStaking(), proposeVaultAdapters(), changeVaultAdapters(), proposeVaultFees(), changeVaultFees(), setPermissions(), addStakingRewardsTokens(), changeStakingRewardsSpeeds(), fundStakingRewards(), setEscrowTokenFees(), addTemplateCategories(), toggleTemplateEndorsements(), pauseAdapters(), pauseVaults(), unpauseAdapters(), unpauseVaults(), nominateNewAdminProxyOwner(), acceptAdminProxyOwnership(), setPerformanceFee(), setAdapterPerformanceFees(), setHarvestCooldown(), setAdapterHarvestCooldowns(), setDeploymentController(), setActiveTemplateId()
29:   contract VaultController is Owned {

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

File: src/vault/VaultRegistry.sol

/// @audit registerVault(), getVault(), getVaultsByAsset(), getTotalVaults(), getRegisteredAddresses(), getSubmitter()
15:   contract VaultRegistry is Owned {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L15

File: src/vault/Vault.sol

/// @audit initialize(), deposit(), accruedManagementFee(), accruedPerformanceFee(), takeManagementAndPerformanceFees(), proposeFees(), changeFees(), setFeeRecipient(), proposeAdapter(), changeAdapter(), setQuitPeriod()
26:   contract Vault is

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

[G‑14] Remove unused local variable

There is 1 instance of this issue:

File: src/vault/Vault.sol

/// @audit highWaterMark_
448:          uint256 highWaterMark_ = highWaterMark;

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

[G‑15] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed

There are 2 instances of this issue:

File: src/utils/MultiRewardStaking.sol

/// @audit uint32 rewardsEndTimestamp
342:        rewardsEndTimestamp = _calcRewardsEnd(rewards.rewardsEndTimestamp, rewards.rewardsPerSecond, amount);

/// @audit uint224 deltaIndex
406:        deltaIndex = accrued.mulDiv(uint256(10**decimals()), supplyTokens, Math.Rounding.Down).safeCastTo224();

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

[G‑16] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There are 4 instances of this issue:

File: src/vault/VaultController.sol

36:     bytes32 public immutable VAULT = "Vault";

37:     bytes32 public immutable ADAPTER = "Adapter";

38:     bytes32 public immutable STRATEGY = "Strategy";

39:     bytes32 public immutable STAKING = "Staking";

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

[G‑17] Superfluous event fields

block.timestamp and block.number are added to event information by default so adding them manually wastes gas

There are 2 instances of this issue:

File: src/vault/Vault.sol

512:      event NewFeesProposed(VaultFees newFees, uint256 timestamp);

569:      event NewAdapterProposed(IERC4626 newAdapter, uint256 timestamp);

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

[G‑18] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 9 instances of this issue:

File: src/utils/MultiRewardStaking.sol

243     function addRewardToken(
244       IERC20 rewardToken,
245       uint160 rewardsPerSecond,
246       uint256 amount,
247       bool useEscrow,
248       uint192 escrowPercentage,
249       uint32 escrowDuration,
250       uint32 offset
251:    ) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardStaking.sol#L243-L251

File: src/vault/adapter/abstracts/AdapterBase.sol

55        function __AdapterBase_init(bytes memory popERC4626InitData)
56            internal
57:           onlyInitializing

456       function strategyDeposit(uint256 amount, uint256 shares)
457           public
458:          onlyStrategy

467       function strategyWithdraw(uint256 amount, uint256 shares)
468           public
469:          onlyStrategy

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L55-L57

File: src/vault/AdminProxy.sol

19      function execute(address target, bytes calldata callData)
20        external
21        onlyOwner
22:       returns (bool success, bytes memory returndata)

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/AdminProxy.sol#L19-L22

File: src/vault/CloneRegistry.sol

41      function addClone(
42        bytes32 templateCategory,
43        bytes32 templateId,
44        address clone
45:     ) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L41-L45

File: src/vault/DeploymentController.sol

99      function deploy(
100       bytes32 templateCategory,
101       bytes32 templateId,
102       bytes calldata data
103:    ) external onlyOwner returns (address clone) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L99-L103

File: src/vault/TemplateRegistry.sol

67      function addTemplate(
68        bytes32 templateCategory,
69        bytes32 templateId,
70        Template memory template
71:     ) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L67-L71

File: src/vault/VaultController.sol

579     function toggleTemplateEndorsements(bytes32[] calldata templateCategories, bytes32[] calldata templateIds)
580       external
581:      onlyOwner

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L579-L581

[G‑19] Constructors can be marked payable

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

There are 9 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

30:     constructor(address _owner, address _feeRecipient) Owned(_owner) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L30

File: src/vault/AdminProxy.sol

16:     constructor(address _owner) Owned(_owner) {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/AdminProxy.sol#L16

File: src/vault/CloneFactory.sol

23:     constructor(address _owner) Owned(_owner) {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneFactory.sol#L23

File: src/vault/CloneRegistry.sol

22:     constructor(address _owner) Owned(_owner) {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L22

File: src/vault/DeploymentController.sol

35      constructor(
36        address _owner,
37        ICloneFactory _cloneFactory,
38        ICloneRegistry _cloneRegistry,
39        ITemplateRegistry _templateRegistry
40:     ) Owned(_owner) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L35-L40

File: src/vault/PermissionRegistry.sol

20:     constructor(address _owner) Owned(_owner) {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L20

File: src/vault/TemplateRegistry.sol

24:     constructor(address _owner) Owned(_owner) {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L24

File: src/vault/VaultController.sol

53      constructor(
54        address _owner,
55        IAdminProxy _adminProxy,
56        IDeploymentController _deploymentController,
57        IVaultRegistry _vaultRegistry,
58        IPermissionRegistry _permissionRegistry,
59        IMultiRewardEscrow _escrow
60:     ) Owned(_owner) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L53-L60

File: src/vault/VaultRegistry.sol

21:     constructor(address _owner) Owned(_owner) {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L21

[G‑20] Don't use _msgSender() if not supporting EIP-2771

Use msg.sender if the code does not implement EIP-2771 trusted forwarder support

There are 4 instances of this issue:

File: src/vault/adapter/abstracts/AdapterBase.sol

119:          _deposit(_msgSender(), receiver, assets, shares);

138:          _deposit(_msgSender(), receiver, assets, shares);

182:          _withdraw(_msgSender(), receiver, owner, assets, shares);

201:          _withdraw(_msgSender(), receiver, owner, assets, shares);

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L119


Excluded findings

These findings are excluded from awards calculations because there are publicly-available automated tools that find them. The valid ones appear here for completeness

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Using calldata instead of memory for read-only arguments in external functions saves gas161920
[G‑02]State variables should be cached in stack variables rather than re-reading them from storage6-
[G‑03]<array>.length should not be looked up in every loop of a for-loop515
[G‑04]Using bools for storage incurs overhead351300
[G‑05]++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)22110
[G‑06]Using private rather than public for constants, saves gas1-
[G‑07]Functions guaranteed to revert when called by normal users can be marked payable32672

Total: 85 instances over 7 issues with 54017 gas saved

Gas totals use lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Using calldata instead of memory for read-only arguments in external functions saves gas

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution. Note that even if an interface defines a function as having memory arguments, it's still valid for implementation contracs to use calldata arguments instead.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gass-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

Note that I've also flagged instances where the function is public but can be marked as external since it's not called by the contract, and cases where a constructor is involved

There are 16 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit escrowIds - (valid but excluded finding)
154:    function claimRewards(bytes32[] memory escrowIds) external {

/// @audit tokens - (valid but excluded finding)
/// @audit tokenFees - (valid but excluded finding)
207:    function setFees(IERC20[] memory tokens, uint256[] memory tokenFees) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L154

File: src/utils/MultiRewardStaking.sol

/// @audit _rewardTokens - (valid but excluded finding)
170:    function claimRewards(address user, IERC20[] memory _rewardTokens) external accrueRewards(msg.sender, user) {

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

File: src/vault/adapter/beefy/BeefyAdapter.sol

/// @audit adapterInitData - (valid but excluded finding)
/// @audit beefyInitData - (valid but excluded finding)
46        function initialize(
47            bytes memory adapterInitData,
48            address registry,
49            bytes memory beefyInitData
50:       ) external initializer {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/BeefyAdapter.sol#L46-L50

File: src/vault/adapter/yearn/YearnAdapter.sol

/// @audit adapterInitData - (valid but excluded finding)
/// @audit (valid but excluded finding)
34        function initialize(
35            bytes memory adapterInitData,
36            address externalRegistry,
37            bytes memory
38:       ) external initializer {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/yearn/YearnAdapter.sol#L34-L38

File: src/vault/TemplateRegistry.sol

/// @audit template - (valid but excluded finding)
67      function addTemplate(
68        bytes32 templateCategory,
69        bytes32 templateId,
70        Template memory template
71:     ) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L67-L71

File: src/vault/VaultController.sol

/// @audit vaultData - (valid but excluded finding)
/// @audit adapterData - (valid but excluded finding)
/// @audit strategyData - (valid but excluded finding)
/// @audit rewardsData - (valid but excluded finding)
/// @audit metadata - (valid but excluded finding)
89      function deployVault(
90        VaultInitParams memory vaultData,
91        DeploymentArgs memory adapterData,
92        DeploymentArgs memory strategyData,
93        address staking,
94        bytes memory rewardsData,
95        VaultMetadata memory metadata,
96        uint256 initialDeposit
97:     ) external canCreate returns (address vault) {

/// @audit adapterData - (valid but excluded finding)
/// @audit strategyData - (valid but excluded finding)
186     function deployAdapter(
187       IERC20 asset,
188       DeploymentArgs memory adapterData,
189       DeploymentArgs memory strategyData,
190       uint256 initialDeposit
191:    ) external canCreate returns (address adapter) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultController.sol#L89-L97

[G‑02] State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

There are 6 instances of this issue:

File: src/vault/VaultController.sol

/// @audit adminProxy on line 288 - (valid but excluded finding)
294:          abi.encodeWithSelector(IMultiRewardStaking.initialize.selector, asset, escrow, adminProxy)

/// @audit adminProxy on line 450 - (valid but excluded finding)
457:        IERC20(rewardsToken).transferFrom(msg.sender, address(adminProxy), amount);

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

File: src/vault/Vault.sol

/// @audit proposedFees on line 544 - (valid but excluded finding)
545:          fees = proposedFees;

/// @audit adapter on line 599 - (valid but excluded finding)
604:          asset.approve(address(adapter), 0);

/// @audit adapter on line 604 - (valid but excluded finding)
606:          emit ChangedAdapter(adapter, proposedAdapter);

/// @audit proposedAdapter on line 606 - (valid but excluded finding)
608:          adapter = proposedAdapter;

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

[G‑03] <array>.length should not be looked up in every loop of a for-loop

The overheads outlined below are PER LOOP, excluding the first loop

  • storage arrays incur a Gwarmaccess (100 gas)
  • memory arrays use MLOAD (3 gas)
  • calldata arrays use CALLDATALOAD (3 gas)

Caching the length changes each of these to a DUP<N> (3 gas), and gets rid of the extra DUP<N> needed to store the stack offset

There are 5 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit (valid but excluded finding)
53:       for (uint256 i = 0; i < escrowIds.length; i++) {

/// @audit (valid but excluded finding)
155:      for (uint256 i = 0; i < escrowIds.length; i++) {

/// @audit (valid but excluded finding)
210:      for (uint256 i = 0; i < tokens.length; i++) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L53

File: src/utils/MultiRewardStaking.sol

/// @audit (valid but excluded finding)
171:      for (uint8 i; i < _rewardTokens.length; i++) {

/// @audit (valid but excluded finding)
373:      for (uint8 i; i < _rewardTokens.length; i++) {

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

[G‑04] Using bools for storage incurs overhead

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past

There are 3 instances of this issue:

File: src/vault/CloneRegistry.sol

/// @audit (valid but excluded finding)
28:     mapping(address => bool) public cloneExists;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneRegistry.sol#L28

File: src/vault/TemplateRegistry.sol

/// @audit (valid but excluded finding)
33:     mapping(bytes32 => bool) public templateExists;

/// @audit (valid but excluded finding)
35:     mapping(bytes32 => bool) public templateCategoryExists;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L33

[G‑05] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

There are 22 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit (valid but excluded finding)
53:       for (uint256 i = 0; i < escrowIds.length; i++) {

/// @audit (valid but excluded finding)
102:      nonce++;

/// @audit (valid but excluded finding)
155:      for (uint256 i = 0; i < escrowIds.length; i++) {

/// @audit (valid but excluded finding)
210:      for (uint256 i = 0; i < tokens.length; i++) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L53

File: src/utils/MultiRewardStaking.sol

/// @audit (valid but excluded finding)
171:      for (uint8 i; i < _rewardTokens.length; i++) {

/// @audit (valid but excluded finding)
373:      for (uint8 i; i < _rewardTokens.length; i++) {

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

File: src/vault/PermissionRegistry.sol

/// @audit (valid but excluded finding)
42:       for (uint256 i = 0; i < len; i++) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L42

File: src/vault/VaultController.sol

/// @audit (valid but excluded finding)
319:      for (uint8 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
337:      for (uint8 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
357:      for (uint8 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
374:      for (uint8 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
437:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
494:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
523:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
564:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
587:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
607:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
620:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
633:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
646:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
766:      for (uint256 i = 0; i < len; i++) {

/// @audit (valid but excluded finding)
806:      for (uint256 i = 0; i < len; i++) {

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

[G‑06] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table

There is 1 instance of this issue:

File: src/vault/adapter/beefy/BeefyAdapter.sol

/// @audit (valid but excluded finding)
31:       uint256 public constant BPS_DENOMINATOR = 10_000;

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/BeefyAdapter.sol#L31

[G‑07] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 32 instances of this issue:

File: src/utils/MultiRewardEscrow.sol

/// @audit (valid but excluded finding)
207:    function setFees(IERC20[] memory tokens, uint256[] memory tokenFees) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/utils/MultiRewardEscrow.sol#L207

File: src/utils/MultiRewardStaking.sol

/// @audit (valid but excluded finding)
296:    function changeRewardSpeed(IERC20 rewardToken, uint160 rewardsPerSecond) external onlyOwner {

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

File: src/vault/adapter/abstracts/AdapterBase.sol

/// @audit (valid but excluded finding)
500:      function setHarvestCooldown(uint256 newCooldown) external onlyOwner {

/// @audit (valid but excluded finding)
549:      function setPerformanceFee(uint256 newFee) public onlyOwner {

/// @audit (valid but excluded finding)
574:      function pause() external onlyOwner {

/// @audit (valid but excluded finding)
582:      function unpause() external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/AdapterBase.sol#L500

File: src/vault/adapter/abstracts/WithRewards.sol

/// @audit (valid but excluded finding)
15:     function claim() public virtual onlyStrategy {}

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/abstracts/WithRewards.sol#L15

File: src/vault/adapter/beefy/BeefyAdapter.sol

/// @audit (valid but excluded finding)
221:      function claim() public override onlyStrategy {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/adapter/beefy/BeefyAdapter.sol#L221

File: src/vault/CloneFactory.sol

/// @audit (valid but excluded finding)
39:     function deploy(Template calldata template, bytes calldata data) external onlyOwner returns (address clone) {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/CloneFactory.sol#L39

File: src/vault/DeploymentController.sol

/// @audit (valid but excluded finding)
55:     function addTemplateCategory(bytes32 templateCategory) external onlyOwner {

/// @audit (valid but excluded finding)
80:     function toggleTemplateEndorsement(bytes32 templateCategory, bytes32 templateId) external onlyOwner {

/// @audit (valid but excluded finding)
121:    function nominateNewDependencyOwner(address _owner) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/DeploymentController.sol#L55

File: src/vault/PermissionRegistry.sol

/// @audit (valid but excluded finding)
38:     function setPermissions(address[] calldata targets, Permission[] calldata newPermissions) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/PermissionRegistry.sol#L38

File: src/vault/TemplateRegistry.sol

/// @audit (valid but excluded finding)
52:     function addTemplateCategory(bytes32 templateCategory) external onlyOwner {

/// @audit (valid but excluded finding)
102:    function toggleTemplateEndorsement(bytes32 templateCategory, bytes32 templateId) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/TemplateRegistry.sol#L52

File: src/vault/VaultController.sol

/// @audit (valid but excluded finding)
408:    function setPermissions(address[] calldata targets, Permission[] calldata newPermissions) external onlyOwner {

/// @audit (valid but excluded finding)
543:    function setEscrowTokenFees(IERC20[] calldata tokens, uint256[] calldata fees) external onlyOwner {

/// @audit (valid but excluded finding)
561:    function addTemplateCategories(bytes32[] calldata templateCategories) external onlyOwner {

/// @audit (valid but excluded finding)
723:    function nominateNewAdminProxyOwner(address newOwner) external onlyOwner {

/// @audit (valid but excluded finding)
751:    function setPerformanceFee(uint256 newFee) external onlyOwner {

/// @audit (valid but excluded finding)
764:    function setAdapterPerformanceFees(address[] calldata adapters) external onlyOwner {

/// @audit (valid but excluded finding)
791:    function setHarvestCooldown(uint256 newCooldown) external onlyOwner {

/// @audit (valid but excluded finding)
804:    function setAdapterHarvestCooldowns(address[] calldata adapters) external onlyOwner {

/// @audit (valid but excluded finding)
832:    function setDeploymentController(IDeploymentController _deploymentController) external onlyOwner {

/// @audit (valid but excluded finding)
864:    function setActiveTemplateId(bytes32 templateCategory, bytes32 templateId) external onlyOwner {

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

File: src/vault/VaultRegistry.sol

/// @audit (valid but excluded finding)
44:     function registerVault(VaultMetadata calldata _metadata) external onlyOwner {

https://github.com/code-423n4/2023-01-popcorn/blob/d95fc31449c260901811196d617366d6352258cd/src/vault/VaultRegistry.sol#L44

File: src/vault/Vault.sol

/// @audit (valid but excluded finding)
525:      function proposeFees(VaultFees calldata newFees) external onlyOwner {

/// @audit (valid but excluded finding)
553:      function setFeeRecipient(address _feeRecipient) external onlyOwner {

/// @audit (valid but excluded finding)
578:      function proposeAdapter(IERC4626 newAdapter) external onlyOwner {

/// @audit (valid but excluded finding)
629:      function setQuitPeriod(uint256 _quitPeriod) external onlyOwner {

/// @audit (valid but excluded finding)
643:      function pause() external onlyOwner {

/// @audit (valid but excluded finding)
648:      function unpause() external onlyOwner {

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

#0 - c4-judge

2023-02-28T14:52:53Z

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