Open code423n4 opened 2 years ago
-1 Declare variables outside of for loop to save gas Crazy how the warden has proof to show gas savings, am pretty confident there would be no savings with optimizer. As the savings make no sense. Technically the suggested version is declaring 2 extra mstores (MSTORE -> 0 in the variable) however they proven to save gas so that's 537 gas
-2 Unnecssary approve 0
Per this line: https://github.com/convex-eth/platform/blob/3ea150f5bf5482cb0572b67cd92ecce3b345249d/contracts/contracts/Booster.sol#L257
I agree with the unnecessary approve 0, per evm.codes this is 100 gas saved
3 using storage pointer Per the wardens own math there are no savings (you always have a pointer to storage)
4 Move checks at the beginning of the function to save gas on reverting calls Personally have grown to not like these type of optimizations as why would someone call a contract for it to revert
Let's say this would save 50 gas
5 Remove unused function Technically the function is external so no way to prove it's unused Because this is a deployment gas saving, I agree with it's validity but won't give it any gas saving value
6 Long error messages From this discussion we deduce that 1 byte = 2500 gas
For sake of simplicity let's assume that each character is one byte
That means that for each error message above 32 bytes you're spending an extra char * 2500 gas to store that onChain.
While this is still a deployment gas saving, I'll give it 2500 gas savings score as it effectively saves a ton of gas. 2500 * 4 = 10000
Total 10687 gas saved
Also want to commend the warden for the amazing formatting and thoroughness of the report
Issue #1: Declare variables outside of for loop to save gas
Impact
Declaring variables inside for loops caused these variables to be re-declared in every loop. You can save gas by declaring them outside the for loop and updating them inside.
Where in the code
Steps to Mitigate
Using the example in the first link, simply change:
to
Proof of Concept
I made some mock tests in foundry to check the difference in gas.
The results:
Here is the example code, bare in mind I separate these in different files to make sure there’s no extra gas added due to how the dispatcher finds functions:
Contract A
Contract B
Test
Issue #2: Unnecessary safeApprove update to 0
Impact
This line may be redundant in the logic of the deposit function. If I’m not missing something, you are approving
lpToken
to spend_amount
, then that_amount
is deposited into theconvexBooster
, which should set theallowances
to_amount - _amount
, which is 0, so the highlighted line where the approve is updated to 0 seems redundant, so it could be removed to save some gas and contract size. Again, I could be missing some special behaviour here.Steps to mitigate
Remove this line.
Issue #3: Cast mapping into storage variable inside function to save gas
Impact
Declaring state variables used more than once in the logic of a function can save gas. In this case, the deposit and withdraw functions can cast
deposits[_pid][msg.sender]
into a storage variable to save gas.Where in the Code
Steps to mitigate
Create a storage variable inside the function and use that variable instead of the mapping.
Using deposit as an example, change:
to:
Proof of Concept
I ran mock gas-tests using foundry. The results:
Here are the contracts I ran along with the tests. Bare in mind I separate the logic in different contracts to avoid the extra gas from the binary search the dispatcher does to find the function to call.
Contract A
Contract B
Test
Issue #4: Move checks at the beginning of the function to save gas on reverting calls
Impact
It’s always a good pattern to put checks as the first thing in a function to make sure if something’s wrong, then there’s no extra logic computed, therefore there’s gas saved in a revertir of failing call.
Where in the code
Steps to Mitigate
Change:
To:
Change:
To:
Issue #5: Remove poolLength() function
Impact
The poolLength() function is never called and adds unnecessary code size. Because
poolInfo
is a public variable, getting is length either on-chain or off-chain is trivial. One can call thecontract.poolInfo()
and then compute the length of the result to get the length. This function can be removed.Steps to Mitigate
Remove poolLength() function.
Issue #6: Shorten error messages
Impact
This is a common gas optimization possibility that appears in a lot of contracts. For this reason, wardens have added it to a list of common gas optimizations. I’ll link to the explanation, which is as detailed as it gets:
https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g007---long-revert-strings
In short, if you are using Solidity
version >=0.8.4
, the most gas-efficient approach is to create custom errors and useif (condition) revert customError()
syntax. The second best approach is to shorten your error strings to have a length less than 32. Here’s where you can apply those optimization in your error messages:Issue #7: Make
step
immutable or constantImpact
In USDMPegRecovery the state variable
step
is public. However, it doesn’t have a setter, it’s value is not updated throughout the contract, and its value is set to a constant number value in the constructor. Immutable and constant state variables are cheaper than internal function.Steps to mitigate
Change:
to