MondrianWallet2::executeTransaction
allows for breaking of a fundamental invariant of ZKSync: the uniqueness of (sender, nonce) pairs in transactions.Description: As the ZKSync documentation states:
One of the important invariants of every blockchain is that each transaction has a unique hash. [...]
Even though these transactions would be technically valid by the rules of the blockchain, violating hash uniqueness would be very hard for indexers and other tools to process. [...]
One of the easiest ways to ensure that transaction hashes do not repeat is to have a pair (sender, nonce) always unique.
The following protocol [on ZKSync] is used:
Before each transaction starts, the system queries the NonceHolder to check whether the provided nonce has already been used or not.
If the nonce has not been used yet, the transaction validation is run. The provided nonce is expected to be marked as "used" during this time.
After the validation, the system checks whether this nonce is now marked as used.
In short, for ZKSync to work properly, each transaction that is executed needs to have a unique (sender, nonce) pair. The MondrianWallet::validateTransaction
function ensures this invariance holds, by increasing the nonce with each validated transaction.
Usually, ZKSync's bootloader of calls validate before executing a transaction and checks if a transaction has already been executed. However, because in MondrianWallet2
the owner of the contract can also execute a transaction, they can choose to execute a transaction multiple times - irrespective if this transaction has already been executed.
Impact: As the owner of MondrianWallet2
can execute a transaction multiple times, it breaks a fundamental invariant of ZKSync: the uniqueness of (sender, nonce) pairs. It can potentially have serious consequences for the functioning of the contract.
Proof of Concept:
A user creates a transaction to mint usdc coins.
The user executes the transaction, with nonce 0.
The user executes the same transaction - again with nonce 0.
And again, and again.
Place the following in ModrianWallet2Test.t.sol
.
Recommended Mitigation: The simplest mitigation is to only allow the bootloader to call executeTransaction
. This can be done by replacing the requireFromBootLoaderOrOwner
modifier with the requireFromBootLoader
modifier.
This also allows for the deletion of the requireFromBootLoaderOrOwner
modifier in its entirety:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.