Learn how Abstract differs from Ethereum’s EVM opcodes.
CREATE
& CREATE2
create
& create2
functions operate correctly,
the compiler must be aware of the bytecode of the deployed contract in advance.
type(T).creationCode
.type(T).runtimeCode
will always produce a compile-time error.create
and create2
will be different on Abstract
than Ethereum as they use different bytecode. This means the same bytecode deployed on Ethereum
will have a different contract address on Abstract.
View address derivation formula
CALL
, STATICCALL
, DELEGATECALL
out
and outsize
arguments for
call(g, a, v, in, insize, out, outsize)
. In EVM, if outsize != 0
, the allocated memory will grow to out + outsize
(rounded up to the words) regardless of the returndatasize
. On Abstract, returndatacopy
, similar to calldatacopy
,
is implemented as a cycle iterating over return data with a few additional checks and triggering a panic if
out + outsize > returndatasize
to simulate the same behavior as in EVM.
Thus, unlike EVM where memory growth occurs before the call itself, on Abstract, the necessary copying of return data
happens only after the call has ended, leading to a difference in msize()
and sometimes Abstract not panicking where
EVM would panic due to the difference in memory growth.
MsgValueSimulator
. The simulator receives the callee address and Ether amount, performs all necessary balance
changes, and then calls the callee.
MSTORE
, MLOAD
mstore(100, 0)
the msize
on zkEVM will be 132
, but on the EVM it will be 160
. Note, that also unlike EVM which
has quadratic growth for memory payments, on zkEVM the fees are charged linearly at a rate of 1
erg per byte.
The other thing is that our compiler can sometimes optimize unused memory reads/writes. This can lead to different msize
compared to Ethereum since fewer bytes have been allocated, leading to cases where EVM panics, but zkEVM will not due to
the difference in memory growth.
CALLDATALOAD
, CALLDATACOPY
offset
for calldataload(offset)
is greater than 2^32-33
then execution will panic.
Internally on zkEVM, calldatacopy(to, offset, len)
there is just a loop with the calldataload
and mstore
on each iteration.
That means that the code will panic if 2^32-32 + offset % 32 < offset + len
.
RETURN
, STOP
RETURN
or STOP
in an
assembly block in the constructor on Abstract,
it will leave the immutable variables uninitialized.
TIMESTAMP
, NUMBER
block.timestamp
and block.number
,
check out the blocks on ZKsync Documentation.
COINBASE
Bootloader
contract, which is 0x8001
on Abstract.
DIFFICULTY
, PREVRANDAO
2500000000000000
on Abstract.
BASEFEE
SELFDESTRUCT
CALLCODE
DELEGATECALL
.
Always produces a compile-time error with the zkEVM compiler.
PC
>=0.7.0
, but accessible in Solidity 0.6
.
Always produces a compile-time error with the zkEVM compiler.
CODESIZE
Deploy code | Runtime code |
---|---|
Size of the constructor arguments | Contract size |
datasize
to distinguish the contract code and constructor arguments, so we
substitute datasize
with 0 and codesize
with calldatasize
in Abstract deployment code. This way when Yul calculates the
calldata size as sub(codesize, datasize)
, the result is the size of the constructor arguments.
CODECOPY
Deploy code | Runtime code (old EVM codegen) | Runtime code (new Yul codegen) |
---|---|---|
Copies the constructor arguments | Zeroes memory out | Compile-time error |
EXTCODECOPY
CODESIZE
and EXTCODESIZE
.
EXTCODECOPY
always produces a compile-time error with the zkEVM compiler.
DATASIZE
, DATAOFFSET
, DATACOPY
ContractDeployer
.
On the compiler front-end the code of the deployed contract is substituted with its hash. The hash is returned by the dataoffset
Yul instruction or the PUSH [$]
EVM legacy assembly instruction. The hash is then passed to the datacopy
Yul instruction or
the CODECOPY
EVM legacy instruction, which writes the hash to the correct position of the calldata of the call to ContractDeployer
.
The deployer calldata consists of several elements:
Element | Offset | Size |
---|---|---|
Deployer method signature | 0 | 4 |
Salt | 4 | 32 |
Contract hash | 36 | 32 |
Constructor calldata offset | 68 | 32 |
Constructor calldata length | 100 | 32 |
Constructor calldata | 132 | N |
datasize
and PUSH [$]
return the header size (132), and the space for constructor arguments is allocated by solc on top of it.
Finally, the CREATE
or CREATE2
instructions pass 132+N bytes to the ContractDeployer
contract, which makes all
the necessary changes to the state and returns the contract address or zero if there has been an error.
If some Ether is passed, the call to the ContractDeployer
also goes through the MsgValueSimulator
just like ordinary calls.
We do not recommend using CREATE
for anything other than creating contracts with the new
operator. However, a lot of contracts create contracts
in assembly blocks instead, so authors must ensure that the behavior is compatible with the logic described above.
Yul example
EVM legacy assembly example
SETIMMUTABLE
, LOADIMMUTABLE
zksolc
for each string literal identifier allocated by solc
.ImmutableSimulator
, where it is stored in a mapping with
the contract address as the key.ImmutableSimulator
to fetch a value using
the address and value index. In the deploy code, immutable values are read from the auxiliary heap, where they are still available.Yul example
EVM legacy assembly example