You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in for loops, when using the length of a storage array as the condition being checked after each loop, caching the array length in memory can yield significant gas savings if the array length is high
PROOF OF CONCEPT
Instances include:
Aura.sol
scope: mint()
_totalSupply is read twice (storage variable read every time totalSupply() is called)
Calldata instead of memory for RO function parameters
PROBLEM
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory.
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.
Hardcode state variables with their initial value instead of writing them during contract deployment with constructor parameters.
Custom Errors
IMPACT
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
Replace require and revert statements with custom errors.
For instance, in Aura.sol:
Replace
require(msg.sender == operator, "Only operator");
with
if (msg.sender != operator) {
revert IsNotOperator();
}
and define the custom error in the contract
error IsNotOperator();
Default value initialization
IMPACT
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 gas.
PROOF OF CONCEPT
Instances include:
AuraBalRewardPool.sol
AuraBalRewardPool.sol:35: uint256 public pendingPenalty = 0;
AuraBalRewardPool.sol:38: uint256 public periodFinish = 0;
AuraBalRewardPool.sol:39: uint256 public rewardRate = 0;
AuraClaimZap.sol
AuraClaimZap.sol:143: uint256 i = 0;
AuraClaimZap.sol:147: uint256 i = 0;
AuraClaimZap.sol:151: uint256 i = 0;
CrvDepositor.sol:36: uint256 public incentiveCrv = 0;
ExtraRewardStashV3.sol
ExtraRewardStashV3.sol:125: uint256 i = 0;
VirtualBalanceRewardPool.sol
VirtualBalanceRewardPool.sol:89: uint256 public periodFinish = 0;
VirtualBalanceRewardPool.sol:90: uint256 public rewardRate = 0;;
VirtualBalanceRewardPool.sol:93: uint256 public queuedRewards = 0;
VirtualBalanceRewardPool.sol:94: uint256 public currentRewards = 0;
VirtualBalanceRewardPool.sol:95: uint256 public historicalRewards = 0;
VoterProxy.sol
VoterProxy.sol:308: uint256 _balance = 0;
TOOLS USED
Manual Analysis
MITIGATION
Remove explicit initialization for default values.
Event emitting of local variable
PROBLEM
When emitting an event, using a local variable instead of a storage variable saves gas.
PROOF OF CONCEPT
Instances include:
Booster.sol
Booster.sol:235 emit FeeInfoUpdated(_feeDistro, lockRewards, crv) //lockRewards can be cached
BoosterOwner.sol
BoosterOwner.sol:99 emit AcceptedOwnership(owner); //can emit using a cached version of pendingowner before setting pendingowner to 0
BoosterOwner.sol:163 emit ShutdownStarted(forceTimestamp); //can emit block.timestamp + FORCE_DELAY
ConvexMasterChef.sol
ConvexMasterChef.sol:287 emit EmergencyWithdraw(msg.sender, _pid, user.amount); //user.amount can be cached
TOOLS USED
Manual Analysis
MITIGATION
In most instances, the storage variable is read multiple times in the function and it is recommended to cache them into memory, then passing these cached variables in the emit statement, as explained in the cache paragraph
Prefix increments
IMPACT
Prefix increments are cheaper than postfix increments.
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 them closer to an address
PROOF OF CONCEPT
Instances include:
AuraLocker.sol
AuraLocker.sol:109:
address public immutable cvxcrvStaking;
// Incentives
uint256 public kickRewardPerEpoch = 100;
uint256 public kickRewardEpochDelay = 3;
// Shutdown
bool public isShutdown = false;
AuraVestedEscrow.sol
AuraVestedEscrow.sol:26:
address public admin;
IAuraLocker public auraLocker;
uint256 public immutable startTime;
uint256 public immutable endTime;
uint256 public immutable totalTime;
bool public initialised = false;
Booster.sol
Booster.sol:45: address public lockRewards; //cvxCrv rewards(crv)
mapping(address => FeeDistro) public feeTokens;
struct FeeDistro {
address distro;
address rewards;
bool active;
}
bool public isShutdown;
ExtraRewardStashV3.sol
ExtraRewardStashV3.sol:37: address public rewardFactory;
mapping(address => uint256) public historicalRewards;
bool public hasRedirected;
CrvDepositor.sol
CrvDepositor.sol:35: address public immutable minter;
uint256 public incentiveCrv = 0;
uint256 public unlockTime;
bool public cooldown;
TOOLS USED
Manual Analysis
MITIGATION
Place every boolean right after an address to save one storage slot
Unchecked arithmetic
IMPACT
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas
Place the arithmetic operations in an unchecked block
Unnecessary library
IMPACT
As of Solidity 0.8.0, arithmetic operations revert on underflow and overflow by default. The use of external libraries to perform additions and subtractions should be avoided as it costs extra gas.
PROOF OF CONCEPT
Most of the contracts in scope use the add and sub functions of the AuraMath.sol.
TOOLS USED
Manual Analysis
MITIGATION
Use Solidity native additions and subtractions for uint256 variables
The text was updated successfully, but these errors were encountered:
Gas Report
Table of Contents
Caching storage variables in memory to save gas
IMPACT
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in
for
loops, when using the length of a storage array as the condition being checked after each loop, caching the array length in memory can yield significant gas savings if the array length is highPROOF OF CONCEPT
Instances include:
Aura.sol
scope:
mint()
_totalSupply
is read twice (storage variable read every timetotalSupply()
is called)AuraMerkleDrop.sol
scope:
withdrawExpired()
dao
is read twicescope:
claim()
auraLocker
is read twiceAuraStakingProxy.sol
scope:
applyPendingOwner()
pendingOwner
is read twicescope:
setApprovals()
crvDepositorWrapper
is read twicecrewards
is read twicescope:
distribute()
keeper
is read twicecrvDepositorWrapper
is read twicescope:
distributeOther()
rewards
is read three timesAuraVestedEscrow.sol
scope:
cancel()
admin
is read twicescope:
_claim()
auraLocker
is read three timesClaimFeesHelper.sol
scope:
claimFees()
feeDistro
is read three timeslastTokenTimes
is read twiceBaseRewardPool.sol
scope:
queueNewRewards()
periodFinish
is read twicescope:
notifyRewardAmount()
periodFinish
is read twiceBooster.sol
scope:
setFeeInfo()
lockRewards
is read four timesrewardFactory
is read twicescope:
addPool()
rewardFactory
is read twicescope:
shutdownSystem()
poolInfo
is read (poolInfo.length
) times:number of reads depending on
poolInfo
as it is in a for loopscope:
_earmarkRewards()
treasury
is read three timesplatformFee
is read twicelockRewards
is read twiceBoosterOwner.sol
scope:
acceptOwnership()
pendingowner
is read twiceCrvDepositor.sol
scope:
lookCurve()
incentiveCrv
is read twicescope:
depositFor()
incentiveCrv
is read three timesExtraRewardStashV3.sol
scope:
claimRewards()
operator
is read three timesrewardHook
is read twicescope:
processStash()
operator
is read (1 +tokenList.length
) times:number of reads depending on
tokenList
as it is in a for loopStashFactoryV2.sol
scope:
CreateStash()
v3Implementation
is read six timesVirtualBalanceRewardPool.sol
scope:
queueNewRewards()
periodFinish
is read twiceVoterProxy.sol
scope:
setOperator()
operator
is read twicescope:
withdraw()
rewardDeposit
is read three timesscope:
claimCrv()
operator
is read twicescope:
claimFees()
operator
is read twiceTOOLS USED
Manual Analysis
MITIGATION
cache these storage variables in memory
Calldata instead of memory for RO function parameters
PROBLEM
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory.
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.
PROOF OF CONCEPT
Instances include:
BalLiquidityProvider.sol
scope:
provideLiquidity()
PoolManagerSecondaryProxy.sol
scope:
setUsedAddress()
TOOLS USED
Manual Analysis
MITIGATION
Replace
memory
withcalldata
Comparisons with zero for unsigned integers
IMPACT
>0
is less gas efficient than!= 0
if you enable the optimizer at 10k AND you’re in a require statement.Detailed explanation with the opcodes here
PROOF OF CONCEPT
Instances include:
Aura.sol
AuraBalRewardPool.sol
AuraLocker.sol
AuraMerkleDrop.sol
AuraPenaltyForwarder.sol
AuraVestedEscrow.sol
BalLiquidityProvider.sol
ExtraRewardsDistributor.sol
BaseRewardPool.sol
Booster.sol
CrvDepositor.sol
PoolManagerSecondaryProxy.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace
> 0
with!= 0
Comparison Operators
IMPACT
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
PROOF OF CONCEPT
Instances include:
AuraLocker.sol
Aura.sol
AuraStakingProxy.sol
AuraVestedEscrow.sol
ClaimFeesHelper.sol
ExtraRewardsDistributor.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace
<=
with<
, and>=
with>
. Do not forget to increment/decrement the compared variableexample:
However, if
1
is negligible compared to the value of the variable, we can omit the increment.Constructor parameters should be avoided when possible
IMPACT
Constructor parameters are expensive. The contract deployment will be cheaper in gas if they are hard coded instead of using constructor parameters.
PROOF OF CONCEPT
Instances include:
Aura.sol
AuraBalRewardPool.sol
AuraClaimZap.sol
AuraLocker.sol
AuraMerkleDrop.sol
AuraPenaltyForward.sol
AuraStakingProxy.sol
AuraVestedEscrow.sol
BalLiquidityProvider.sol
ClaimFeesHelper.sol
ArbitartorVault.sol
BaseRewardPool.sol
BaseRewardPool4626.sol
Booster.sol
BoosterOwner.sol
ConvexMasterChef.sol
CrvDepositor.sol
DepositToken.sol
DepositToken.sol
PoolManagerProxy.sol
PoolManagerSecondaryProxy.sol
PoolManagerV3.sol
RewardFactory.sol
RewardHook.sol
StashFactoryV2.sol
TokenFactory.sol
VirtualBalanceRewardPool.sol
VoterProxy.sol
TOOLS USED
Manual Analysis
MITIGATION
Hardcode state variables with their initial value instead of writing them during contract deployment with constructor parameters.
Custom Errors
IMPACT
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
PROOF OF CONCEPT
Instances include:
Aura.sol
AuraBalRewardPool.sol
AuraClaimZap.sol
AuraLocker.sol
AuraMath.sol
AuraMerkleDrop.sol
AuraMinter.sol
AuraPenaltyForward.sol
AuraStakingProxy.sol
AuraVestedEscrow.sol
BalLiquidityProvider.sol
ClaimFeesHelper.sol
CrvDepositorWrapper.sol
ExtraRewardsDistributor.sol
ArbitartorVault.sol
ArbitartorVault.sol
BaseRewardPool4626.sol
Booster.sol
BoosterOwner.sol
cCrv.sol
ConvexMasterChef.sol
CrvDepositor.sol
DepositToken.sol
ExtraRewardStashV3.sol
PoolManagerProxy.sol
PoolManagerSecondaryProxy.sol
PoolManagerV3.sol
RewardFactory.sol
StashFactory.sol
TokenFactory.sol
VirtualBalanceRewardPool.sol
VoterProxy.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace require and revert statements with custom errors.
For instance, in
Aura.sol
:Replace
with
and define the custom error in the contract
Default value initialization
IMPACT
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 gas.
PROOF OF CONCEPT
Instances include:
AuraBalRewardPool.sol
AuraClaimZap.sol
AuraClaimZap.sol
AuraLocker.sol
AuraMerkleDrop.sol
AuraVestedEscrow.sol
BalLiquidityProvider.sol
ExtraRewardsDistributor.sol
ArbitartorVault.sol
BaseRewardPool.sol
Booster.sol
BoosterOwner.sol
ConvexMasterChef.sol
CrvDepositor.sol
ExtraRewardStashV3.sol
VirtualBalanceRewardPool.sol
VoterProxy.sol
TOOLS USED
Manual Analysis
MITIGATION
Remove explicit initialization for default values.
Event emitting of local variable
PROBLEM
When emitting an event, using a local variable instead of a storage variable saves gas.
PROOF OF CONCEPT
Instances include:
Booster.sol
BoosterOwner.sol
ConvexMasterChef.sol
TOOLS USED
Manual Analysis
MITIGATION
In most instances, the storage variable is read multiple times in the function and it is recommended to cache them into memory, then passing these cached variables in the
emit
statement, as explained in the cache paragraphPrefix increments
IMPACT
Prefix increments are cheaper than postfix increments.
PROOF OF CONCEPT
Instances include:
AuraClaimZap.sol
AuraLocker.sol
AuraVestedEscrow.sol
BalLiquidityProvider.sol
ExtraRewardsDistributor.sol
ArbitartorVault.sol
BaseRewardPool.sol
Booster.sol
BoosterOwner.sol
ExtraRewardStashV3.sol
PoolManagerSecondaryProxy.sol
TOOLS USED
Manual Analysis
MITIGATION
change
variable++
to++variable
.Require instead of AND
IMPACT
Require statements including conditions with the
&&
operator can be broken down in multiple require statements to save gas.PROOF OF CONCEPT
Instances include:
AuraStakingProxy.sol
BalLiquidityProvider.sol
Booster.sol
StashFactoryV2.sol
TOOLS USED
Manual Analysis
MITIGATION
Tight Variable Packing
PROBLEM
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 them closer to an address
PROOF OF CONCEPT
Instances include:
AuraLocker.sol
AuraVestedEscrow.sol
Booster.sol
ExtraRewardStashV3.sol
CrvDepositor.sol
TOOLS USED
Manual Analysis
MITIGATION
Place every
boolean
right after anaddress
to save one storage slotUnchecked arithmetic
IMPACT
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an
unchecked
block will save gasPROOF OF CONCEPT
Instances include:
AuraBalRewardPool.sol
AuraMinter.sol
AuraVestedEscrow.sol
AuraVestedEscrow.sol
CrvDepositor.sol
TOOLS USED
Manual Analysis
MITIGATION
Place the arithmetic operations in an
unchecked
blockUnnecessary library
IMPACT
As of Solidity 0.8.0, arithmetic operations revert on underflow and overflow by default. The use of external libraries to perform additions and subtractions should be avoided as it costs extra gas.
PROOF OF CONCEPT
Most of the contracts in scope use the
add
andsub
functions of theAuraMath.sol
.TOOLS USED
Manual Analysis
MITIGATION
Use Solidity native additions and subtractions for uint256 variables
The text was updated successfully, but these errors were encountered: