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 NFT Bounty layer, focusing on things like scaling and consensus hardening. Still, 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 go through plausible 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, even so, there were still some small changes:
-
EIP-4399:
DIFFICULTYbecomesPREVRANDAO. On the PoW chain theDIFFICULTYopcode was often applied as a source of (bad) randomness. Still, with the switch to PoS, difficulty no longer has a meaning as no more mining is being done. This results in theDIFFICULTYopcode being useless. To maintain compatibility with smart contracts viaDIFFICULTYfor randomness, it was decided to renameDIFFICULTYtoPREVRANDAOand make it return the previous' blockRANDAOmix. To put in place this, Solidity deprecatedblock.difficultyand brought inblock.prevrandaoin . Note: under the hood the same opcode (0x44) is still called, soblock.difficultyin pre 0.8.18 Solidity will produce the same value asblock.prevrandaoin version 0.8.18 and higher.Just like
DIFFICULTY,PREVRANDAOis not a source of true randomness. It can be predicted and biased to certain extents, so great care must be taken when via it as such. Refer to the EIP for a more accurate and complete write-up ofPREVRANDAO's security assumptions.Further, not all networks build
PREVRANDAOequally. For example, Optimism lets the sequencer set the value ofPREVRANDAOevery block, giving weaker security guarantees. On ArbitrumPREVRANDAOwill always return '1', which should obviously not be applied 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. Still, 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.timestampmay only grow 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 rolled out other goodies such as PUSH0:
-
EIP 3855:
PUSH0Even though you will not interact straight with is a new opcode that pushes '0' to the stack.PUSH0when writing or reviewing smart contracts in pure Solidity, it is still material to be aware of its existence and usage. Starting from , meaning that any compiled contracts may containPUSH0. When deploying the compiled contract it helps to check that the target network supportsPUSH0, otherwise execution will revert when aPUSH0instruction is encountered.Fortunately, Solidity generates a
PUSH0instruction in the deployment code. This means that if the target network does not supportPUSH0a revert takes place at deployment time, not at a later stage, when funds could potentially be frozen.If your target network does not support
PUSH0it 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 a number of smaller features:
- EIP 4844: Danksharding brought two new opcodes and one new precompile.
BLOBBASEFEEandBLOBHASHare 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, alike toblockhash()'s behaviour.
In addition, the precompile returns the number of elements in the blob and the BLS modulus. The new 'point evaluation' precompile allows you to verify a point in a blob. At present, 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 employed. When doing so, make sure that the precompile has been deployed on the target network, else the call will succeed by default.
-
EIP 5656:
MCOPYis a new opcode rolled out to optimize certain operations, comparable toPUSH0. Solidity will use it by . As such, make sure the target network supports it, else execution will revert whenMCOPYis encountered. In contrast toPUSH0,MCOPYis not employed 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/TLOADare new opcodes giving access to transient storage. These are right now only accessible with inline assembly in Solidity. When withtstore()ortload(), keep the target network supports them. Further, it helps to read the lifetimes in play: transient storage is only cleared at the end of a transaction. When via 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:
SELFDESTRUCTis 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. IfSELFDESTRUCTis executed in the same transaction as the contract was created in, then behaviour keeps the same and the contract is deleted besides all ETH balance being transferred. This change may break contracts right now relying onSELFDESTRUCT, and is material to be aware of when creating a contract that usesSELFDESTRUCT. Regardless,SELFDESTRUCTkeeps deprecated and should not be applied.
Future
As such, it is useful to be aware of them. These features are not live yet, but may be implemented soon.
-
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.senderequalstx.originthen the caller must be an EOA and not a smart contract. As such, this check can no longer be relied upon. -
is a big blog that adds structure to EVM bytecode. Most of these changes should be handled by Solidity under the hood. Still, some aspects are still visible to developers:
- 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 consequential caveat: it works as if the EOF contract's code is
EF00, for exampleEXTCODEHASHof an EOF contract returnskeccak256(0xEF00).
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 for more details. You'll want to have a snack on hand though, it's a big one!
- 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 consequential caveat: it works as if the EOF contract's code is
Ethereum's upgrades often bring exciting new features and possibilities. Unfortunately, they sometimes change behaviour and break assumptions, often in non-obvious ways. It helps to be aware of these, both as a smart contract developer and as a security engineer to create hardy and secure applications.