Popcorn contest - descharre'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: 74/169

Findings: 2

Award: $105.30

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Summary

Low Risk

IDFindingInstances
L-01Missing checks for 0 address4
L-02Wrong check for tokenFees1
L-03Unspecific compiler version pragma1
L-04Return value of call is not checked1
L-05Account existence check for low-level calls1

Non critical

IDFindingInstances
N-01Use indexed fields in an event18
N-02Unused return variables2
N-03Modifiers should not make state changes5

Details

Low Risk

[L-01] Missing checks for 0 address

[L-02] Wrong check for tokenFees

The if statement reverts when the tokenFee is larger than 1e17. However the natspec commment says 1e18 is 100% and I don't see anywhere in the documentation where it says max fee can be 1e17. The if statent should instead check if it's not larger than 1e18.

      if (tokenFees[i] >= 1e17) revert DontGetGreedy(tokenFees[i]);

[L-03] Unspecific compiler version pragma

Avoid floating pragmas for non-library contracts. Contracts should always be deployed with the same compiler version and flags that they have been tested with. Locking the pragma helps ensure that contracts do not accidentally get deployed using a compiler which may have higher risks of undiscovered bugs.

[L-04] Return value of call is not checked

The return value of call is not checked in the execute() function of AdminProxy.sol. This can lead to unexpected failures.

A recommendation is to check the boolean success and revert the transaction if it's false.

[L-05] Account existence check for low-level calls

Low-level calls call/delegatecall/staticcall return true even if the account called is non-existent (per EVM design). Account existence must be checked before the calling.

Non critical

[N-01] Use indexed fields in an event

When an event is missing indexed fields it can be hard for off-chain scripts to efficiently filter those events.

[N-02] Unused named return variables

When you have named return variables and you don't use them, then it's better to just specify the type you are gonna return.

[N-03] Modifiers should not make state changes

Modifiers should only implement checks and not make state changes. This violates the checks-effects-interactions-pattern.

  • The modifier Vault/takeFees calls the _mint function which makes state changes. Additonally it also changes the state variables highWaterMark and feesUpdatedAt.
  • The modifier syncFeeCheckpoint changes the state variable highWaterMark
  • The modifier accrueRewards calls multiple function that changes state variables.
  • The modifier AdapterBase/takeFees changes the state variable highWaterMark and calls the _mint function.

#0 - c4-judge

2023-02-28T15:07:23Z

dmvt marked the issue as grade-b

Awards

69.8247 USDC - $69.82

Labels

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

External Links

Summary

IDFindingGas savedInstances
G-01Make for loop unchecked--
G-02It's not necessary to do a safecast on block.timestamp4509
G-03Use unchecked block when computation can't under/overflow-2
G-04Checks are unnecessary in _transfer function2402
G-05Don't assign to a memory variable when it's only used once7831

Details

[G-01] Make for loop unchecked

The risk of for loops getting overflowed is extremely low. Because it always increments by 1 and is limited to the arrays length. Even if the arrays are extremely long, it will take a massive amount of time and gas to let the for loop overflow. The longer the array, the more gas you will save.

-   for (uint256 i = 0; i < escrowIds.length; i++) {
+   for (uint256 i = 0; i < escrowIds.length; ) {
      selectedEscrows[i] = escrows[escrowIds[i]];
+     unchecked{
+        i++;
+      }
    }

[G-02] It's not necessary to do a safecast on block.timestamp

When you cast block.timestamp to a uint32 it will never overflow. That's why it's not necessary to safecast. You can cast to uint32 in the regular way instead. Saves around 50 gas.

-    block.timestamp.safeCastTo32()
+    uint32(block.timestamp)

[G-03] Use unchecked block when computation can't under/overflow

When a variable is incremented by one there is no risk in using a unchecked block. It will take a massive amount of time and gas to make it overflow

An unchecked block can also be used when previous functions or statement make sure there is no possibility of over/underflowing

[G-04] Checks are unnecessary in _transfer function

All the if statements are unnecessary because they are also in the _mint and _burn functions. It's just a gas saver to do them twice

MultiRewardStaking.sol#L147-L148

  • transfer() gas saved: 241
  • transferFrom() gas saved: 242

[G-05] Don't assign to a memory variable when it's only used once

When a variable is only accessed once it's a waste of gas to assign it to a memory variable first. Especially when you write struct to memory like in the following example, you can save a lot of gas with this method.

MultiRewardStaking.sol#L255:addRewardToken() gas saved: 783

-    RewardInfo memory rewards = rewardInfos[rewardToken];
-    if (rewards.lastUpdatedTimestamp > 0) revert RewardTokenAlreadyExist(rewardToken);
+    if (rewardInfos[rewardToken].lastUpdatedTimestamp > 0) revert RewardTokenAlreadyExist(rewardToken);

#0 - c4-judge

2023-02-28T14:52:37Z

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