Differences in YUL IR translation
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.
For a high level overview about the differences in the overall runtime architecture, please refer first to the Differences to Ethereum chapter.
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. Therefor, in EVM termonology, the deploy code equals the runtime code.
In constructor code, the codesize
instruction will return the call data size instead of the actual code blob size.
YUL functions
The below list contains noteworthy differences in the translation of YUL functions.
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-1
will trap the contract immediatly.
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 therefor variable).
- Thus, accessing memory offsets larger than the fixed buffer size will trap the contract at runtime with an
OutOfBound
error. - The compiler might detect and optimize unused memory reads and writes, leading to a different
msize
compared 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.
call
, delegatecall
, staticall
Calls ignore the supplied gas limit (if any) but forward all remaining resources.
The reason is multifold, see Differences to Ethereum (section Cross-Contract Call Gas Limit
).
Contract authors are strongly advised to protect against re-entrancy attacks.
The solc
compiler supplies a gas stipend of 2300
to calls resulting from address payable.{send,transfer}
. Which means: The resource limits solc injects for balance transfer calls will be ignored and not protect against re-entrancy attacks.
revive
uses a heuristic to detect this. If detected, the compiler will disable call re-entrancy to protect against re-entrancy attacks with balance transfers (child context resources still remain uncapped). But this is just a heuristc and not guaranteed behavior.
Note: Using address payable.{send,transfer}
is considered deprecated since a long time ago anyways. There is a revive pre-compile planned for making safe balance transfers.
create
, create2
Deployments on revive work different than on EVM, see also Differences to Ethereum (section PolkaVM instead of 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.
This might fall apart in code creating contracts inside assembly
blocks. We strongly discourage using the create
family opcodes to manually craft deployments in assembly
blocks! Usually, the reason for using assembly
blocks 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
).
gas
, gaslimit
Instead of gas limits, contracts on Polkadot are subject to multi dimensional weight limits (see Differences to Ethereum, section Gas Model
). These opcodes return the corresponding ref_time
weight limit part only.
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.
extcodecopy
, selfdestruct
These are deprecated and produce a compile time error.
sstore
, sload
Due to the different endianness of the underlying VM target architecture, all values are reversed in storage. Contracts compiled by revive itself can't observe this (perforaming a byte-swap before storing and then byte-swapping again after loading would effectively result in a no-op). However, for example tools inspecting contract storage or delgate-callees written in different languages need to be aware of it.