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: DIFFICULTYbecomesPREVRANDAO. On the PoW chain theDIFFICULTYopcode 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 theDIFFICULTYopcode being useless. To maintain compatibility with smart contracts usingDIFFICULTYfor randomness, it was decided to renameDIFFICULTYtoPREVRANDAOand make it return the previous' blockRANDAOmix. To implement this, Solidity deprecatedblock.difficultyand introducedblock.prevrandaoin version 0.8.18. 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 using it as such. Refer to the EIP for a more accurate and complete description ofPREVRANDAO's security assumptions.Additionally, not all networks implement 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 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.timestampmay 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: PUSH0is a new opcode that pushes '0' to the stack. Even though you will not interact directly withPUSH0when 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 usePUSH0by 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 aPUSH0instruction 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. 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, 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: MCOPYis 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 whenMCOPYis encountered. In contrast toPUSH0,MCOPYis 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/TLOADare 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: 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 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,SELFDESTRUCTremains 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.senderequalstx.originthen 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 exampleEXTCODEHASHof an EOF contract returnskeccak256(0xEF00).
- delegatecallsfrom 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.
 
              