Platform: Code4rena
Start Date: 28/10/2022
Pot Size: $165,500 USDC
Total HM: 2
Participants: 24
Period: 12 days
Judge: GalloDaSballo
Total Solo HM: 1
Id: 177
League: ETH
Rank: 19/24
Findings: 1
Award: $229.76
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
Also found by: Aymen0909, HardlyCodeMan, IllIllI, ReyAdmirado, Rolezn, TomJ, c3phas, gogo, mcwildy
229.7602 USDC - $229.76
 
Certain variables is defined even though they are used only once. Remove these unnecessary variables to save gas. For cases where it will reduce the readability, one can use comments to help describe what the code is doing.
oldFacet
variable of _addFunctions function of Diamond.sol
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/Diamond.sol#L134-L135
Mitigation:
Delete line 134 and replace line 135 with below coderequire(ds.selectorToFacet[selector].facetAddress == address(0), "J"); // facet for this selector already exists
selectorsLength
variable of _saveFacetIfNew function of Diamond.sol
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/Diamond.sol#L187-L189
Mitigation:
Delete line 187 and replace line 189 with below codeif (ds.facetToSelectors[_facet].selectors.length == 0) {
Don't define variable that is used only once. Details are listed on above PoC.
 
Below has redundant use of variables. Instead of defining a new variable, emit the event first and then update the variable.
Total of 2 instances found.
function acceptGovernor() external { address pendingGovernor = s.pendingGovernor; require(msg.sender == pendingGovernor, "n4"); // Only proposed by current governor address can claim the governor rights if (pendingGovernor != s.governor) { emit NewPendingGovernor(pendingGovernor, address(0)); emit NewGovernor(s.governor, pendingGovernor); s.governor = pendingGovernor; delete s.pendingGovernor; } }
function setVerifierParams(VerifierParams calldata _newVerifierParams) external onlyGovernor { emit NewVerifierParams(s.verifierParams, _newVerifierParams); s.verifierParams = _newVerifierParams; }
Instead of defining a new variable, emit the event first and then update the variable like shown in above PoC.
 
The order of struct member can be reordered in a way to reduce the usage amount of storage slots.
Reference from solidity documentation: Finally, in order to allow the EVM to optimize for this, ensure that you try to order your storage variables and struct members such that they can be packed tightly. For example, declaring your storage variables in the order of uint128, uint128, uint256 instead of uint128, uint256, uint128, as the former will only take up two slots of storage whereas the latter will take up three.
Total of 2 instances found.
StoredBlockInfo
of IExecutor.sol
Originally it uses 8 slots but this can be reduced to 7 slots by reordering the struct member like below.
Switch indexRepeatedStorageChanges
(8 bytes) and blockHash
(32 bytes) order,
so that blockNumber
(8 bytes) and indexRepeatedStorageChanges
can fit into 1 slot.
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/interfaces/IExecutor.sol#L17-L18struct StoredBlockInfo { uint64 blockNumber; uint64 indexRepeatedStorageChanges; bytes32 blockHash; uint256 numberOfLayer1Txs; bytes32 priorityOperationsHash; bytes32 l2LogsTreeRoot; uint256 timestamp; bytes32 commitment; }
AppStorage
of Storage.sol
Originally it uses 16 slots but this can be reduced to 15 slots by reordering the struct member like below.
Move bool variable zkPorterIsAvailable
(1 bytes) to bottom of address varialbe pendingGovernor
(20 bytes),
so that these 2 variable can be packed into 1 slot.
https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/Storage.sol#L69-L105struct AppStorage { DiamondCutStorage diamondCutStorage; address governor; address pendingGovernor; bool zkPorterIsAvailable; mapping(address => bool) validators; Verifier verifier; uint256 totalBlocksExecuted; uint256 totalBlocksVerified; uint256 totalBlocksCommitted; mapping(uint256 => bytes32) storedBlockHashes; mapping(uint256 => bytes32) l2LogsRootHashes; PriorityQueue.Queue priorityQueue; IAllowList allowList; VerifierParams verifierParams; bytes32 l2BootloaderBytecodeHash; bytes32 l2DefaultAccountBytecodeHash; }
Reorder struct member like shown in above PoC.
 
X = X + Y costs less gass than X += Y for state variables
Total of 1 instance found.
./DiamondCut.sol:29: s.diamondCutStorage.currentProposalId += 1;
 
State variable that is only set in the constructor and can't be changed afterwards, should be declared as immutable.
Total of 5 instances found.
l2Bridge variable of L1ERC20Bridge.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L47
l2TokenFactory variable of L1ERC20Bridge.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L50
l2ProxyTokenBytecodeHash variable of L1ERC20Bridge.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L53
l2Bridge variable of L1EthBridge.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1EthBridge.sol#L44
l2TokenFactory variable of L2ERC20Bridge.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L23
Change to immutable.
 
When there are multiple conditions in require statement, break down the require statement into multiple require statements instead of using && can save gas.
./ethereum_L2ContractHelper.sol:65: require(version == 1 && _bytecodeHash[1] == bytes1(0), "zf"); // Incorrectly formatted bytecodeHash
Break down into several require statement.
 
If the function is not called internally, it is cheaper to set your function visibility to external instead of public.
Total of 1 instance found.
Mailbox.sol:l2TransactionBaseCost() https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L89
Change the function visibility to external.
 
Certain function is defined even though it is called only once. Inline it instead to where it is called to avoid usage of extra gas.
Total of 21 instances found.
uncheckedAdd() of UncheckedMath.sol This function called only once at line 111 of Executor.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/common/libraries/UncheckedMath.sol#L12 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L111
_L2MessageToLog() of Mailbox.sol This function called only once at line 32 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L75 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L32
_requestL2Transaction() of Mailbox.sol This function called only once at line 113 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L116 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L113
_writePriorityOp() of Mailbox.sol This function called only once at line 131 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L144 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L131
calculateRoot() of Merkle.sol This function called only once at line 68 of Mailbox.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/Merkle.sol#L17 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L68
_depositFunds() of L1ERC20Bridge.sol This function called only once at line 116 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L136 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L116
_getERC20Getters() of L1ERC20Bridge.sol This function called only once at line 155 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L164 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/bridge/L1ERC20Bridge.sol#L155
getSize() of PriorityQueue.sol This function called only once at line 65 of Getters.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/PriorityQueue.sol#L44 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Getters.sol#L65
pushBack() of PriorityQueue.sol This function called only once at line 165 of Mailbox.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/PriorityQueue.sol#L54 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Mailbox.sol#L165
front() of PriorityQueue.sol This function called only once at line 70 of Getters.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/PriorityQueue.sol#L63 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Getters.sol#L70
popFront() of PriorityQueue.sol This function called only once at line 181 of Executor.sol https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/libraries/PriorityQueue.sol#L71 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L181
_deployL2Token() of L2ERC20Bridge.sol This function called only once at line 62 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L73 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/zksync/contracts/bridge/L2ERC20Bridge.sol#L62
_collectOperationsFromPriorityQueue() of Executor.sol This function called only once at line 198 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L177 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L198
_executeOneBlock() of Executor.sol This function called only once at line 211 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L190 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L211
_getBlockProofPublicInput() of Executor.sol This function called only once at line 248 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L274 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L248
_verifyRecursivePartOfProof() of Executor.sol This function called only once at line 264 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L296 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L264
_maxU256() of Executor.sol This function called only once at line 338 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L349 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L338
_createBlockCommitment() of Executor.sol This function called only once at line 65 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L354 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L65
_blockPassThroughData() of Executor.sol This function called only once at line 355 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L362 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L355
_blockMetaParameters() of Executor.sol This function called only once at line 356 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L372 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L356
_blockAuxilaryOutput() of Executor.sol This function called only once at line 357 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L376 https://github.com/code-423n4/2022-10-zksync/blob/456078b53a6d09636b84522ac8f3e8049e4e3af5/ethereum/contracts/zksync/facets/Executor.sol#L357
I recommend to not define above functions and instead inline it at place it is called.
 
It is cheaper gas to use calldata than memory if the function parameter is read only. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. More details on following link. link: https://docs.soliditylang.org/en/v0.8.15/types.html#data-location
Total of 5 instances found.
./ExternalDecoder.sol:10: function decodeString(bytes memory _input) external pure returns (string memory result) { ./ExternalDecoder.sol:15: function decodeUint8(bytes memory _input) external pure returns (uint8 result) { ./Mailbox.sol:43: L2Log memory _log, ./L2StandardERC20.sol:43: function bridgeInitialize(address _l1Address, bytes memory _data) external initializer { ./Executor.sol:152: function commitBlocks(StoredBlockInfo memory _lastCommittedBlockData, CommitBlockInfo[] calldata _newBlocksData)
Change memory to calldata
 
Since EVM operates on 32 bytes at a time, if the element is smaller than that, the EVM must use more operations in order to reduce the elements from 32 bytes to specified size. Therefore it is more gas saving to use 32 bytes unless it is used in a struct or state variable in order to reduce storage slot.
Reference: https://docs.soliditylang.org/en/v0.8.15/internals/layout_in_storage.html
Total of 13 instances found.
./DiamondInit.sol:30: uint64 _genesisIndexRepeatedStorageChanges, ./Mailbox.sol:92: uint32 // _calldataLength ./Mailbox.sol:125: uint64 expirationBlock = uint64(block.number + PRIORITY_EXPIRATION); ./Mailbox.sol:150: uint64 _expirationBlock, ./Diamond.sol:207: uint16 selectorPosition = uint16(ds.facetToSelectors[_facet].selectors.length); ./L1ERC20Bridge.sol:185: uint16 _l2TxNumberInBlock, ./L1ERC20Bridge.sol:228: uint16 _l2TxNumberInBlock, ./L2StandardERC20.sol:12: event BridgeInitialization(address indexed l1Token, string name, string symbol, uint8 decimals); ./L2StandardERC20.sol:29: uint8 private decimals_; ./L2StandardERC20.sol:126: function decimals() public view override returns (uint8) { ./L1EthBridge.sol:138: uint16 _l2TxNumberInBlock, ./L1EthBridge.sol:185: uint16 _l2TxNumberInBlock, ./ethereum/contracts/common/L2ContractHelper.sol:64: uint8 version = uint8(_bytecodeHash[0]);
I suggest using uint256 instead of anything smaller and downcast where needed.
 
There are function with empty blocks. These should do something like emit an event or reverting. If not it should be removed from the contract.
Total of 2 found.
DiamondInit.sol:15: constructor() reentrancyGuardInitializer {}
IZkSync.sol:11:interface IZkSync is IMailbox, IGovernance, IExecutor, IDiamondCut, IGetters {}
 
#0 - miladpiri
2022-11-23T14:54:41Z
It can be as high quality report!
#1 - GalloDaSballo
2022-11-25T01:49:33Z
Defined Variables Used Only Once Minor memory savings (Storage is packed into 1 slot), most likely 3 for memory * 2 + some extra math for unpacking, maybe 20 gas 26
Redundant Use of Variable 6 gas
Struct Member can be Packed into Fewer Storage Slots 4k
Unchanging State Variable Should be Immutable Ignoring as mostly off (1 / 5 is correct)
X = X + Y costs less gass than X += Y 34
Use require instead of && 3
Change Function Visibility Public to External Skipping
Internal Function Called Only Once can be Inlined 21*24 = 504
Use Calldata instead of Memory for Read Only Function Parameters Some will not compile, skipping for lack of detail
Using Elements Smaller than 32 bytes (256 bits) Might Use More Gas Skipping
Empty Blocks Should Emit Something or be Removed Same
#2 - GalloDaSballo
2022-12-02T21:56:46Z
4573
#3 - c4-judge
2022-12-03T20:56:44Z
GalloDaSballo marked the issue as grade-b