hardfork.spec.ts 6.73 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
import { Contract, BigNumber } from 'ethers'
import { ethers } from 'hardhat'

import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'

export const traceToGasByOpcode = (structLogs, opcode) => {
  let gas = 0
  const opcodes = []
  for (const log of structLogs) {
    if (log.op === opcode) {
      opcodes.push(opcode)
      gas += log.gasCost
    }
  }
  return gas
}

describe('Hard forks', () => {
  let env: OptimismEnv
  let SimpleStorage: Contract
  let SelfDestruction: Contract
  let Precompiles: Contract

  before(async () => {
    env = await OptimismEnv.new()
    const Factory__SimpleStorage = await ethers.getContractFactory(
      'SimpleStorage',
      env.l2Wallet
    )
    SimpleStorage = await Factory__SimpleStorage.deploy()

    const Factory__SelfDestruction = await ethers.getContractFactory(
      'SelfDestruction',
      env.l2Wallet
    )
    SelfDestruction = await Factory__SelfDestruction.deploy()

    const Factory__Precompiles = await ethers.getContractFactory(
      'Precompiles',
      env.l2Wallet
    )
    Precompiles = await Factory__Precompiles.deploy()
  })

  describe('Berlin', () => {
    // https://eips.ethereum.org/EIPS/eip-2929
    describe('EIP-2929', () => {
      it('should update the gas schedule', async () => {
        // Get the tip height
        const tip = await env.l2Provider.getBlock('latest')

        // send a transaction to be able to trace
        const tx = await SimpleStorage.setValueNotXDomain(
          `0x${'77'.repeat(32)}`
        )
        await tx.wait()

        // Collect the traces
        const berlinTrace = await env.l2Provider.send(
          'debug_traceTransaction',
          [tx.hash]
        )
        const preBerlinTrace = await env.l2Provider.send(
          'debug_traceTransaction',
          [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
        )
        expect(berlinTrace.gas).to.not.eq(preBerlinTrace.gas)

        const berlinSstoreCosts = traceToGasByOpcode(
          berlinTrace.structLogs,
          'SSTORE'
        )
        const preBerlinSstoreCosts = traceToGasByOpcode(
          preBerlinTrace.structLogs,
          'SSTORE'
        )
        expect(berlinSstoreCosts).to.not.eq(preBerlinSstoreCosts)
      })
    })

    // https://eips.ethereum.org/EIPS/eip-2565
    describe('EIP-2565', async () => {
      it('should become cheaper', async () => {
        const tip = await env.l2Provider.getBlock('latest')

        const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 })
        await tx.wait()

        const berlinTrace = await env.l2Provider.send(
          'debug_traceTransaction',
          [tx.hash]
        )
        const preBerlinTrace = await env.l2Provider.send(
          'debug_traceTransaction',
          [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
        )
        expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas)
      })
    })
  })

  // Optimism includes EIP-3529 as part of its Berlin hardfork. It is part
  // of the London hardfork on L1. Since it is coupled to the Berlin
  // hardfork, some of its functionality cannot be directly tests via
  // integration tests since we can currently only turn on all of the Berlin
  // EIPs or none of the Berlin EIPs
  describe('Berlin Additional (L1 London)', () => {
    // https://eips.ethereum.org/EIPS/eip-3529
    describe('EIP-3529', async () => {
      const bytes32Zero = '0x' + '00'.repeat(32)
      const bytes32NonZero = '0x' + 'ff'.repeat(32)

      it('should lower the refund for storage clear', async () => {
        const tip = await env.l2Provider.getBlock('latest')

        const value = await SelfDestruction.callStatic.data()
        // It should be non zero
        expect(BigNumber.from(value).toNumber()).to.not.eq(0)

        {
          // Set the value to another non zero value
          // Going from non zero to non zero
          const tx = await SelfDestruction.setData(bytes32NonZero, {
            gasLimit: 5_000_000,
          })
          await tx.wait()

          const berlinTrace = await env.l2Provider.send(
            'debug_traceTransaction',
            [tx.hash]
          )
          const preBerlinTrace = await env.l2Provider.send(
            'debug_traceTransaction',
            [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
          )
          // Updating a non zero value to another non zero value should not change
          expect(berlinTrace.gas).to.deep.eq(preBerlinTrace.gas)
        }

        {
          // Set the value to the zero value
          // Going from non zero to zero
          const tx = await SelfDestruction.setData(bytes32Zero, {
            gasLimit: 5_000_000,
          })
          await tx.wait()

          const berlinTrace = await env.l2Provider.send(
            'debug_traceTransaction',
            [tx.hash]
          )
          const preBerlinTrace = await env.l2Provider.send(
            'debug_traceTransaction',
            [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
          )

          // Updating to a zero value from a non zero value should becomes
          // more expensive due to this change being coupled with EIP-2929
          expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
        }

        {
          // Set the value to a non zero value
          // Going from zero to non zero
          const tx = await SelfDestruction.setData(bytes32NonZero, {
            gasLimit: 5_000_000,
          })
          await tx.wait()

          const berlinTrace = await env.l2Provider.send(
            'debug_traceTransaction',
            [tx.hash]
          )
          const preBerlinTrace = await env.l2Provider.send(
            'debug_traceTransaction',
            [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
          )

          // Updating to a zero value from a non zero value should becomes
          // more expensive due to this change being coupled with EIP-2929
          expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
        }
      })

      it('should remove the refund for selfdestruct', async () => {
        const tip = await env.l2Provider.getBlock('latest')

        // Send transaction with a large gas limit
        const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 })
        await tx.wait()

        const berlinTrace = await env.l2Provider.send(
          'debug_traceTransaction',
          [tx.hash]
        )
        const preBerlinTrace = await env.l2Provider.send(
          'debug_traceTransaction',
          [tx.hash, { overrides: { berlinBlock: tip.number * 2 } }]
        )

        // The berlin execution should use more gas than the pre Berlin
        // execution because there is no longer a selfdestruct gas
        // refund
        expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
      })
    })
  })
})