Testing
Understand the differences in testing smart contracts using Foundry ZKsync.
Bytecode Constraints
Issue
On Validium, bytecode must conform to specific size and length constraints to be considered valid. These constraints ensure that the bytecode operates correctly within Validium's modified zkEVM environment. Bytecode that does not meet these criteria will result in compilation or deployment errors.
The bytecode constraints are as follows:
The bytecode length (in bytes) must be divisible by 32 (32-byte words).
The bytecode must have fewer than 2^16 words.
The bytecode length (in words) must be an odd number.
Problematic Code
Consider a test scenario where we attempt to manually set the bytecode at a specific address using Foundry’s vm.etch
cheatcode. In this example, some attempts to etch the bytecode violate the size and word alignment rules.
In this code:
The first
vm.etch
call uses a 1-byte word, which is invalid because Validium requires the bytecode length to be divisible by 32 bytes.The second
vm.etch
call uses a bytecode of 64 bytes (even number of words), which is also invalid because Validium requires an odd number of words.The third
vm.etch
call uses a valid bytecode length of 32 bytes (1 word), which is both divisible by 32 bytes and contains an odd number of words.
Error
The first two cases will produce errors due to violating the bytecode size and word constraints:
These errors indicate that the bytecode fails to meet Validium’s requirements for bytecode validity.
Solution
To resolve these errors, ensure that the bytecode conforms to the following rules:
The length of the bytecode must always be divisible by 32 bytes.
The total word count (in 32-byte words) must be an odd number.
The bytecode should be less than 2^16 words in length.
Fixed Code Example
Here’s how to correctly etch the bytecode in Validium, following the bytecode constraints:
In this fixed example:
The bytecode length is exactly 32 bytes (one 32-byte word), which satisfies Validium's requirement for bytecode size divisibility by 32.
The total word count is odd (1 word), meeting the requirement for an odd number of words.
Cheatcode Limitations
Issue
In Validium's zkEVM, cheatcodes are only supported at the root level of an executing test, meaning they must be called outside of any CREATE
or CALL
operations that are dispatched to the zkEVM. Cheatcodes used within contract constructors or function calls that dispatch transactions will not work and can lead to undefined behavior.
Valid Cheatcode Usage
Cheatcodes can be used normally in tests as long as they are outside of any contract creation (CREATE
) or external function calls (CALL
). Here's an example demonstrating valid cheatcode usage:
In these cases, the vm.roll
cheatcode is valid because it is called outside of any contract creation or function calls that interact with zkEVM.
Cheatcode Usage in Libraries
Since libraries do not result in a CREATE
or CALL
, you can use cheatcodes within library functions without issues. Here's an example of how libraries can use cheatcodes:
Libraries can call cheatcodes since they do not trigger a contract creation or external call, making them valid for cheatcode usage in Validium.
Problematic Code
Cheatcodes used within contract constructors or functions that dispatch transactions to the zkEVM will result in undefined behavior or fail to execute. Here's an example demonstrating invalid cheatcode usage:
In this example, vm.roll
is used inside the constructor and a function of MyContract
, which will not work on Validium because the cheatcode is executed within a CREATE
or CALL
operation.
Error
Although the error messages may not be explicit, calling cheatcodes inside a contract's constructor or function will either lead to undefined behavior or failure during the test execution.
Solution
To ensure cheatcodes work as expected on Validium, always call them outside of contract constructors or external function calls. Use libraries or test-level cheatcodes for reliable test execution.
Fixed Code Example
Here’s an example of valid cheatcode usage in a test:
This example ensures that the cheatcodes are called only at the test level, outside any contract creation or external function calls.
Forking
When using forking cheatcodes such as vm.selectFork
or vm.createSelectFork
in tests, the execution context automatically switches based on the network being forked.
Forking to a Validium Network: If the RPC endpoint supports Validium (verified by checking the presence of the
zks_L1ChainId
method), the test execution switches to the Validium context. This means the test will follow Validium-specific behaviors and limitations.Forking to a non-Validium network: If the selected fork is not a Validium endpoint, the test execution remains in the standard EVM context.
Cheatcode Override
To manually control the execution context, a custom cheatcode vm.zkVm
is provided:
Enabling Validium mode: Use
vm.zkVm(true)
to switch the test execution to Validium mode.Switching back to EVM mode: Use
vm.zkVm(false)
to revert back to the standard EVM mode during test execution.
Note
Using the --zksync
flag when running tests is equivalent to placing vm.zkVm(true)
as the first statement in the test, automatically setting the execution to Validium mode.
Origin Address
Issue
In Validium's zkEVM, calls to tx.origin
are not supported, unlike in Ethereum where tx.origin
can be used to retrieve the original external account that initiated the transaction. As a result, any attempts to mock or interact with tx.origin
in Validium will fail.
Problematic Code
In the following example, the tx.origin
address is used in a mocked call. While this works in Ethereum, it will not work in Validium because tx.origin
is unsupported.
In this code, tx.origin
is used to obtain the address of the original transaction sender, but on Validium, any interaction involving tx.origin
will fail.
Error
Attempting to use tx.origin
on Validium will result in failed transactions or mocked calls. However, Validium may not provide specific error messages related to the failure, making it difficult to trace the root cause.
Solution
You should use msg.sender
instead of using tx.origin
to track the address of the current external caller, which is supported on Validium.
Fixed Code Example
In this fixed example, msg.sender
is used instead of tx.origin
, which ensures compatibility with Validium.
Reserved Address Range
Issue
On Validium's zkEVM, addresses in the range [0..2^16-1]
are reserved for kernel space, meaning they are not available for contract deployment or other user-level interactions. Attempting to use these addresses, either directly or through mocking, can lead to undefined behavior in your tests.
Problematic Code
In this test example, the address 0
is used in a mocked call. Since this address is part of the reserved range, using it will result in undefined behavior and test failures.
This example attempts to mock a call to address(0)
, which is part of the reserved kernel space and therefore invalid on Validium.
Error
Using addresses within the reserved range may lead to undefined behavior and possible test failures without specific error messages. It's crucial to avoid these addresses entirely to ensure test reliability.
Solution
Ensure that addresses used for testing and mocking are outside of the reserved kernel space range. Start from address 65536
onwards for any user-level interaction.
In fuzz testing, you can exclude the reserved address range either by setting assumptions or using specific fuzz configuration.
Here’s a corrected test example:
Fixed Code Example
In this corrected example, we use address(65536)
, which is a valid user-space address in Validium.
Additional Configuration
During fuzz testing, it's important to avoid generating addresses within the reserved range. This can be done in two ways:
Using
vm.assume
: You can assert that the address generated in fuzz testing is greater than or equal to65536
:Fuzz Configuration: Alternatively, you can set the
no_zksync_reserved_addresses
option in your fuzz configuration to automatically exclude addresses in the reserved range.
These approaches ensure that fuzz testing avoids the reserved kernel space, preventing test failures and ensuring compliance with Validium's address rules.
Last updated