Table of Contents
- Table of Contents
- Summary
- Scope
- System Overview
- Security Model and Trust Assumptions
- Medium Severity
- Low Severity
- Notes & Additional Information
- Misleading Contract Name
- Multiple Instances of Missing Named Parameters in Mappings
- Incomplete Documentation of Differences Between WETH9 and L2Weth Contracts
- Unnecessary Cast
- State Variable Visibility Not Explicitly Declared
- Lack of Security Contact
- Lack of Indexed Event Parameters
- Unused Constants
- Non-Explicit Imports Are Used
- Literal Number With Many Digits
- Unused Import
- Typographical Errors
- Missing and Incomplete Docstrings
- Function Is Updating the State Without Event Emissions
- Conclusion
Summary
- Type
- ZK Rollup
- Timeline
- From 2023-12-04
- To 2023-12-22
- Languages
- Solidity
- Total Issues
- 21 (16 resolved, 1 partially resolved)
- Critical Severity Issues
- 0 (0 resolved)
- High Severity Issues
- 0 (0 resolved)
- Medium Severity Issues
- 2 (1 resolved, 1 partially resolved)
- Low Severity Issues
- 5 (4 resolved)
- Notes & Additional Information
- 14 (11 resolved)
Scope
We audited the diff of the matter-labs/era-contracts repository from the base at commit c44852f and the head at commit 2e0734b, as well as the diff of the matter-labs/era-system-contracts repository from the base at commit 4dca36d and the head at commit 0b238cd. New and marked contracts were audited in full while the rest of the in-scope codebase was audited only for the diffs.
In scope were the following contracts:
era-contracts/
├─ ethereum/
│ └─ contracts/
│ ├─ bridge/
│ │ ├─ L1ERC20Bridge.sol
│ │ ├─ L1WethBridge.sol
│ │ └─ interfaces/
│ │ └─ IL1Bridge.sol
│ ├─ common/
│ │ ├─ L2ContractAddresses.sol
│ │ ├─ ReentrancyGuard.sol
│ │ └─ libraries/
│ │ ├─ L2ContractHelper.sol
│ │ ├─ UncheckedMath.sol
│ │ └─ UnsafeBytes.sol
│ ├─ governance/
│ │ ├─ Governance.sol
│ │ └─ IGovernance.sol
│ ├─ upgrades/
│ │ ├─ BaseZkSyncUpgrade.sol
│ │ └─ DefaultUpgrade.sol
│ └─ zksync/
│ ├─ Config.sol
│ ├─ DiamondInit.sol
│ ├─ DiamondProxy.sol
│ ├─ Storage.sol
│ ├─ ValidatorTimelock.sol (full audit)
│ └─ libraries/
│ ├─ Diamond.sol
│ ├─ LibMap.sol
│ ├─ Merkle.sol
│ └─ PriorityQueue.sol
└─ zksync/
└─ contracts/
├─ L2ContractHelper.sol
└─ bridge/
├─ L2ERC20Bridge.sol
├─ L2StandardERC20.sol
├─ L2Weth.sol
└─ L2WethBridge.sol
era-system-contracts/
└─ contracts/
├─ BootloaderUtilities.sol
├─ ComplexUpgrader.sol
├─ Constants.sol
├─ ImmutableSimulator.sol
├─ KnownCodesStorage.sol
├─ L2EthToken.sol
├─ MsgValueSimulator.sol
├─ NonceHolder.sol
├─ SystemContext.sol
├─ interfaces/
│ ├─ IComplexUpgrader.sol
│ ├─ ICompressor.sol
│ ├─ IKnownCodesStorage.sol
│ ├─ IL1Messenger.sol
│ ├─ ISystemContext.sol
│ ├─ ISystemContextDeprecated.sol
│ └─ ISystemContract.sol
└─ libraries/
├─ RLPEncoder.sol
├─ SystemContractHelper.sol
└─ UnsafeBytesCalldata.sol
System Overview
This system upgrade refactors existing code while also adding new contracts to the protocol. Most prominent is the introduction of the new Governance
contract which has been implemented to slow down the execution of protocol changes. In addition, future upgrades will be based on the new DefaultUpgrade
contract. These contracts will be explained in detail below, followed by an outline of changes for the existing contracts.
Governance Contract
The new Governance contract is inspired by OpenZeppelin's TimelockController
and the in-house Diamond Proxy upgrade mechanism. This contract is fundamental to managing and coordinating upgrades and changes across Matter Labs' governed zkSync Era contracts. The Governance contract's functionalities include the following:
- Operational Management: The
Governance
contract's owner can schedule, execute, and cancel operations, while ensuring that each step occurs with the appropriate permissions and after the set delay. - Transparent and Shadow Upgrades: The
Governance
contract supports two types of upgrades: transparent upgrades with on-chain data exposure, and shadow upgrades where the operation data is not published on-chain before the execution. This dual approach balances transparency with security needs. - Security Council: A unique feature of the
Governance
contract is the integration of a security council, presumably a multisig contract. This council plays a crucial role in overseeing operations and is capable of executing scheduled proposals instantly. This adds a layer of flexibility and rapid response capabilities to the governance process. - Timelock Mechanism: The contract enforces a timelock mechanism, ensuring that operations are subject to a delay before they can be executed. This feature prevents abrupt changes and gives the community time to review and respond to proposed updates or actions. The security council is able to skip the delay for instant execution of scheduled operations.
Upgrade Contracts
The BaseZkSyncUpgrade
contract provides a foundation for upgrading the zkSync protocol. It introduces a ProposedUpgrade
struct to represent an upgrade proposal. It enforces the upgrade to happen at a given time and provides functions to do the following:
- Set the L2 bootloader bytecode hash.
- Set the default account bytecode hash.
- Set the new verifier address and its parameters.
- Set the L2 transaction hash that can perform more L2 contract changes. This hash is checked against during batch finalization in the
Executor
. - Set a new protocol version.
The DefaultUpgrade
contract extends BaseZkSyncUpgrade
and serves as a default implementation for upgrading the zkSync protocol. It defines placeholder functions for custom logic which is used to upgrade L1 contracts and perform post-upgrade actions. The upgrade function orchestrates the entire upgrade process, invoking functions from the base contract.
Era Contracts Changes
Further era-contracts-related changes involve the following:
- The
ValidatorTimelock
contract now stores batch timestamps as 32-bit values instead of 256-bit values to use less storage. A modified version ofvectorized/solady
'sLibMap
is used to pack eight 32-bit values into one storage slot. - The
ValidatorTimelock::executeBatches
function now allows for committing, proving, and executing a batch within the same block. Previously, it was not possible to commit, prove, and execute a batch in the same block. - With the removal of the
AllowList
contract, thesenderCanCallFunction
modifier was also removed from the affected functions. - The deposit limit check as part of the
AllowList
functionality was also taken out of theL1ERC20Bridge
. - The
L2StandardERC20
contract was extended with a reinitialization function to overwrite the token metadata. - Several constants were added to some in-scope contracts.
- Formatting, documentation, and code-style changes were made in some in-scope contracts.
- The Solidity version was upgraded to 0.8.20 for all in-scope contracts.
Era System Contracts Changes
The era-system-contracts changes are as follows:
- The
SystemContractHelper::unsafePrecompileCall
internal function no longer ensures that there is enough gas for burning. - The
SystemContractHelper::precompileCall
internal function was removed. - The
SystemContractHelper::getZkSyncMeta
internal function now assigns values to theZkSyncMeta::heapSize
andZkSyncMeta::auxHeapSize
return parameters. - The
UnsafeBytesCalldata::readUint256
internal function was added. - The
NonceHolder::incrementDeploymentNonce
function no longer has theISystemContract::onlySystemCall
modifier but still requires the message sender to beDEPLOYER_SYSTEM_CONTRACT
. - The
KnownCodesStorage::_burnGas
internal function was removed. SystemLogKey::NEW_TIMESTAMP_KEY
was renamed toSystemLogKey::PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY
and now contains both the 128-bit batch timestamp and the 128-bit L2 block timestamp in 256 bits.- Several constants were added to some in-scope contracts.
- Formatting, documentation, and code-style changes were made in some in-scope contracts.
- The Solidity version was upgraded to 0.8.20 for all in-scope contracts.
Security Model and Trust Assumptions
This new upgrade introduces trust assumptions that are mostly related to the new governance contract:
- The owner of the
Governance
andTimelockValidator
contracts is anticipated to be a multisig setup conforming to industry standards and incorporating the use of cold wallets. - The security council is envisioned to be a multisig entity characterized by diversity, substantial size, and a high decision-making threshold. As of the time of this audit, the exact design and composition of the security council are still under development.
- In an event where both the owner and the security council are compromised, they still have the capability to execute immediate actions. The expectation is that these entities always act with the userbase's best interests in mind.
- The protocol is supposed to set a delay that provides users with enough time to withdraw their funds in case of any suspicious activities.
Privileged Roles
Governance
Contract Owner: Possesses the authority to implement significant changes within the protocol, albeit with a built-in delay to allow for community reaction and response.- Security Council: The only actor that can bypass the delay in case of emergencies or immediate threats.
ValidatorTimelock
Contract Owner: Possesses the authority to implement significant changes by updating the verifier EOA address and the minimum delay required between committing and executing batches.
The design of these roles shows a balance between centralized control and distributed trust, aiming to safeguard the protocol while enabling effective governance and rapid response to any arising challenges or threats.
Medium Severity
Overwriting ERC-20 Metadata Can Cause Unexpected Behaviors
The L2StandardERC20
contract is the default contract that is deployed when any ERC-20 token is bridged for the first time. This contract has been extended with a reinitializeToken
function that allows the Beacon owner to overwrite the symbol
, name
, and decimals
state variables. This feature is particularly beneficial when token developers aim to deploy an official custom contract on the L2, enabling differentiation between the official and default token by altering the symbol and the name. While the rationale for updating the token symbol and name is valid, modifying the decimal value could result in unpredictable behavior and thus introduce significant risk. Furthermore, note that by setting a new name in the EIP712Upgradeable
contract through the ERC20PermitUpgradeable
contract, previously signed permits will become invalid.
To mitigate these risks, consider removing the option of overwriting the decimals
value in the reinitializeToken
function. Moreover, consider issuing clear warnings regarding any changes in the token's name in order to ensure that users are fully aware of the potential invalidation of their previously signed permits.
Update: Resolved in pull request #139 at commit a6ceba0. The decimals
overwrite was removed from the function.
Critical and High Vulnerabilities in Yarn Module Dependencies
The project's current dependencies include versions of yarn modules that are known to have critical and high vulnerabilities. The following dependencies are not imported directly but are nonetheless included as part of other project dependencies:
In the era-contracts
repository:
lodash
(4.17.20)json5
(0.5.1)get-func-name
(2.0.0)underscore
(1.9.1)minimatch
(3.0.4)crypto-js
(3.3.0)browserify-sign
(4.0.0)http-cache-semantics
(4.0.0)flat
(4.1.0)node-fetch
(1.7.3)async
(2.6.3)
In the era-system-contracts
repository:
To mitigate the risks associated with these vulnerabilities, consider upgrading these modules to versions that have resolved those issues. This may involve updating the primary dependencies that include these modules. If the vulnerable modules are part of unmaintained projects, consider seeking alternative modules that offer similar functionality without the security risks. In cases where immediate replacement or upgrading is not feasible, it is crucial to document these issues clearly and address the potential risks in the project's documentation and security protocols. This proactive approach ensures awareness and readiness to handle any security challenges that might arise from these vulnerabilities.
Update: Partially resolved in pull request #98 at commit ada1bac, and in pull request #153 at commit e9246e3. The Matter Labs team stated:
We upgraded most of the listed dependencies, except for
flat
andminimatch
. These dependencies belong to the Hardhat gas reporter which is still in use in our repo today. Since the main product of the repo is smart contracts, the corresponding JavaScript vulnerabilities cannot be exploited.
Furthermore, it was recommended to set up Dependabot to monitor vulnerable dependencies and keep them up to date more automatically.
Low Severity
minDelay
Set to Zero Undermines Security Council Purpose
The Governance
contract is a timelock that allows scheduling operations that make critical changes to the protocol. These scheduled operations can be executed after waiting for a minDelay
period in seconds. During construction or through a scheduled operation, this minDelay
can be set to zero (or a low value).
This option of a zero minDelay
has the following implications:
- A compromised owner can perform malicious actions immediately.
- Hence, the role of the security council to skip the delay is obsolete.
- Users do not have enough time to withdraw their funds.
To mitigate these risks, consider defining a constant minimum limit for minDelay
(e.g., by facilitating the UPGRADE_NOTICE_PERIOD
constant), thereby allowing adequate time for stakeholders to respond to any proposed modifications.
Update: Acknowledged, not resolved. While the team intends to have a non-zero minDelay
after the initial stage, a minimal minDelay
has not been hard coded. The Matter Labs team stated:
At the initial stage of the protocol, the
minDelay
is 0. It is part of the initial training wheels of the protocol to allow for swift reaction in case of a needed emergency upgrade. While the alternative to achieve the same effect is to leverage the security council (basically set the security council to be the same entity as the owner of the Governance), having it as zero is more explicit and easier to maintain.
Undisclosed Functionality Mismatch in L1 and L2 Token Functions Post-Bridging
When bridging tokens from L1 to L2, by default, the L2StandardERC20
contract will be deployed initially. However, this default implementation can introduce a gap in functionalities when compared to their L1 counterparts, such as rebase methods or voting capabilities. This discrepancy can cause confusion for users or developers trying to interact with these tokens like they would on L1.
To enhance transparency, consider adding a disclaimer to the existing note in the documentation as well as to the ERC-20 bridge contracts documentation [1, 2]. In addition, the user interface of the bridge, which is controlled by Matter Labs, should inform users of the potential limitations in token functionality post-bridging.
Update: Resolved in pull request #140 at commit 5cbf05b, and in pull request #845 at commit 82232bc.
Inaccurate Attribution in LibMap.sol
In the LibMap.sol
contract, specifically at line 5, the authorship is attributed to Solady. However, upon examination, it is clear that the code is not directly sourced from Solady but rather draws inspiration from their work. This misattribution could lead to confusion regarding the origins and the licensing of the code.
To maintain clarity and proper intellectual property acknowledgment, consider revising the comment to accurately reflect the nature of Solady's influence, perhaps indicating that the implementation is inspired by Solady's work rather than directly copied from there.
Update: Resolved in pull request #141 at commit 9712419.
Documentation Mismatch
Throughout the codebase, there are several documentation mismatches:
- In the
Governance.sol
contract, specifically within thecancel
method, the documentation indicates that both the owner and thesecurityCouncil
have the ability to cancel. However, the implementation restricts this action solely to the owner through theonlyOwner
modifier. This means in the event of the owner account getting compromised, thesecurityCouncil
cannot intervene. - In the
L2ContractHelper
contract, theIL2Messenger
interface is described for sending arbitrary-length messages from L2 to L1. It describes the four parameterssenderAddress
,isService
,key
, andvalue
of the underlying operation, but then refers toisService
asmarker
. Consider sticking to one word for consistency. - In
LibMap.sol
, the_index
parameter of theset
function is inaccurately described. The documentation states "The index of the uint32 value to retrieve", whereas it is used for setting the value. - The documentation of the
verifierParams
element of theProposedUpgrade
struct says that "If either of its fields is 0, the params will not be updated". However, in the_setVerifierParams
function, the action of setting new parameters is only skipped when all parameters are zero. Consider correcting the documentation to match the implementation.
Update: Resolved in pull request #142 at commit a320b9b.
Upgrade Validation and Execution Is Not Strict
The BaseZkSyncUpgrade
contract is an abstract contract used to perform standardized upgrades on the rollup. To ensure stricter execution and validation in this process, consider the following suggestions.
The DefaultUpgrade
contract extends the BaseZkSyncUpgrade
contract to invoke its functions from the overridden upgrade
function using the values passed in through the ProposedUpgrade
struct. In order to enforce that the new values will be set through this struct, and given that the BaseZkSyncUpgrade
functions will only execute on non-zero inputs (otherwise it will be a no-op), consider moving the invoked functions from DefaultUpgrade.upgrade
to BaseZkSyncUpgrade.upgrade
to provide a stricter execution through the parent contract.
In addition, the _setVerifier
function is used to set the verifier contract that will be used on L1 to verify the ZK proofs of the L2 rollup. As the documentation explains, batches that have been committed but not verified with the old verifier cannot be verified afterwards.
To prevent this mistake on a smart contract level, consider checking that the number of batches verified matches the number of the batches committed in the storage.
Update: Resolved in pull request #158 at commit b34ca08. The Matter Labs team stated:
We decided to not check that all the batches are verified, due to the following reasons:
- Firstly, it is not the only thing that needs to be checked. For instance, we need to ensure that all the batches with the old version have been committed prior to executing the upgrade. This cannot be done on-chain.
- The following scenario is possible:
- We may start an upgrade (that fixes some theoretical issue with the verifier, i.e., the system does not change its logic but there is an edge case whereby a batch may become unprovable and we are trying to fix it).
- Let's assume that we introduced a delay in upgrades (i.e., they are not instant). During the waiting period, the issue has been triggered. Meaning, we cannot achieve the "all committed blocks must be verified" requirement without the upgrade itself.
- Now, in order to turn off the "all committed blocks must be verified" requirement, we will have to restart the waiting period (or do an emergency upgrade) to use the upgrade implementation that does not enforce it.
While the benefits are clear, it introduces edge cases that are rather easier to avoid by just always double-checking the requirements off-chain before triggering the upgrade.
Notes & Additional Information
Misleading Contract Name
The Governance
contract, while descriptive of its operational role, can cause confusion given its name. The term "Governance" could be perceived as encompassing all governance aspects, such as voting, proposal submissions, and community decision-making. However, as the documentation suggests, the purpose of this contract is to act as a timelock for operations that change critical properties and functionalities of the protocol.
For clarity, consider naming the contract in a way that relates to its implementation (e.g., "TimelockedGovernor" or "UpgradeScheduler").
Update: Acknowledged, not resolved. The Matter Labs team stated:
The current name was used because this contract is intended to serve as the
governor
of the mainDiamondProxy
. While it is true that switching to a name that better represents the functionality of the contract could be considered, theGovernance
contract has already been deployed. To avoid the risk-prone process of migrating the governance, and the discrepancies between the open-source repository and the deployed code, it is better to avoid renaming the contract at this point.
Multiple Instances of Missing Named Parameters in Mappings
Since Solidity 0.8.18, developers can utilize named parameters in mappings. This means mappings can take the form of mapping(KeyType keyName => ValueType valueName)
. This updated syntax provides a more transparent representation of a mapping's purpose.
Throughout the codebase, there are multiple mappings without named parameters:
- The
selectorToFacet
andfacetToSelectors
state variable in theDiamond
library - The
map
state variable in theLibMap
contract - The
data
state variable in thePriorityQueue
contract - The
immutableDataStorage
state variable in theImmutableSimulator
contract - The
balance
state variable in theL2EthToken
contract - The
rawNonces
andnonceValues
state variables in theNonceHolder
contract - The
batchHash
state variable in theSystemContext
contract - The
timestamps
state variable in theGovernance
contract - The
isWithdrawalFinalized
,depositAmount
, andtotalDepositedAmountPerUser
state variables in theL1ERC20Bridge
contract - The
isWithdrawalFinalized
state variable in theL1WethBridge
contract - The
l1TokenAddress
state variable in theL2ERC20Bridge
contract
Consider adding named parameters to the mappings in order to improve the readability and maintainability of the codebase.
Update: Resolved in pull request #143 at commit 647a798, and in pull request #96 at commit 8b19ffc.
Incomplete Documentation of Differences Between WETH9
and L2Weth
Contracts
The documentation for the L2Weth
contract outlines its differences from the WETH9
contract. However, this list is not exhaustive. Key functionalities like the added depositTo
and withdrawTo
methods, along with the bridge functions, are missing.
To enhance clarity and accuracy, consider expanding the list of differences to include these additional features or rephrasing the documentation to indicate that the listed differences are not exhaustive.
Update: Resolved in pull request #144 at commit 0f5d29d. The depositTo
and withdrawTo
functions were added to the documentation as WETH9 differences. The bridge-related differences were not specifically mentioned, but these functions are not callable by users.
Unnecessary Cast
At line 328 of L1ERC20Bridge
, the l2TokenBeacon
variable is already an address
type, but is unnecessarily cast explicitly as an address
.
To improve the overall clarity, intent, and readability of the codebase, consider removing unnecessary casts.
Update: Resolved in pull request #145 at commit 7759538.
State Variable Visibility Not Explicitly Declared
Throughout both codebases, there are state variables that lack an explicitly declared visibility.
In the era-contracts
repository:
- The state variables
DIAMOND_INIT_SUCCESS_RETURN_VALUE
andDIAMOND_STORAGE_POSITION
inDiamond.sol
- The state variable
CREATE2_PREFIX
inL2ContractHelper.sol
- The state variable
availableGetters
inL2StandardERC20.sol
In the era-system-contracts
repository:
- the state variables
DEPLOY_NONCE_MULTIPLIER
andMAXIMAL_MIN_NONCE_INCREMENT
inNonceHolder.sol
For clarity, consider always explicitly declaring the visibility of variables, even when the default visibility matches the intended visibility.
Update: Resolved in pull request #97 at commit 20a8fb7, and in pull request #146 at commit 93c019d.
Lack of Security Contact
Providing a specific security contact (such as an email or ENS name) within a smart contract significantly simplifies the process for individuals to communicate if they identify a vulnerability in the code. This practice is quite beneficial as it permits the code owners to dictate the communication channel for vulnerability disclosure, eliminating the risk of miscommunication or failure to report due to a lack of knowledge on how to do so. Even interfaces and libraries, which may not be deployed in and of themselves, may be used by another protocol, which would need a security contact when coming across a vulnerability in the integration.
Throughout both codebases, the following libraries and interfaces do not have a security contact.
In the era-contracts
repository:
- The
IGovernance
interface - The
IL1Bridge
interface - The
LibMap
library
In the era-system-contracts
repository:
- The
IComplexUpgrader
interface - The
ICompressor
interface - The
IKnownCodesStorage
interface - The
IL1Messenger
interface - The
ISystemContext
interface - The
ISystemContextDeprecated
interface - The
ISystemContract
abstract contract
Consider placing a NatSpec comment containing a security contact above the contract, library, or interface definitions. Using the @custom:security-contact
convention is recommended as it has been adopted by the OpenZeppelin Wizard and the ethereum-lists.
Update: Resolved in pull request #149 at commit c6ed3fa, and in pull request #99 at commit f4a906e.
Lack of Indexed Event Parameters
Throughout the codebase, two events do not have their parameters indexed:
- The
ChangeSecurityCouncil
event inIGovernance.sol
- The
NewValidator
event inValidatorTimelock.sol
Consider indexing event parameters to improve the ability of off-chain services to search and filter for specific events.
Update: Acknowledged, not resolved. The Matter Labs team stated:
Using Indexed parameters could indeed be preferred. However, at this point, it is better not to add them due to the following considerations:
- These contracts are not upgradable. To upgrade
ValidatorTimelock
, we need to deploy a new one, introducing additional risks during the process of migrations as the commitment times between batches will not be migrated. The migration of governance is even more risk-prone and time-consuming.- These changes might be breaking for those who already track these events.
Unused Constants
The following constants were identified as being unused:
L2_BYTECODE_COMPRESSOR_SYSTEM_CONTRACT_ADDR
INITIAL_STORAGE_CHANGE_SERIALIZE_SIZE
REPEATED_STORAGE_CHANGE_SERIALIZE_SIZE
UPGRADE_NOTICE_PERIOD
MAX_PUBDATA_PER_BATCH
PRIORITY_TX_MAX_PUBDATA
Consider clarifying whether these constants are necessary. Otherwise, consider removing them.
Update: Resolved in pull request #154 at commit 23a7381.
Non-Explicit Imports Are Used
The use of non-explicit imports in the codebase can decrease the code clarity, and may create naming conflicts between locally defined and imported variables. This is particularly relevant when multiple contracts exist within the same Solidity files or when inheritance chains are long.
Throughout the codebases, global imports are being used. For instance:
- All imports in
BootloaderUtilities.sol
- 1 import in
ImmutableSimulator.sol
- 3 imports in
MsgValueSimulator.sol
- 2 imports in
NonceHolder.sol
- 5 imports in
BaseZkSyncUpgrade.sol
- All imports in
DefaultUpgrade.sol
- All imports in
Diamond.sol
- All imports in
Merkle.sol
- All imports in
Storage.sol
- All imports in
ValidatorTimelock.sol
- All imports in
L1ERC20Bridge.sol
- 11 imports in
L1WethBridge.sol
- 7 imports in
L2ERC20Bridge.sol
- All imports in
L2StandardERC20.sol
- All imports in
L2Weth.sol
- 5 imports in
L2WethBridge.sol
Following the principle that clearer code is better code, consider using named import syntax import {A, B, C} from "X";
to explicitly declare which contracts are being imported.
Update: Resolved in pull request #100 at commit 9926470, and in pull request #155 at commit df43911.
Literal Number With Many Digits
In the SystemContext
contract, the block difficulty is defined as 2500000000000000
. Literal numbers with many digits are hard to parse and harm the readability of the code.
Consider using the scientific notation instead.
Update: Resolved in pull request #101 at commit 0666f02.
Unused Import
The import of SystemContractHelper
is not used throughout the KnownCodesStorage
contract.
Consider removing this import to improve the overall clarity and readability of the codebase.
Update: Resolved in pull request #102 at commit 8152d5c.
Typographical Errors
The following typographical errors were identified throughout the codebases:
- "Propage" should be "Propagate"
- "where" should be "when"
- The following revert strings should say "is allowed":
- The following comments should say "[...] the L2 bridge [...]":
- "Structure used to represent zkSync transaction" should be "a zkSync transaction" or "zkSync transactions"
- The list of parameters in an L2 to L1 message is missing a comma
- "The struct that describes for the users will be charged for pubdata for L1->L2 transactions" should be "The struct that describes whether users [...]"
- "occured" should be "occurred"
Update: Resolved in pull request #156 at commit 3d7cc8e, and in pull request #103 at commit 508f6e8.
Missing and Incomplete Docstrings
Throughout the codebases, some parts are missing docstrings while some docstrings are incomplete:
- The
fallback
function of theMsgValueSimulator
contract does not have any docstrings. - The
isNonceUsed
function of theNonceHolder
contract does not have any docstrings. - In the
upgrade
function of theBaseZkSyncUpgrade
contract, the_proposedUpgrade
parameter is not documented. - The
initialize
function of theL2ERC20Bridge
contracts does not have any docstrings. - The
reinitializeToken
function of theL2StandardERC20
contract does not use proper NatSpec comments and none of its parameters are documented.
Consider thoroughly documenting all functions (and their parameters) that are part of any contract's public API. Functions implementing sensitive functionality, even if not public, should be documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Update: Resolved in pull request #157 at commit efab770, and in pull request #104 at commit 16bfe51.
Function Is Updating the State Without Event Emissions
In the constructor
of the ValidatorTimelock
contract, the initial values for the executionDelay
and validator
state variables are set. However, the corresponding events, NewExecutionDelay
and NewValidator
, are not emitted at this point. While these state variables can optionally be changed later, and such changes are communicated through the events, the absence of initial values means that the complete history is not accurately reflected in the emitted events.
Consider emitting events in the constructor
in order to enable off-chain indexers to track the complete history for these particular values.
Update: Acknowledged, not resolved. The Matter Labs team stated:
The addition of these events is preferred. However, to upgrade
ValidatorTimelock
, we need to deploy a new one, introducing additional risks during the process of migrations as the commitment times between batches will not be migrated. So, to avoid additional risks with redeployment, or discrepancies between the open-source repo and the deployed code, it is better not to introduce these events at this point.
Conclusion
This system upgrade introduces important measures aimed at improving the safety of the protocol. Delaying the protocol changes through the Governance
contract and the finalization of L2 batches through the ValidatorTimelock
contract allows the stakeholders to act on any suspicious activity in time. Of course, such measures are only as safe as the implementation of privileged roles and security properties, which is still under exploration for the security council.
The risk of vulnerabilities in yarn dependencies and the overwriting of token metadata have been presented as medium-severity issues. Other than that, we found the code quality to be good and only reported low-severity issues and improvement recommendations. Throughout the audit period, the Matter Labs team was helpful and responsive in answering our questions.