helpers.spec.ts 8.21 KB
Newer Older
1
import hre from 'hardhat'
2
import '@nomiclabs/hardhat-ethers'
3
import { Contract, utils } from 'ethers'
4
import { toRpcHexString } from '@eth-optimism/core-utils'
5
import Artifact__L2OutputOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/L2OutputOracle.sol/L2OutputOracle.json'
Mark Tyneway's avatar
Mark Tyneway committed
6
import Artifact__Proxy from '@eth-optimism/contracts-bedrock/forge-artifacts/Proxy.sol/Proxy.json'
7 8 9
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'

import { expect } from './setup'
Mark Tyneway's avatar
Mark Tyneway committed
10 11
import {
  findOutputForIndex,
12
  findFirstUnfinalizedOutputIndex,
Mark Tyneway's avatar
Mark Tyneway committed
13
} from '../../src/fault-mon'
14 15

describe('helpers', () => {
16 17 18 19 20
  const deployConfig = {
    l2OutputOracleSubmissionInterval: 6,
    l2BlockTime: 2,
    l2OutputOracleStartingBlockNumber: 0,
    l2OutputOracleStartingTimestamp: 0,
21 22
    l2OutputOracleProposer: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    l2OutputOracleChallenger: '0x6925B8704Ff96DEe942623d6FB5e946EF5884b63',
23 24 25
    // Can be any non-zero value, 1000 is fine.
    finalizationPeriodSeconds: 1000,
  }
26 27 28 29 30 31

  let signer: SignerWithAddress
  before(async () => {
    ;[signer] = await hre.ethers.getSigners()
  })

32
  let L2OutputOracle: Contract
Mark Tyneway's avatar
Mark Tyneway committed
33
  let Proxy: Contract
34
  beforeEach(async () => {
Mark Tyneway's avatar
Mark Tyneway committed
35 36 37 38 39 40 41 42
    const Factory__Proxy = new hre.ethers.ContractFactory(
      Artifact__Proxy.abi,
      Artifact__Proxy.bytecode.object,
      signer
    )

    Proxy = await Factory__Proxy.deploy(signer.address)

43 44 45 46 47 48
    const Factory__L2OutputOracle = new hre.ethers.ContractFactory(
      Artifact__L2OutputOracle.abi,
      Artifact__L2OutputOracle.bytecode.object,
      signer
    )

Mark Tyneway's avatar
Mark Tyneway committed
49
    const L2OutputOracleImplementation = await Factory__L2OutputOracle.deploy(
50
      deployConfig.l2OutputOracleSubmissionInterval,
51
      deployConfig.l2BlockTime,
52 53 54 55
      deployConfig.l2OutputOracleStartingBlockNumber,
      deployConfig.l2OutputOracleStartingTimestamp,
      deployConfig.l2OutputOracleProposer,
      deployConfig.l2OutputOracleChallenger,
56
      deployConfig.finalizationPeriodSeconds
57
    )
Mark Tyneway's avatar
Mark Tyneway committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71

    await Proxy.upgradeToAndCall(
      L2OutputOracleImplementation.address,
      L2OutputOracleImplementation.interface.encodeFunctionData('initialize', [
        deployConfig.l2OutputOracleStartingBlockNumber,
        deployConfig.l2OutputOracleStartingTimestamp,
      ])
    )

    L2OutputOracle = new hre.ethers.Contract(
      Proxy.address,
      Artifact__L2OutputOracle.abi,
      signer
    )
72 73
  })

74
  describe('findOutputForIndex', () => {
75
    describe('when the output exists once', () => {
76
      beforeEach(async () => {
77 78 79
        const latestBlock = await hre.ethers.provider.getBlock('latest')
        const params = {
          _outputRoot: utils.formatBytes32String('testhash'),
80 81 82
          _l2BlockNumber:
            deployConfig.l2OutputOracleStartingBlockNumber +
            deployConfig.l2OutputOracleSubmissionInterval,
83
          _l1BlockHash: latestBlock.hash,
84
          _l1BlockNumber: latestBlock.number,
85 86 87 88 89 90
        }
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
          params._l2BlockNumber,
          params._l1BlockHash,
          params._l1BlockNumber
91 92 93
        )
      })

94 95
      it('should return the output', async () => {
        const output = await findOutputForIndex(L2OutputOracle, 0)
96

97
        expect(output.l2OutputIndex).to.equal(0)
98 99 100
      })
    })

101
    describe('when the output does not exist', () => {
102 103
      it('should throw an error', async () => {
        await expect(
104 105
          findOutputForIndex(L2OutputOracle, 0)
        ).to.eventually.be.rejectedWith('unable to find output for index')
106 107 108 109 110 111 112
      })
    })
  })

  describe('findFirstUnfinalizedIndex', () => {
    describe('when the chain is more then FPW seconds old', () => {
      beforeEach(async () => {
113 114
        const latestBlock = await hre.ethers.provider.getBlock('latest')
        const params = {
115 116 117
          _l2BlockNumber:
            deployConfig.l2OutputOracleStartingBlockNumber +
            deployConfig.l2OutputOracleSubmissionInterval,
118
          _l1BlockHash: latestBlock.hash,
119
          _l1BlockNumber: latestBlock.number,
120 121
        }
        await L2OutputOracle.proposeL2Output(
122
          utils.formatBytes32String('outputRoot1'),
123 124 125
          params._l2BlockNumber,
          params._l1BlockHash,
          params._l1BlockNumber
126 127 128 129
        )

        // Simulate FPW passing
        await hre.ethers.provider.send('evm_increaseTime', [
130
          toRpcHexString(deployConfig.finalizationPeriodSeconds * 2),
131 132
        ])

133
        await L2OutputOracle.proposeL2Output(
134
          utils.formatBytes32String('outputRoot2'),
135 136 137
          params._l2BlockNumber + deployConfig.l2OutputOracleSubmissionInterval,
          params._l1BlockHash,
          params._l1BlockNumber
138
        )
139
        await L2OutputOracle.proposeL2Output(
140
          utils.formatBytes32String('outputRoot3'),
141
          params._l2BlockNumber +
Mark Tyneway's avatar
Mark Tyneway committed
142
            deployConfig.l2OutputOracleSubmissionInterval * 2,
143 144
          params._l1BlockHash,
          params._l1BlockNumber
145 146 147 148
        )
      })

      it('should find the first batch older than the FPW', async () => {
149
        const first = await findFirstUnfinalizedOutputIndex(
150
          L2OutputOracle,
151
          deployConfig.finalizationPeriodSeconds
152 153 154 155 156 157 158 159
        )

        expect(first).to.equal(1)
      })
    })

    describe('when the chain is less than FPW seconds old', () => {
      beforeEach(async () => {
160 161 162
        const latestBlock = await hre.ethers.provider.getBlock('latest')
        const params = {
          _outputRoot: utils.formatBytes32String('testhash'),
163 164 165
          _l2BlockNumber:
            deployConfig.l2OutputOracleStartingBlockNumber +
            deployConfig.l2OutputOracleSubmissionInterval,
166
          _l1BlockHash: latestBlock.hash,
167
          _l1BlockNumber: latestBlock.number,
168 169 170 171 172 173
        }
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
          params._l2BlockNumber,
          params._l1BlockHash,
          params._l1BlockNumber
174
        )
175 176 177 178 179
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
          params._l2BlockNumber + deployConfig.l2OutputOracleSubmissionInterval,
          params._l1BlockHash,
          params._l1BlockNumber
180
        )
181 182
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
183
          params._l2BlockNumber +
Mark Tyneway's avatar
Mark Tyneway committed
184
            deployConfig.l2OutputOracleSubmissionInterval * 2,
185 186
          params._l1BlockHash,
          params._l1BlockNumber
187 188 189 190
        )
      })

      it('should return zero', async () => {
191
        const first = await findFirstUnfinalizedOutputIndex(
192
          L2OutputOracle,
193
          deployConfig.finalizationPeriodSeconds
194 195 196 197 198 199 200 201
        )

        expect(first).to.equal(0)
      })
    })

    describe('when no batches submitted for the entire FPW', () => {
      beforeEach(async () => {
202 203 204
        const latestBlock = await hre.ethers.provider.getBlock('latest')
        const params = {
          _outputRoot: utils.formatBytes32String('testhash'),
205 206 207
          _l2BlockNumber:
            deployConfig.l2OutputOracleStartingBlockNumber +
            deployConfig.l2OutputOracleSubmissionInterval,
208
          _l1BlockHash: latestBlock.hash,
209
          _l1BlockNumber: latestBlock.number,
210 211 212 213 214 215
        }
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
          params._l2BlockNumber,
          params._l1BlockHash,
          params._l1BlockNumber
216
        )
217 218 219 220 221
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
          params._l2BlockNumber + deployConfig.l2OutputOracleSubmissionInterval,
          params._l1BlockHash,
          params._l1BlockNumber
222
        )
223 224
        await L2OutputOracle.proposeL2Output(
          params._outputRoot,
225
          params._l2BlockNumber +
Mark Tyneway's avatar
Mark Tyneway committed
226
            deployConfig.l2OutputOracleSubmissionInterval * 2,
227 228
          params._l1BlockHash,
          params._l1BlockNumber
229 230 231 232
        )

        // Simulate FPW passing and no new batches
        await hre.ethers.provider.send('evm_increaseTime', [
233
          toRpcHexString(deployConfig.finalizationPeriodSeconds * 2),
234 235 236 237 238 239 240
        ])

        // Mine a block to force timestamp to update
        await hre.ethers.provider.send('hardhat_mine', ['0x1'])
      })

      it('should return undefined', async () => {
241
        const first = await findFirstUnfinalizedOutputIndex(
242
          L2OutputOracle,
243
          deployConfig.finalizationPeriodSeconds
244 245 246 247 248 249 250
        )

        expect(first).to.equal(undefined)
      })
    })
  })
})