Platform: Code4rena
Start Date: 15/06/2022
Pot Size: $30,000 USDC
Total HM: 5
Participants: 55
Period: 3 days
Judge: Jack the Pug
Id: 138
League: ETH
Rank: 29/55
Findings: 2
Award: $92.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xFar5eer, 0xNazgul, 0xNineDec, 242, Chom, Czar102, Funen, GimelSec, Meera, Picodes, Sm4rty, Tadashi, TerrierLover, Waze, _Adam, a12jmx, asutorufos, codexploder, cryptphi, defsec, gzeon, hyh, joestakey, minhquanym, oyc_109, reassor, robee, saian, sorrynotsorry, unforgiven, zzzitron
60.392 USDC - $60.39
Few vulnerabilities were found examining the contract.
Some of the function comments are missing natspec function parameters or returns
Non-Critical
Instances include:
address _vault
address delegate
bool newWithdrawalSafetyCheck
bool newProcessLocksOnReinvest
address token
address[] calldata tokens
uint256 _amount
uint256 _amount
IRewardDistributor hiddenHandDistributor, IRewardDistributor.Claim[] calldata _claims
bytes calldata performData
address token, uint256 amount
address token, uint256 amount
uint256 amount
Manual Analysis
Add a comment for these parameters
It is best practice to use constant variables rather than literal values to make the code easier to understand and maintain.
Non-Critical
Instances include:
Manual Analysis
Define a constant variable for it.
Events should use indexed fields
Non-Critical
Instances include:
event RewardsCollected(address token, uint256 amount)
Manual Analysis
Add indexed fields to this event.
Setters should emit an event so that Dapps can detect important changes to storage
Non-Critical
Instances include:
function manualSetDelegate
function setWithdrawalSafetyCheck
function setProcessLocksOnReinvest
function setBribesProcessor
Manual Analysis
Emit an event in all setters.
Some functions are missing comments.
Non-Critical
Instances include:
Manual Analysis
Add comments to this function
It is good practice to mark functions as external
instead of public
if they are not called by the contract where they are defined.
Non-Critical
Instances include:
function initialize()
function getProtectedTokens()
function manualProcessExpiredLocks()
Manual Analysis
Declare these functions as external
instead of public
Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment
Non-Critical
Instances include:
TODO: Hardcode claim.account = address(this)?
TODO: Too many SLOADs
Manual Analysis
Remove the TODOs/Resolve related issues.
uint
is an alias for uint256
.
It is better to use uint256: it brings readability and consistency in the code, and it future proofs it in case of any changes to the alias of uint
Non-Critical
Instances include:
Manual Analysis
replace uint
with
uint256
Unused function parameters should be removed
Non-Critical
Instances include:
Manual Analysis
remove this function parameter
Properly functioning code should never reach a failing assert statement. If it happened, it would indicate the presence of a bug in the contract. A failing assert uses all the remaining gas. In this case it is the initialize
function and would not impact users, but it would still be wasting the team's funds if something were to go wrong.
Low
Instances include:
assert(IVault(_vault).token() == address(AURA))
Manual Analysis
Replace the assert statement with a require statement or a custom error
This function is deprecated.
Low
Instances include:
AURA.safeApprove(address(LOCKER), type(uint256).max)
AURABAL.safeApprove(address(BALANCER_VAULT), type(uint256).max)
WETH.safeApprove(address(BALANCER_VAULT), type(uint256).max)
Manual Analysis
safeIncreaseAllowance()
and safeDecreaseAllowance()
should be used instead.
Setters should check the input value - ie make revert if it is the zero address.
Low
Instances include:
function manualSetDelegate(address delegate)
function setBribesProcessor(IBribesProcessor newBribesProcessor)
Manual Analysis
Add non-zero address checks to delegate
and newBribesProcessor
.
#0 - GalloDaSballo
2022-06-19T00:42:34Z
I really dislike this format
31.9127 USDC - $31.91
In the EVM, there is no opcode for >=
or <=
.
When using greater than or equal, two operations are performed: >
and =
.
Using strict comparison operators hence saves gas, approximately 20
gas per comparison.
Instances include:
max >= _amount.mul(9_980).div(MAX_BPS)
Manual Analysis
Replace >=
with >
. In this case 1
is negligible compared to the value compared, we can omit the increment.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here
Custom errors are defined using the error statement.
It not only saves gas upon deployment - ~5500
gas saved per custom error instead of a require statement, but it is also cheaper in a function call, 22
gas saved per require statement replaced with a custom error.
Instances include:
Manual Analysis
Replace require and revert statements with custom errors.
For instance:
Replace
require(beforeVaultBalance == _getBalance(), "Balance can't change");
with
if (beforeVaultBalance != _getBalance()) { revert ChangedBalance(); }
and define the custom error in the contract
error ChangedBalance();
If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type).
Explicitly initializing it with its default value is an anti-pattern and wastes 22
gas per variable initialized.
Instances include:
uint i = 0
uint i = 0
uint i = 0
Manual Analysis
Remove explicit initialization for default values.
When we define internal functions to perform computation:
When it does not affect readability, it is recommended to inline functions in order to save gas. It saves ~3,000
gas per inlined function upon deployment, and 30
gas per function call.
Instances include:
function _deposit(uint256 _amount)
function _getBalance()
function _getPricePerFullShare())
function _notifyBribesProcessor()
Manual Analysis
Inline these functions where they are called
Prefix increments are cheaper than postfix increments: it returns the incremented variable instead of returning a temporary variable storing the initial value of the variable. It saves 5
gas per iteration
Instances include:
Manual Analysis
change variable++
to ++variable
.
Require statements including conditions with the &&
operator can be broken down in multiple require statements to save gas. It saves 50
gas per require statement broken down.
Instances include:
Manual Analysis
Break down the statements in multiple require statements.
-require(balanceOfPool() == 0 && LOCKER.balanceOf(address(this)) == 0,"You have to wait for unlock or have to manually rebalance out of it"); +require(balanceOfPool() == 0) +require(LOCKER.balanceOf(address(this)) == 0);
You can also improve gas savings by using custom errors
Revert strings larger than 32-bytes will incur an extra cost upon deployment. It costs ~9,500
extra gas per extra 32-byte upon deployment.
Instances include:
Manual Analysis
Reduce the string message to something smaller than 32-byte to save approximately 19,000
gas upon deployment
Solidity contracts have contiguous 32 bytes (256 bits) slots used in storage. By arranging the variables, it is possible to minimize the number of slots used within a contract's storage and therefore reduce deployment costs.
address type variables are each of 20 bytes size (way less than 32 bytes). However, they here take up a whole 32 bytes slot (they are contiguous).
As bool type variables are of size 1 byte, there's a slot here that can get saved by moving one address closer to a bool. This saves a gsset - 20,000
gas.
Instances include:
bool public withdrawalSafetyCheck; // If nothing is unlocked, processExpiredLocks will revert bool public processLocksOnReinvest; bool private isClaimingBribes; IBribesProcessor public bribesProcessor; IBalancerVault public constant BALANCER_VAULT = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); address public constant BADGER = 0x3472A5A71965499acd81997a54BBA8D852C6E53d; address public constant BADGER_TREE = 0x660802Fc641b154aBA66a62137e71f331B6d787A
Manual Analysis
Place BADGER
before withdrawalSafetyCheck
to save one storage slot
+address public constant BADGER = 0x3472A5A71965499acd81997a54BBA8D852C6E53d; bool public withdrawalSafetyCheck; // If nothing is unlocked, processExpiredLocks will revert bool public processLocksOnReinvest; bool private isClaimingBribes; IBribesProcessor public bribesProcessor; IBalancerVault public constant BALANCER_VAULT = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8); address public constant BADGER_TREE = 0x660802Fc641b154aBA66a62137e71f331B6d787A
Use a more recent version of solidity. The advantages here are: built-in overflow/underflow checks (>= 0.8.0) Low level inliner (>= 0.8.2) Cheaper runtime gas (especially relevant when the contract has small functions) Optimizer improvements in packed structs (>= 0.8.3) custom errors (>= 0.8.4), external calls skipping contract existence (>= 0.8.10).
Instances include:
pragma solidity 0.6.12
Manual Analysis
Update the solidity version.
#0 - GalloDaSballo
2022-06-19T00:48:27Z
At most should be 6 gas for the == and the AND, also what if we want both comparisons?
I appreciate the math but in lack of POC call your bluff
An MSTORE is 3 gas not 22
You'd have to break the Strategy Mix which has helped build almost 60 strategies over a year
Agree
Disagree ` bool public withdrawalSafetyCheck; // If nothing is unlocked, processExpiredLocks will revert bool public processLocksOnReinvest;
bool private isClaimingBribes;
IBribesProcessor public bribesProcessor; ` This will fill a single slot
BADGER
is a constant, it has no SLOT, same for rest of code
I'm good