The effects of Ethereum's upgrades on smart contracts
Rougly every year, Ethereum undergoes an upgrade. This is a coordinated hard fork that contains new features. Most of these upgrades impact the blockchain layer, focusing on things like scaling and consensus hardening. However, these hard forks also often impact the application layer, this is when new features are exposed to smart contracts. Not all of these new features are backwards compatible and certain assumptions may break. As such, it is crucial to be aware of them when creating immutable smart contracts.
We'll go through the recent Ethereum hard forks, starting from The Merge, and explore their impact on the smart contract layer, with a focus on security. At the end we take a look forward and discuss potential future upgrades.
Paris (The Merge)
Paris, or 'The Merge' was the big fork where Ethereum transitioned from proof-of-work to proof-of-stake. This all happened seamlessly as the transition was carefully designed to have very little impact on smart contracts and their users, however, there were still some small changes:
-
EIP-4399:
DIFFICULTY
becomesPREVRANDAO
. On the PoW chain theDIFFICULTY
opcode was often used as a source of (bad) randomness. However, with the switch to PoS, difficulty no longer has a meaning as no more mining is being done. This results in theDIFFICULTY
opcode being useless. To maintain compatibility with smart contracts usingDIFFICULTY
for randomness, it was decided to renameDIFFICULTY
toPREVRANDAO
and make it return the previous' blockRANDAO
mix. To implement this, Solidity deprecatedblock.difficulty
and introducedblock.prevrandao
in version 0.8.18. Note: under the hood the same opcode (0x44
) is still called, soblock.difficulty
in pre 0.8.18 Solidity will produce the same value asblock.prevrandao
in version 0.8.18 and higher.Just like
DIFFICULTY
,PREVRANDAO
is not a source of true randomness. It can be predicted and biased to certain extents, so great care must be taken when using it as such. Refer to the EIP for a more accurate and complete description ofPREVRANDAO
's security assumptions.Additionally, not all networks implement
PREVRANDAO
equally. For example, Optimism lets the sequencer set the value ofPREVRANDAO
every block, giving weaker security guarantees. On ArbitrumPREVRANDAO
will always return '1', which should obviously not be used for randomness. -
The timestamp of a block is more deterministic. Because mining is probabilistic, the time between blocks on the PoW chain was not constant. However, on average a block was produced every 13 seconds. In contrast, the beacon chain has a slot (i.e. an opportunity to produce a block) exactly every 12 seconds. As such, after the merge
block.timestamp
may only increase with multiples of 12. This may impact contracts that rely onblock.timestamp
. Of course, this is only the case for Ethereum mainnet, other networks have different block times.
Shapella
Shapella's big feature was withdrawals, but it also introduced other goodies such as PUSH0
:
- EIP 3855:
PUSH0
is a new opcode that pushes '0' to the stack. Even though you will not interact directly withPUSH0
when writing or reviewing smart contracts in pure Solidity, it is still important to be aware of its existence and usage. Starting from version 0.8.20 Solidity will usePUSH0
by default, meaning that any compiled contracts may containPUSH0
. When deploying the compiled contract it is important to check that the target network supportsPUSH0
, otherwise execution will revert when aPUSH0
instruction is encountered.
Fortunately, Solidity generates a PUSH0
instruction in the deployment code. This means that if the target network does not support PUSH0
a revert occurs at deployment time, not at a later stage, when funds could potentially be frozen.
If your target network does not support PUSH0
it is recommended to either lower the Solidity version to 0.8.19 or earlier, or drop the target EVM version to Paris or earlier.
Dencun
Dencun implemented danksharding alongside several smaller features:
- EIP 4844: Danksharding brought two new opcodes and one new precompile.
BLOBBASEFEE
andBLOBHASH
are exposed in Solidity throughblobbasefee()
andblobhash()
respectively, starting from version 0.8.24. As you would expect,blobbasefee()
returns the base fee for blobs in this block.blobhash()
returns the versioned hash of the KZG commitment of the blob with the given index in this transaction. If the given index does not exist then zero bytes are returned, similar toblockhash()
's behaviour.
The new 'point evaluation' precompile allows you to verify a point in a blob. In addition, the precompile returns the number of elements in the blob and the BLS modulus. Currently, Solidity offers no way to call this precompile at a high level, as such a low level call to the precompile address (0x0a
) must be used. When doing so, ensure that the precompile has been deployed on the target network, else the call will succeed by default.
-
EIP 5656:
MCOPY
is a new opcode introduced to optimize certain operations, similar toPUSH0
. Solidity will use it by default starting from version 0.8.25. As such, ensure the target network supports it, else execution will revert whenMCOPY
is encountered. In contrast toPUSH0
,MCOPY
is not used in Solidity's deployment code by default. This means that deployment may succeed but other operations may fail, which could lead to frozen funds. -
EIP 1153:
TSTORE
/TLOAD
are new opcodes giving access to transient storage. These are currently only accessible with inline assembly in Solidity. When usingtstore()
ortload()
, ensure the target network supports them. Additionally, it is important to understand the lifetimes in play: transient storage is only cleared at the end of a transaction. When using transient storage for re-entrancy locks, this means that you still need to manually clear them at the end of a call. Otherwise, a user will not be able to have multiple interactions with your contract in a transaction. -
EIP 6780:
SELFDESTRUCT
is modified such that if it is not executed in the same transaction as the contract was created in then the contract is not deleted, but all ETH balance is still transferred. IfSELFDESTRUCT
is executed in the same transaction as the contract was created in, then behaviour remains the same and the contract is deleted in addition to all ETH balance being transferred. This change may break contracts currently relying onSELFDESTRUCT
, and is important to be aware of when creating a contract that usesSELFDESTRUCT
. Regardless,SELFDESTRUCT
remains deprecated and should not be used.
Future
These features are not live yet, but may be implemented soon. As such, it is useful to be aware of them.
-
EIP 3074 or 7702 enhance the UX of EOA's by giving them smart contract capabilities (i.e. the ability to execute code). This breaks the assumption that if
msg.sender
equalstx.origin
then the caller must be an EOA and not a smart contract. As such, this check can no longer be relied upon. -
EOF is a big update that adds structure to EVM bytecode. Most of these changes should be handled by Solidity under the hood. However, some aspects are still visible to developers:
- Gas observability is removed. This means that in EOF contracts,
gas()
is not available and it is not possible to specify a gas limit for a call. - Similarly, code observability is removed. Meaning that it is not possible to copy a contract's code into memory, nor get the size or hash of a contracts code from EOF contracts. Observing the code of an EOF contract from a legacy contract is still possible but with an important caveat: it works as if the EOF contract's code is
EF00
, for exampleEXTCODEHASH
of an EOF contract returnskeccak256(0xEF00)
. delegatecalls
from EOF code to legacy code are not allowed and will revert. The opposite direction, from legacy code to EOF code is allowed.
If your application relies on this functionality you will still be able to use legacy (non-EOF) contracts since these maintain their current behaviour.
Make sure to check out the EOF spec for more details. You'll want to have a snack on hand though, it's a big one!
- Gas observability is removed. This means that in EOF contracts,
Ethereum's upgrades often bring exciting new features and possibilities. Unfortunately, they sometimes change behaviour and break assumptions, often in non-obvious ways. It is important to be aware of these, both as a smart contract developer and as a security engineer to create robust and secure applications.