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
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.
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: it costs approximately 400 less gas in deployment per variable hardcoded.
Hardcode variables with their initial value instead of writing it 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
if (_pooledTokens.length < 2) {
revert IncorrectPoolTokenLength();
}
and define the custom error in the contract
error IncorrectPoolTokenLength();
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.
In all instances, the storage variable can be replaced by the function argument that is written to that storage variable.
Group logic in modifier
PROBLEM
You can group repetitive require statements into a modifier to save on deployment costs. There is however a tradeoff to doing this: declaring the logic inline is cheaper than having it in a modifier when you call the function that uses that modifier.
You can create modifiers for repetitive require statements and apply them to the functions where the require statement mentioned are called. Again, this one is more of a suggestion because there is a trade-off.
Immutable variables can save gas
PROBLEM
If a storage variable is never modified, marking it as immutable can save gas, as it is saved as a constant instead of a storage variable, saving a SLOAD operation (100 gas) every time the variable is read.
PROOF OF CONCEPT
Instances include:
ConnextPriceOracle.sol
wrapped is initialized once and can not be modified afterwards. It is read once in every call to getTokenPrice.
TOOLS USED
Manual Analysis
MITIGATION
add the immutable keyword to address public wrapped
Prefix increments
IMPACT
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
As of Solidity 0.8.0, underflow and overflow checks are performed by default on additions and subtractions. Using an external library for these computations should be avoided as it will waste gas.
PROOF OF CONCEPT
Several contracts in scope use the SafeMath library of OpenZeppelin and its functions sub and add
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
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. This is particularly true in for loops, as it saves some gas at each iteration.
Place the arithmetic operations in an unchecked block
Unnecessary computation
IMPACT
When emitting an event that includes a new and an old value, it is cheaper in gas to avoid caching the old value in memory. Instead, emit the event, then save the new value in storage.
report submitted via email on 2022/06/13 at 08:43 UTC
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:
ConnextPriceOracle.sol
scope:
getTokenPrice()
v1PriceOracle
is read twice:line 93
line 94
ProposedOwnableUpgradeable.sol
scope:
renounceRouterOwnership()
_routerOwnershipTimestamp
is read twice:line 175
line 178
scope:
renounceAssetOwnership()
_routerOwnershipTimestamp
is read twice:line 217
line 220
scope:
renounceOwnership()
_routerOwnershipTimestamp
is read twice:line 255
line 258
proposed
is read twice:line 262
line 265
scope:
acceptProposedOwner()
proposed
is read twice:line 274
line 286
SponsorVault.sol
scope:
reimburseLiquidityFees()
tokenExchanges[_token]
is read twice:line 203
line 205
scope:
reimburseRelayerFees()
gasTokenOracle
is read twice:line 243
line 244
relayerFeeCap
is read twice, but only ifsponsoredFee
is greaterrelayerFeeCap
than:line 256
ProposedOwnable.sol
scope:
renounceOwnership()
_proposedOwnershipTimestamp
is read twice:line 125
line 128
_proposedO
is read twice:line 132
line 135
TOOLS 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:
BridgeFacet.sol
scope:
handle()
bytes memory _message
scope:
_reconcile()
bytes memory _message
scope:
_getTransferId()
ConnextMessage.TokenId memory _canonical
scope:
_reconcileProcessMessage()
bytes memory _message
PromiseRouter.sol
scope:
handle()
bytes memory _message
RelayerFeeRouter.sol
scope:
handle()
bytes memory _message
AssetLogic.sol
scope:
swapToLocalAssetIfNeeded()
ConnextMessage.TokenId memory _canonical
ConnextMessage.sol
scope:
formatTokenId()
TokenId memory _tokenId
scope:
formatDetailsHash()
string memory _name
string memory _symbol
LibCrossDomainProperty.sol
scope:
parseDomainAndSenderBytes()
bytes memory _property
LibDiamond.sol
scope:
proposeDiamondCut()
IDiamondCut.FacetCut[] memory _diamondCut
bytes memory _calldata
scope:
rescindDiamondCut()
IDiamondCut.FacetCut[] memory _diamondCut
bytes memory _calldata
scope:
diamondCut()
IDiamondCut.FacetCut[] memory _diamondCut
bytes memory _calldata
scope:
addFunctions()
bytes4[] memory _functionSelectors
scope:
replaceFunctions()
bytes4[] memory _functionSelectors
scope:
removeFunctions()
bytes4[] memory _functionSelectors
scope:
initializeDiamondCut()
bytes memory _calldata
TOOLS USED
Manual Analysis
MITIGATION
Replace
memory
withcalldata
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:
ProposedOwnableFacet.sol
(block.timestamp - s._routerOwnershipTimestamp) <= _delay
(block.timestamp - s._assetOwnershipTimestamp) <= _delay
(block.timestamp - s._proposedOwnershipTimestamp) <= _delay
(block.timestamp - s._proposedOwnershipTimestamp) <= _delay
RoutersFacet.sol
block.timestamp - s.routerPermissionInfo.proposedRouterTimestamp[router] <= _delay
getSwapToken.sol
index >= s.swapStorages[canonicalId].pooledTokens.length
index >= s.swapStorages[canonicalId].balances.length
_pooledTokens.length <= 1
_a >= AmplificationUtils.MAX_A
_fee >= SwapUtils.MAX_SWAP_FEE
_adminFee >= SwapUtils.MAX_ADMIN_FEE
ProposedOwnableUpgradeable.sol
(block.timestamp - _routerOwnershipTimestamp) <= _delay
(block.timestamp - _assetOwnershipTimestamp) <= _delay
(block.timestamp - _proposedOwnershipTimestamp) <= _delay
(block.timestamp - _proposedOwnershipTimestamp) <= _delay
SponsorVault.sol
currentBalance >= amountIn
StableSwap.sol
_pooledTokens.length <= 32
decimals[i] <= SwapUtils.POOL_PRECISION_DECIMALS
block.timestamp <= deadline
ProposedOwnable.sol
(block.timestamp - _proposedOwnershipTimestamp) <= _delay
(block.timestamp - _proposedOwnershipTimestamp) <= _delay
AmplificationUtils.sol
block.timestamp >= self.initialATime.add(1 days)
futureTime_ >= block.timestamp.add(MIN_RAMP_TIME)
futureAPrecise.mul(MAX_A_CHANGE) >= initialAPrecise
futureAPrecise <= initialAPrecise.mul(MAX_A_CHANGE)
AssetLogic.sol
_maxIn >= ipool.calculateSwapInv(tokenIndexIn, tokenIndexOut, _amountOut)
_amountIn <= _maxIn
MathUtils.sol
difference(a, b) <= 1
SwapUtils.sol
tokenAmount <= xp[tokenIndex]
amount <= totalSupply
dx <= tokenFrom.balanceOf(msg.sender)
dy >= minDy
dy <= self.balances[tokenIndexTo]
dx <= maxDx
dx <= tokenFrom.balanceOf(msg.sender)
dx <= tokenFrom.balanceOf(msg.sender)
dy >= minDy
dy <= self.balances[tokenIndexTo]
dx <= maxDx
toMint >= minToMint
amount <= lpToken.balanceOf(msg.sender)
amounts[i] >= minAmounts[i]
tokenAmount <= lpToken.balanceOf(msg.sender)
dy >= minAmount
maxBurnAmount <= v.lpToken.balanceOf(msg.sender)https://github.com/code-423n4/2022-06-connext/blob/4dd6149748b635f95460d4c3924c7e3fb6716967/contracts/contracts/core/connext/libraries/SwapUtils.sol#L1007\
tokenAmount <= maxBurnAmount
newAdminFee <= MAX_ADMIN_FEE
newSwapFee <= MAX_SWAP_FEE
PromiseMessage.sol
_len <= LENGTH_RETURNDATA_START
_len >= MIN_CLAIM_LEN
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.example:
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: it costs approximately
400
less gas in deployment per variable hardcoded.PROOF OF CONCEPT
Instances include:
ConnextPriceOracle.sol
wrapped = _wrapped;
Executor.sol
connext = _connext
TOOLS USED
Manual Analysis
MITIGATION
Hardcode variables with their initial value instead of writing it 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:
BaseConnextFacet.sol
require(s._status != _ENTERED, "ReentrancyGuard: reentrant call")
require(_remote != bytes32(0), "!remote")
ConnextPriceOracle.sol
require(baseTokenPrice > 0, "invalid base token")
LPToken.sol
require(amount != 0, "LPToken: cannot mint 0")
require(to != address(this), "LPToken: cannot send to itself")
StableSwap.sol
require(_pooledTokens.length > 1, "_pooledTokens.length <= 1")
require(_pooledTokens.length <= 32, "_pooledTokens.length > 32")
require(_pooledTokens.length == decimals.length, "_pooledTokens decimals mismatch")
require(tokenIndexes[address(_pooledTokens[i])] == 0 && _pooledTokens[0] != _pooledTokens[i],"Duplicate tokens")
require(address(_pooledTokens[i]) != address(0), "The 0 address isn't an ERC-20")
require(decimals[i] <= SwapUtils.POOL_PRECISION_DECIMALS, "Token decimals exceeds max")
require(_a < AmplificationUtils.MAX_A, "_a exceeds maximum")
require(_fee < SwapUtils.MAX_SWAP_FEE, "_fee exceeds maximum")
require(_adminFee < SwapUtils.MAX_ADMIN_FEE, "_adminFee exceeds maximum")
require(lpToken.initialize(lpTokenName, lpTokenSymbol), "could not init lpToken clone")
require(index < swapStorage.pooledTokens.length, "Out of range")
require(address(getToken(index)) == tokenAddress, "Token does not exist")
require(index < swapStorage.pooledTokens.length, "Index out of range")
AmplificationUtils.sol
require(block.timestamp >= self.initialATime.add(1 days), "Wait 1 day before starting ramp")
require(futureTime_ >= block.timestamp.add(MIN_RAMP_TIME), "Insufficient ramp time")
require(futureA_ > 0 && futureA_ < MAX_A, "futureA_ must be > 0 and < MAX_A")
require(futureAPrecise.mul(MAX_A_CHANGE) >= initialAPrecise, "futureA_ is too small")
require(futureAPrecise <= initialAPrecise.mul(MAX_A_CHANGE), "futureA_ is too large")
require(self.futureATime > block.timestamp, "Ramp is already stopped")
ConnextMessage.sol
require(isValidAction(_action), "!action")
LibDiamond.sol
require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner")
require(diamondStorage().acceptanceTimes[keccak256(abi.encode(_diamondCut))] < block.timestamp,"LibDiamond: delay not elapsed")
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut")
require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)")
require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists")
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut")
require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)")
require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function")
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut")
require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)")
require(_facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist")
require(_facetAddress != address(this), "LibDiamondCut: Can't remove immutable function")
require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty")
require(_calldata.length > 0, "LibDiamondCut: _calldata is empty but _init is not address(0)")
require(contractSize > 0, _errorMessage)
SwapUtils.sol
require(tokenIndex < xp.length, "Token index out of range")
require(tokenAmount <= xp[tokenIndex], "Withdraw exceeds available")
require(tokenIndex < numTokens, "Token not found"
require(numTokens == precisionMultipliers.length, "Balances must match multipliers")
require(tokenIndexFrom != tokenIndexTo, "Can't compare token to itself")
require(tokenIndexFrom < numTokens && tokenIndexTo < numTokens, "Tokens must be in pool")
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "Token index out of range")
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "Token index out of range")
require(amount <= totalSupply, "Cannot exceed total supply")
require(index < self.pooledTokens.length, "Token index out of range")
require(dx <= tokenFrom.balanceOf(msg.sender), "Cannot swap more than you own")
require(dy >= minDy, "Swap didn't result in min tokens")
require(dy <= self.balances[tokenIndexTo], "Cannot get more than pool balance")
require(dx <= maxDx, "Swap needs more than max tokens")
require(dx <= tokenFrom.balanceOf(msg.sender), "Cannot swap more than you own")
require(dx == tokenFrom.balanceOf(address(this)).sub(beforeBalance), "not support fee token")
require(dx <= tokenFrom.balanceOf(msg.sender), "Cannot swap more than you own")
require(dy >= minDy, "Swap didn't result in min tokens")
require(dy <= self.balances[tokenIndexTo], "Cannot get more than pool balance")
require(dx <= maxDx, "Swap didn't result in min tokens")
require(amounts.length == pooledTokens.length, "Amounts must match pooled tokens")
require(v.totalSupply != 0 || amounts[i] > 0, "Must supply all tokens in pool")
require(v.d1 > v.d0, "D should increase")
require(toMint >= minToMint, "Couldn't mint min requested")
require(amount <= lpToken.balanceOf(msg.sender), ">LP.balanceOf")
require(minAmounts.length == pooledTokens.length, "minAmounts must match poolTokens")
require(amounts[i] >= minAmounts[i], "amounts[i] < minAmounts[i]")
require(tokenAmount <= lpToken.balanceOf(msg.sender), ">LP.balanceOf")
require(tokenIndex < pooledTokens.length, "Token not found")
require(dy >= minAmount, "dy < minAmount")
require(amounts.length == pooledTokens.length, "Amounts should match pool tokens")
require(maxBurnAmount <= v.lpToken.balanceOf(msg.sender) && maxBurnAmount != 0, ">LP.balanceOf")
require(tokenAmount != 0, "Burnt amount cannot be zero")
require(tokenAmount <= maxBurnAmount, "tokenAmount > maxBurnAmount")
require(newAdminFee <= MAX_ADMIN_FEE, "Fee is too high")
require(newSwapFee <= MAX_SWAP_FEE, "Fee is too high")
TOOLS USED
Manual Analysis
MITIGATION
Replace require and revert statements with custom errors.
For instance, in
StableSwap.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:
StableSwapFaucet.sol
uint8 i = 0
ConnextPriceOracle.sol
uint256 i = 0
StableSwap.sol
uint8 i = 0
Encoding.sol
uint8 i = 0
SwapUtils.sol
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 j = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
uint256 i = 0
RelayerFeeMessage.sol
uint256 i = 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:
ProposedOwnableFacet.sol
emit RouterOwnershipRenunciationProposed(s._routerOwnershipTimestamp)
emit AssetOwnershipRenunciationProposed(s._assetOwnershipTimestamp)
emit OwnershipProposed(s._proposed)
ProposedOwnableUpgradeable.sol
emit RouterOwnershipRenunciationProposed(_routerOwnershipTimestamp)
emit AssetOwnershipRenunciationProposed(_assetOwnershipTimestamp)
ProposedOwnable.sol
emit OwnershipProposed(_proposed)
TOOLS USED
Manual Analysis
MITIGATION
In all instances, the storage variable can be replaced by the function argument that is written to that storage variable.
Group logic in modifier
PROBLEM
You can group repetitive require statements into a modifier to save on deployment costs. There is however a tradeoff to doing this: declaring the logic inline is cheaper than having it in a modifier when you call the function that uses that modifier.
PROOF OF CONCEPT
Instances include:
StableSwap.sol
require(index < swapStorage.pooledTokens.length, "Out of range")
require(index < swapStorage.pooledTokens.length, "Index out of range")
LibDiamonds.sol
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut")
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut")
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut")
SwapUtils.sol
require(dx <= tokenFrom.balanceOf(msg.sender), "Cannot swap more than you own")
require(dx <= tokenFrom.balanceOf(msg.sender), "Cannot swap more than you own")
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "Token index out of range")
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "Token index out of range")
TOOLS USED
Manual Analysis
MITIGATION
You can create modifiers for repetitive require statements and apply them to the functions where the require statement mentioned are called. Again, this one is more of a suggestion because there is a trade-off.
Immutable variables can save gas
PROBLEM
If a storage variable is never modified, marking it as immutable can save gas, as it is saved as a constant instead of a storage variable, saving a
SLOAD
operation (100 gas) every time the variable is read.PROOF OF CONCEPT
Instances include:
ConnextPriceOracle.sol
wrapped
is initialized once and can not be modified afterwards. It is read once in every call togetTokenPrice
.TOOLS USED
Manual Analysis
MITIGATION
add the
immutable
keyword toaddress public wrapped
Prefix increments
IMPACT
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
PROOF OF CONCEPT
Instances include:
BridgeFacet.sol
i++
i++
i++
DiamondLoupeFacet.sol
i++
RelayerFacet.sol
i++
i++
StableSwapFacet.sol
i++
ConnextPriceOracle.sol
i++
StableSwap.sol
i++
LibDiamond.sol
facetIndex++
selectorIndex++
selectorPosition++
selectorIndex++
selectorPosition++
selectorIndex++
SwapUtils.sol
i++
i++
i++
i++
i++
j++
i++
i++
i++
i++
i++
i++
i++
i++
i++
i++
i++
i++
RelayerFeeMessage.sol
i++
TOOLS USED
Manual Analysis
MITIGATION
change
variable++
to++variable
.Redundant libraries
IMPACT
As of Solidity 0.8.0, underflow and overflow checks are performed by default on additions and subtractions. Using an external library for these computations should be avoided as it will waste gas.
PROOF OF CONCEPT
Several contracts in scope use the SafeMath library of OpenZeppelin and its functions
sub
andadd
TOOLS USED
Manual Analysis
MITIGATION
Use the native additions and subtractions.
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:
StableSwap.sol
require(tokenIndexes[address(_pooledTokens[i])] == 0 && _pooledTokens[0] != _pooledTokens[i],"Duplicate tokens")
AmplificationUtils.sol
require(futureA_ > 0 && futureA_ < MAX_A, "futureA_ must be > 0 and < MAX_A")
SwapUtils.sol
require(tokenIndexFrom < numTokens && tokenIndexTo < numTokens, "Tokens must be in pool")
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "Token index out of range")
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "Token index out of range")
require(maxBurnAmount <= v.lpToken.balanceOf(msg.sender) && maxBurnAmount != 0, ">LP.balanceOf")
TOOLS USED
Manual Analysis
MITIGATION
Split the require statement in several statements. For instance:
To improve the savings, you can also use custom errors instead of require statements
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:
ProposedOwnableUpgradeable.sol
TOOLS USED
Manual Analysis
MITIGATION
Place
_routerOwnershipRenounced
after_owner
and_assetOwnershipRenounced
_proposed
to save two storage slotsUnchecked 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. This is particularly true in for loops, as it saves some gas at each iteration.PROOF OF CONCEPT
Instances include:
BridgeFacet.sol
availableAmount - backUnbackedAmount, cannot overflow because of the condition line 1099
DiamondLoupeFacet.sol
i++
StableSwapFacet.sol
i++
ConnextPriceOracle.sol
i++
StableSwap.sol
i++
LibDiamond.sol
facetIndex++
selectorIndex++
selectorIndex++
selectorIndex++
SwapUtils.sol
i++
i++
i++
i++
i++
j++
i++
i++
i++
i++
i++
i++
i++
i++
i++
i++
i++
i++
TOOLS USED
Manual Analysis
MITIGATION
Place the arithmetic operations in an
unchecked
blockUnnecessary computation
IMPACT
When emitting an event that includes a new and an old value, it is cheaper in gas to avoid caching the old value in memory. Instead, emit the event, then save the new value in storage.
PROOF OF CONCEPT
Instances include:
ConnextPriceOracle.sol
line 169
LibDiamond.sol
line 55
ProposedOwnable.sol
line 162
TOOLS USED
Manual Analysis
MITIGATION
Emit the old storage variable before writing the new value.
e.g:
The text was updated successfully, but these errors were encountered: