Differences to EVM
This section highlights some potentially observable differences in the YUL EVM dialect translation compared to Ethereum Solidity.
Solidity developers deploying dApps to pallet-revive ought to read and understand this section well.
Deploy code vs. runtime code
Our contract runtime does not differentiate between runtime code and deploy (constructor) code. Instead, both are emitted into a single PVM contract code blob and live on-chain. Therefore, in EVM terminology, the deploy code equals the runtime code.
Tip
In constructor code, the
codesizeinstruction will return the call data size instead of the actual code blob size.
Solidity
We are aware of the following differences in the translation of Solidity code.
address.creationCode
This returns the bytecode keccak256 hash instead.
YUL functions
The below list contains noteworthy differences in the translation of YUL functions.
Note
Many functions receive memory buffer offset pointer or size arguments. Since the PVM pointer size is 32 bit, supplying memory offset or buffer size values above
2^32-1will trap the contract immediately.
The solc compiler ought to always emit valid memory references, so Solidity dApp authors don’t need to worry about this unless they deal with low level assembly code.
mload, mstore, msize, mcopy (memory related functions)
In general, revive preserves the memory layout, meaning low level memory operations are supported. However, a few caveats apply:
- The EVM linear heap memory is emulated using a fixed byte buffer of 64kb. This implies that the maximum memory a contract can use is limited to 64kbit (on Ethereum, contract memory is capped by gas and therefore varies).
- Thus, accessing memory offsets larger than the fixed buffer size will trap the contract at runtime with an
OutOfBounderror. - The compiler might detect and optimize unused memory reads and writes, leading to a different
msizecompared to what the EVM would see.
calldataload, calldatacopy
In the constructor code, the offset is ignored and this always returns 0.
codecopy
Only supported in constructor code.
invalid
Traps the contract but does not consume the remaining gas.
create, create2
Deployments on revive work different than on EVM. In a nutshell: Instead of supplying the deploy code concatenated with the constructor arguments (the EVM deploy model), the revive runtime expects two pointers:
- A buffer containing the code hash to deploy.
- The constructor arguments buffer.
To make contract instantiation using the new keyword in Solidity work seamlessly,
revive translates the dataoffset and datasize instructions so that they assume the contract hash instead of the contract code.
The hash is always of constant size.
Thus, revive is able to supply the expected code hash and constructor arguments pointer to the runtime.
Warning
This might fall apart in code creating contracts inside
assemblyblocks. We strongly discourage using thecreatefamily opcodes to manually craft deployments inassemblyblocks! Usually, the reason for usingassemblyblocks is to save gas, which is futile on revive anyways due to lower transaction costs.
dataoffset
Returns the contract hash.
datasize
Returns the contract hash size (constant value of 32).
prevrandao, difficulty
Translates to a constant value of 2500000000000000.
pc, extcodecopy
Only valid to use in EVM (they also have no use case in PVM) and produce a compile time error.
blobhash, blobbasefee
Related to the Ethereum rollup model and produce a compile time error. Polkadot offers a superior rollup model, removing the use case for blob data related opcodes.
Difference regarding the solc via-ir mode
There are two different compilation pipelines available in solc and there are small differences between them.
Since resolc processes the YUL IR, always assume the solc IR based codegen behavior for contracts compiled with the revive compiler.