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
ZkTokenV1
contract points out that the nonce used in both thedelegateBySig
andpermit
functions is the same. Consider mentioning that the same nonce is also used in thedelegateOnBehalf
function. - The documentation for the
_claim
function suggests that this function is only called internally byclaim
andclaimOnBehalf
. However, it is also called byclaimAndDelegate
andclaimAndDelegateOnBehalf
. Consider mentioning this in the documentation. - The documentation for the
ZkMerkleDistributor__ClaimAmountExceedsMaximum
error 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
claimAndDelegate
function suggests that this method cannot be called by smart accounts because it is using the signature parameter that is passed to thedelegateOnBehalf
function of theZkTokenV1
contract. However, thedelegateOnBehalf
function 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__ClaimWindowNotOpen
error, "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
ERC20PermitUpgradeable
import 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
delegateOnBehalf
function inZkTokenV1
will copy the_signature
data to memory. - Both the
claimAndDelegate
andclaimAndDelegateOnBehalf
functions inZkMerkleDistributor
will copy theDelegateInfo
struct 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.