security

The Effects of Ethereum's Upgrades on Smart Contracts

A short account of Ethereum's upgrades and what they mean for how smart contracts behave.

By Meridian Client6
The Effects of Ethereum's Upgrades on Smart Contracts
Photo by Liana S

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: DIFFICULTY becomes PREVRANDAO. On the PoW chain the DIFFICULTY opcode 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 the DIFFICULTY opcode being useless. To maintain compatibility with smart contracts via DIFFICULTY for randomness, it was decided to rename DIFFICULTY to PREVRANDAO and make it return the previous' block RANDAO mix. To put in place this, Solidity deprecated block.difficulty and brought in block.prevrandao in . Note: under the hood the same opcode (0x44) is still called, so block.difficulty in pre 0.8.18 Solidity will produce the same value as block.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 via it as such. Refer to the EIP for a more accurate and complete write-up of PREVRANDAO's security assumptions.

    Further, not all networks build PREVRANDAO equally. For example, Optimism lets the sequencer set the value of PREVRANDAO every block, giving weaker security guarantees. On Arbitrum PREVRANDAO will 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.timestamp may only grow with multiples of 12. This may impact contracts that rely on block.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: PUSH0 Even though you will not interact straight with is a new opcode that pushes '0' to the stack. PUSH0 when 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 contain PUSH0. When deploying the compiled contract it helps to check that the target network supports PUSH0, otherwise execution will revert when a PUSH0 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 takes place 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 a number of smaller features:

  • EIP 4844: Danksharding brought two new opcodes and one new precompile. BLOBBASEFEE and BLOBHASH are exposed in Solidity through blobbasefee() and blobhash() 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 to blockhash()'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: MCOPY is a new opcode rolled out to optimize certain operations, comparable to PUSH0. Solidity will use it by . As such, make sure the target network supports it, else execution will revert when MCOPY is encountered. In contrast to PUSH0, MCOPY is 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/TLOAD are new opcodes giving access to transient storage. These are right now only accessible with inline assembly in Solidity. When with tstore() or tload(), 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: 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. If SELFDESTRUCT is 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 on SELFDESTRUCT, and is material to be aware of when creating a contract that uses SELFDESTRUCT. Regardless, SELFDESTRUCT keeps 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.sender equals tx.origin then 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 example EXTCODEHASH of an EOF contract returns keccak256(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!


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.

Working on something in this space?

NFT Bounty audits Ethereum protocols, smart contracts, and consensus implementations.

Book a scoping talk