- February 5, 2025
OpenZeppelin Security
OpenZeppelin Security
Security Audits
Table of Contents
Summary
- Type
- Token
- Timeline
- From 2024-05-23
- To 2024-05-28
- Languages
- Solidity
- Total Issues
- 4 (4 resolved)
- Critical Severity Issues
- 0 (0 resolved)
- High Severity Issues
- 0 (0 resolved)
- Medium Severity Issues
- 0 (0 resolved)
- Low Severity Issues
- 1 (1 resolved)
- Notes & Additional Information
- 3 (3 resolved)
Scope
We performed a diff audit of the zksync-association/zk-governance repository at commit 27763f1 against commit 08ec4e7.
In scope were the following files:
src
├── ZkMerkleDistributor.sol
├── ZkTokenV1.sol
├── interfaces
│ ├── IMintable.sol
│ └── IMintableAndDelegatable.sol
└── lib
└── Nonces.sol
We also performed a full audit of the zksync-association/zk-governance repository at commit 410452d for the following file:
src
└── ZkTokenV2.sol
System Overview
The ZkTokenV1 contract is the core of the system and is going to be the governance token of ZKsync Era. It is an ERC-20 token with permit functionality which incorporates a voting delegation system so that token holders can delegate their voting power to a trusted representative while preserving the actual token value.
In addition to the functionalities described in the previous audit report, the system contains the following updates.
ZkTokenV1 Contract
A new function was added to allow delegating votes from a signer to a delegatee, adding EIP-1271 support in addition to the ECDSA signature.
ZkMerkleDistributor Contract
The claiming approach has been updated. Users now have more options while claiming their tokens. Previously, users could claim and delegate simultaneously or they could claim on behalf of others and delegate simultaneously. The updated version adds two new separate functions to claim tokens, one to claim on behalf of others and one to claim for yourself, but without any voting rights delegation.
ZkTokenV2 Contract
This additional token contract extends from the V1 contract and was introduced with the sole purpose of renaming the token from zkSync to ZKsync, since the token was already deployed at the time of reviewing, according to the ZKsync Association team. No further features were added to it.
Low Severity
Inconsistency in Signature Verification
When claiming tokens on behalf of another account using claimOnBehalf or claimAndDelegateOnBehalf, the claim information includes an expiry field which indicates at which block.timestamp this signature would expire. In case the block.timestamp is equal to or larger than the expiry ([1] [2]), the claiming operation will fail.
When delegating tokens using claimAndDelegate or claimAndDelegateOnBehalf, the delegate information includes a similar expiry field. However, in this case, the delegation will only fail in case the block.timestamp is strictly greater than the expiry.
Consider modifying the signature expiry verification process for either the claiming signature or the delegation signature in order to make it consistent for both types of signatures.
Update: Resolved at commit 410452d. The signature expiry verification processes of both the claiming signature and delegation signature are now consistent with each other. They both check whether the expiration timestamp is strictly smaller than the current block.timestamp.
Notes & Additional Information
Incorrect or Misleading Documentation
Throughout the codebase, a few instances of incorrect or misleading documentation were identified:
- The documentation of the
ZkTokenV1contract points out that the nonce used in both thedelegateBySigandpermitfunctions is the same. Consider mentioning that the same nonce is also used in thedelegateOnBehalffunction. - The documentation for the
_claimfunction suggests that this function is only called internally byclaimandclaimOnBehalf. However, it is also called byclaimAndDelegateandclaimAndDelegateOnBehalf. Consider mentioning this in the documentation. - The documentation for the
ZkMerkleDistributor__ClaimAmountExceedsMaximumerror states that this error is thrown when the total amount of claimed tokens exceeds the total amount claimed. However, the error should be thrown when the total amount claimed exceeds the maximum claimable amount. - The documentation for the
claimAndDelegatefunction suggests that this method cannot be called by smart accounts because it is using the signature parameter that is passed to thedelegateOnBehalffunction of theZkTokenV1contract. However, thedelegateOnBehalffunction does support EIP-1271 and ECDSA signatures, thereby supporting smart accounts as well. Additionally, consider changing "smart contract accounts" to "smart accounts" to be in conformity with the official zkSync documentation. - In the documentation for the
ZkMerkleDistributor__ClaimWindowNotOpenerror, "or" should be removed.
Clear and accurate documentation helps users and developers understand the codebase. Consider reviewing all the documentation and updating any incorrect and/or misleading statements.
Update: Resolved at commit 410452d. All the instances of incorrect or misleading documentation have been addressed.
Unused Imports
The following instance of unused imports was identified and can be removed:
- The
ERC20PermitUpgradeableimport inZkTokenV1.sol
Consider removing any unused imports to improve the overall clarity and readability of the codebase.
Update: Resolved at commit 410452d. The ERC20PermitUpgradeable import has been removed.
Gas optimizations
Throughout the codebase, the following instances of code could benefit from gas cost optimization:
- The
delegateOnBehalffunction inZkTokenV1will copy the_signaturedata to memory. - Both the
claimAndDelegateandclaimAndDelegateOnBehalffunctions inZkMerkleDistributorwill copy theDelegateInfostruct to memory.
The arguments mentioned above are read-only as none of their values are being modified within the functions. Consider changing the locations of these arguments from memory to calldata in order to reduce the gas amount needed to execute the functions.
Update: Resolved at commit 410452d. All the suggested gas optimizations have been applied on the codebase.
Conclusion
This update adds EIP-1271 support to the ZkTokenV1 and ZkMerkleDistributor contracts in order to expand claiming and voting delegation options using the signatures of smart accounts. In addition, as a result of updates made to the claiming procedure, users now have more flexibility when claiming their tokens.
The audit yielded one low-severity issue along with a few recommendations for code improvement. The codebase is well-written, straightforward to follow, and well-documented. The codebase is a collaborative effort between ZKsync Association and ScopeLift. Both teams were very responsive throughout the engagement and answered all our questions.
