test-runner.ts 14.6 KB
Newer Older
Kelvin Fichter's avatar
Kelvin Fichter committed
1 2 3 4 5
import { expect } from '../../setup'

/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { Contract, BigNumber, ContractFactory } from 'ethers'
ben-chain's avatar
ben-chain committed
6
import { cloneDeep, merge } from 'lodash'
Kevin Ho's avatar
Kevin Ho committed
7
import { smoddit, smockit, ModifiableContract } from '@eth-optimism/smock'
Kelvin Fichter's avatar
Kelvin Fichter committed
8 9 10 11

/* Internal Imports */
import {
  TestDefinition,
12 13
  ParsedTestStep,
  TestParameter,
Kelvin Fichter's avatar
Kelvin Fichter committed
14
  TestStep,
15 16 17
  TestStep_CALL,
  TestStep_Run,
  isRevertFlagError,
Kelvin Fichter's avatar
Kelvin Fichter committed
18 19 20 21 22
  isTestStep_SSTORE,
  isTestStep_SLOAD,
  isTestStep_CALL,
  isTestStep_CREATE,
  isTestStep_CREATE2,
Kevin Ho's avatar
Kevin Ho committed
23
  isTestStep_CREATEEOA,
Kelvin Fichter's avatar
Kelvin Fichter committed
24 25
  isTestStep_Context,
  isTestStep_evm,
26
  isTestStep_Run,
Kelvin Fichter's avatar
Kelvin Fichter committed
27 28 29 30
  isTestStep_EXTCODESIZE,
  isTestStep_EXTCODEHASH,
  isTestStep_EXTCODECOPY,
  isTestStep_REVERT,
Kevin Ho's avatar
Kevin Ho committed
31
  isTestStep_SETNONCE,
Kelvin Fichter's avatar
Kelvin Fichter committed
32
} from './test.types'
33
import { encodeRevertData, REVERT_FLAGS } from '../codec'
ben-chain's avatar
ben-chain committed
34 35 36 37
import {
  OVM_TX_GAS_LIMIT,
  RUN_OVM_TEST_GAS,
  NON_NULL_BYTES32,
38
  NULL_BYTES32,
ben-chain's avatar
ben-chain committed
39
} from '../constants'
40
import { getStorageXOR } from '../'
Kelvin Fichter's avatar
Kelvin Fichter committed
41 42 43 44 45

export class ExecutionManagerTestRunner {
  private snapshot: string
  private contracts: {
    OVM_SafetyChecker: Contract
46 47
    OVM_StateManager: ModifiableContract
    OVM_ExecutionManager: ModifiableContract
Kelvin Fichter's avatar
Kelvin Fichter committed
48 49
    Helper_TestRunner: Contract
    Factory__Helper_TestRunner_CREATE: ContractFactory
50
    OVM_DeployerWhitelist: Contract
Kelvin Fichter's avatar
Kelvin Fichter committed
51 52 53 54 55 56
  } = {
    OVM_SafetyChecker: undefined,
    OVM_StateManager: undefined,
    OVM_ExecutionManager: undefined,
    Helper_TestRunner: undefined,
    Factory__Helper_TestRunner_CREATE: undefined,
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
    OVM_DeployerWhitelist: undefined,
  }

  // Default pre-state with contract deployer whitelist NOT initialized.
  private defaultPreState = {
    StateManager: {
      owner: '$OVM_EXECUTION_MANAGER',
      accounts: {
        ['0x4200000000000000000000000000000000000002']: {
          codeHash: NON_NULL_BYTES32,
          ethAddress: '$OVM_DEPLOYER_WHITELIST',
        },
      },
      contractStorage: {
        ['0x4200000000000000000000000000000000000002']: {
          '0x0000000000000000000000000000000000000000000000000000000000000010': getStorageXOR(
            NULL_BYTES32
          ),
        },
      },
      verifiedContractStorage: {
        ['0x4200000000000000000000000000000000000002']: {
          '0x0000000000000000000000000000000000000000000000000000000000000010': true,
        },
      },
    },
Kelvin Fichter's avatar
Kelvin Fichter committed
83 84 85
  }

  public run(test: TestDefinition) {
86 87 88 89 90
    ;(test.preState = merge(
      cloneDeep(this.defaultPreState),
      cloneDeep(test.preState)
    )),
      (test.postState = test.postState || {})
Kelvin Fichter's avatar
Kelvin Fichter committed
91 92 93 94 95

    describe(`OVM_ExecutionManager Test: ${test.name}`, () => {
      test.subTests?.map((subTest) => {
        this.run({
          ...subTest,
ben-chain's avatar
ben-chain committed
96 97 98 99 100 101 102 103
          preState: merge(
            cloneDeep(test.preState),
            cloneDeep(subTest.preState)
          ),
          postState: merge(
            cloneDeep(test.postState),
            cloneDeep(subTest.postState)
          ),
Kelvin Fichter's avatar
Kelvin Fichter committed
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
        })
      })

      test.parameters?.map((parameter) => {
        beforeEach(async () => {
          await this.initContracts()
        })

        let replacedTest: TestDefinition
        let replacedParameter: TestParameter
        beforeEach(async () => {
          replacedTest = this.setPlaceholderStrings(test)
          replacedParameter = this.setPlaceholderStrings(parameter)
        })

119 120
        beforeEach(() => {
          this.contracts.OVM_StateManager.smodify.set({
Kelvin Fichter's avatar
Kelvin Fichter committed
121 122 123 124 125 126 127 128 129 130
            accounts: {
              [this.contracts.Helper_TestRunner.address]: {
                nonce: 0,
                codeHash: NON_NULL_BYTES32,
                ethAddress: this.contracts.Helper_TestRunner.address,
              },
            },
          })
        })

131 132
        beforeEach(() => {
          this.contracts.OVM_ExecutionManager.smodify.set(
Kelvin Fichter's avatar
Kelvin Fichter committed
133 134
            replacedTest.preState.ExecutionManager
          )
135
          this.contracts.OVM_StateManager.smodify.set(
Kelvin Fichter's avatar
Kelvin Fichter committed
136 137 138 139 140
            replacedTest.preState.StateManager
          )
        })

        afterEach(async () => {
141 142 143 144 145 146 147 148 149 150 151
          expect(
            await this.contracts.OVM_ExecutionManager.smodify.check(
              replacedTest.postState.ExecutionManager
            )
          ).to.equal(true)

          expect(
            await this.contracts.OVM_StateManager.smodify.check(
              replacedTest.postState.StateManager
            )
          ).to.equal(true)
Kelvin Fichter's avatar
Kelvin Fichter committed
152 153
        })

Kelvin Fichter's avatar
Kelvin Fichter committed
154 155 156
        const itfn = parameter.focus ? it.only : it
        itfn(`should execute: ${parameter.name}`, async () => {
          try {
Kelvin Fichter's avatar
Kelvin Fichter committed
157 158 159
            for (const step of replacedParameter.steps) {
              await this.runTestStep(step)
            }
Kelvin Fichter's avatar
Kelvin Fichter committed
160 161
          } catch (err) {
            if (parameter.expectInvalidStateAccess) {
162 163 164
              expect(err.toString()).to.contain(
                'VM Exception while processing transaction: revert'
              )
Kelvin Fichter's avatar
Kelvin Fichter committed
165 166
            } else {
              throw err
Kelvin Fichter's avatar
Kelvin Fichter committed
167
            }
Kelvin Fichter's avatar
Kelvin Fichter committed
168 169
          }
        })
Kelvin Fichter's avatar
Kelvin Fichter committed
170 171 172 173 174 175 176
      })
    })
  }

  private async initContracts() {
    if (this.snapshot) {
      await ethers.provider.send('evm_revert', [this.snapshot])
177
      this.snapshot = await ethers.provider.send('evm_snapshot', [])
Kelvin Fichter's avatar
Kelvin Fichter committed
178 179 180
      return
    }

181 182 183 184
    const AddressManager = await (
      await ethers.getContractFactory('Lib_AddressManager')
    ).deploy()

Kevin Ho's avatar
Kevin Ho committed
185
    const SafetyChecker = await (
Kelvin Fichter's avatar
Kelvin Fichter committed
186 187
      await ethers.getContractFactory('OVM_SafetyChecker')
    ).deploy()
188

Kevin Ho's avatar
Kevin Ho committed
189 190 191 192 193
    const MockSafetyChecker = smockit(SafetyChecker)
    MockSafetyChecker.smocked.isBytecodeSafe.will.return.with(true)

    this.contracts.OVM_SafetyChecker = MockSafetyChecker

194 195 196 197 198
    await AddressManager.setAddress(
      'OVM_SafetyChecker',
      this.contracts.OVM_SafetyChecker.address
    )

199 200 201 202 203 204
    const DeployerWhitelist = await (
      await ethers.getContractFactory('OVM_DeployerWhitelist')
    ).deploy()

    this.contracts.OVM_DeployerWhitelist = DeployerWhitelist

Kelvin Fichter's avatar
Kelvin Fichter committed
205
    this.contracts.OVM_ExecutionManager = await (
206
      await smoddit('OVM_ExecutionManager')
Kelvin Fichter's avatar
Kelvin Fichter committed
207 208 209 210 211 212 213 214 215
    ).deploy(
      AddressManager.address,
      {
        minTransactionGasLimit: 0,
        maxTransactionGasLimit: 1_000_000_000,
        maxGasPerQueuePerEpoch: 1_000_000_000_000,
        secondsPerEpoch: 600,
      },
      {
216
        ovmCHAINID: 420,
217
      }
Kelvin Fichter's avatar
Kelvin Fichter committed
218
    )
219

Kelvin Fichter's avatar
Kelvin Fichter committed
220
    this.contracts.OVM_StateManager = await (
221
      await smoddit('OVM_StateManager')
Kevin Ho's avatar
Kevin Ho committed
222 223 224 225
    ).deploy(await this.contracts.OVM_ExecutionManager.signer.getAddress())
    await this.contracts.OVM_StateManager.setExecutionManager(
      this.contracts.OVM_ExecutionManager.address
    )
226

Kelvin Fichter's avatar
Kelvin Fichter committed
227 228 229
    this.contracts.Helper_TestRunner = await (
      await ethers.getContractFactory('Helper_TestRunner')
    ).deploy()
230

Kelvin Fichter's avatar
Kelvin Fichter committed
231 232 233 234 235 236 237
    this.contracts.Factory__Helper_TestRunner_CREATE = await ethers.getContractFactory(
      'Helper_TestRunner_CREATE'
    )

    this.snapshot = await ethers.provider.send('evm_snapshot', [])
  }

238 239 240 241
  public static getDummyAddress(placeholder: string): string {
    return '0x' + (placeholder.split('$DUMMY_OVM_ADDRESS_')[1] + '0').repeat(20)
  }

Kelvin Fichter's avatar
Kelvin Fichter committed
242 243 244 245 246 247 248 249 250 251
  private setPlaceholderStrings(obj: any) {
    const getReplacementString = (kv: string): string => {
      if (kv === '$OVM_EXECUTION_MANAGER') {
        return this.contracts.OVM_ExecutionManager.address
      } else if (kv === '$OVM_STATE_MANAGER') {
        return this.contracts.OVM_StateManager.address
      } else if (kv === '$OVM_SAFETY_CHECKER') {
        return this.contracts.OVM_SafetyChecker.address
      } else if (kv === '$OVM_CALL_HELPER') {
        return this.contracts.Helper_TestRunner.address
252 253
      } else if (kv == '$OVM_DEPLOYER_WHITELIST') {
        return this.contracts.OVM_DeployerWhitelist.address
Kelvin Fichter's avatar
Kelvin Fichter committed
254
      } else if (kv.startsWith('$DUMMY_OVM_ADDRESS_')) {
255
        return ExecutionManagerTestRunner.getDummyAddress(kv)
Kelvin Fichter's avatar
Kelvin Fichter committed
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
      } else {
        return kv
      }
    }

    let ret: any = cloneDeep(obj)
    if (Array.isArray(ret)) {
      ret = ret.map((element: any) => {
        return this.setPlaceholderStrings(element)
      })
    } else if (typeof ret === 'object' && ret !== null) {
      for (const key of Object.keys(ret)) {
        const replacedKey = getReplacementString(key)

        if (replacedKey !== key) {
          ret[replacedKey] = ret[key]
          delete ret[key]
        }

        ret[replacedKey] = this.setPlaceholderStrings(ret[replacedKey])
      }
    } else if (typeof ret === 'string') {
      ret = getReplacementString(ret)
    }

    return ret
  }

284 285 286 287 288 289 290 291 292
  private async runTestStep(step: TestStep | TestStep_Run) {
    if (isTestStep_Run(step)) {
      let calldata: string
      if (step.functionParams.data) {
        calldata = step.functionParams.data
      } else {
        const runStep: TestStep_CALL = {
          functionName: 'ovmCALL',
          functionParams: {
293
            gasLimit: OVM_TX_GAS_LIMIT,
ben-chain's avatar
ben-chain committed
294 295 296
            target: ExecutionManagerTestRunner.getDummyAddress(
              '$DUMMY_OVM_ADDRESS_1'
            ),
Kelvin Fichter's avatar
Kelvin Fichter committed
297
            subSteps: step.functionParams.subSteps,
298
          },
Kelvin Fichter's avatar
Kelvin Fichter committed
299
          expectedReturnStatus: true,
300 301 302 303 304
        }

        calldata = this.encodeFunctionData(runStep)
      }

305
      const toRun = this.contracts.OVM_ExecutionManager.run(
306 307
        {
          timestamp: step.functionParams.timestamp,
308
          blockNumber: 0,
309
          l1QueueOrigin: step.functionParams.queueOrigin,
310
          l1TxOrigin: step.functionParams.origin,
311 312
          entrypoint: step.functionParams.entrypoint,
          gasLimit: step.functionParams.gasLimit,
Kelvin Fichter's avatar
Kelvin Fichter committed
313
          data: calldata,
314
        },
ben-chain's avatar
ben-chain committed
315
        this.contracts.OVM_StateManager.address,
316
        { gasLimit: step.suppliedGas || RUN_OVM_TEST_GAS }
Kelvin Fichter's avatar
Kelvin Fichter committed
317
      )
318 319 320 321 322
      if (!!step.expectedRevertValue) {
        await expect(toRun).to.be.revertedWith(step.expectedRevertValue)
      } else {
        await toRun
      }
323 324
    } else {
      await this.contracts.OVM_ExecutionManager.ovmCALL(
325
        OVM_TX_GAS_LIMIT,
ben-chain's avatar
ben-chain committed
326
        ExecutionManagerTestRunner.getDummyAddress('$DUMMY_OVM_ADDRESS_1'),
327 328 329
        this.contracts.Helper_TestRunner.interface.encodeFunctionData(
          'runSingleTestStep',
          [this.parseTestStep(step)]
ben-chain's avatar
ben-chain committed
330
        ),
ben-chain's avatar
ben-chain committed
331
        { gasLimit: RUN_OVM_TEST_GAS }
332 333
      )
    }
Kelvin Fichter's avatar
Kelvin Fichter committed
334 335 336 337 338 339 340 341
  }

  private parseTestStep(step: TestStep): ParsedTestStep {
    return {
      functionName: step.functionName,
      functionData: this.encodeFunctionData(step),
      expectedReturnStatus: this.getReturnStatus(step),
      expectedReturnData: this.encodeExpectedReturnData(step),
342
      onlyValidateFlag: this.shouldStepOnlyValidateFlag(step),
Kelvin Fichter's avatar
Kelvin Fichter committed
343 344 345
    }
  }

346 347 348 349 350 351 352 353 354
  private shouldStepOnlyValidateFlag(step: TestStep): boolean {
    if (!!(step as any).expectedReturnValue) {
      if (!!((step as any).expectedReturnValue as any).onlyValidateFlag) {
        return true
      }
    }
    return false
  }

Kelvin Fichter's avatar
Kelvin Fichter committed
355 356 357 358 359
  private getReturnStatus(step: TestStep): boolean {
    if (isTestStep_evm(step)) {
      return false
    } else if (isTestStep_Context(step)) {
      return true
360
    } else if (isTestStep_CALL(step)) {
361 362 363
      if (
        isRevertFlagError(step.expectedReturnValue) &&
        (step.expectedReturnValue.flag === REVERT_FLAGS.INVALID_STATE_ACCESS ||
364 365
          step.expectedReturnValue.flag === REVERT_FLAGS.STATIC_VIOLATION ||
          step.expectedReturnValue.flag === REVERT_FLAGS.CREATOR_NOT_ALLOWED)
366
      ) {
367 368 369 370
        return step.expectedReturnStatus
      } else {
        return true
      }
Kelvin Fichter's avatar
Kelvin Fichter committed
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
    } else {
      return step.expectedReturnStatus
    }
  }

  private encodeFunctionData(step: TestStep): string {
    if (isTestStep_evm(step)) {
      if (isRevertFlagError(step.returnData)) {
        return encodeRevertData(
          step.returnData.flag,
          step.returnData.data,
          step.returnData.nuisanceGasLeft,
          step.returnData.ovmGasRefund
        )
      } else {
        return step.returnData || '0x'
      }
    }

    let functionParams: any[] = []
    if (
      isTestStep_SSTORE(step) ||
      isTestStep_SLOAD(step) ||
Kevin Ho's avatar
Kevin Ho committed
394
      isTestStep_SETNONCE(step) ||
Kelvin Fichter's avatar
Kelvin Fichter committed
395 396
      isTestStep_EXTCODESIZE(step) ||
      isTestStep_EXTCODEHASH(step) ||
Kevin Ho's avatar
Kevin Ho committed
397 398
      isTestStep_EXTCODECOPY(step) ||
      isTestStep_CREATEEOA(step)
Kelvin Fichter's avatar
Kelvin Fichter committed
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
    ) {
      functionParams = Object.values(step.functionParams)
    } else if (isTestStep_CALL(step)) {
      functionParams = [
        step.functionParams.gasLimit,
        step.functionParams.target,
        step.functionParams.calldata ||
          this.contracts.Helper_TestRunner.interface.encodeFunctionData(
            'runMultipleTestSteps',
            [
              step.functionParams.subSteps.map((subStep) => {
                return this.parseTestStep(subStep)
              }),
            ]
          ),
      ]
    } else if (isTestStep_CREATE(step)) {
      functionParams = [
        this.contracts.Factory__Helper_TestRunner_CREATE.getDeployTransaction(
          step.functionParams.bytecode || '0x',
          step.functionParams.subSteps?.map((subStep) => {
            return this.parseTestStep(subStep)
          }) || []
        ).data,
      ]
424 425 426 427 428 429 430 431 432 433
    } else if (isTestStep_CREATE2(step)) {
      functionParams = [
        this.contracts.Factory__Helper_TestRunner_CREATE.getDeployTransaction(
          step.functionParams.bytecode || '0x',
          step.functionParams.subSteps?.map((subStep) => {
            return this.parseTestStep(subStep)
          }) || []
        ).data,
        step.functionParams.salt,
      ]
Kelvin Fichter's avatar
Kelvin Fichter committed
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
    } else if (isTestStep_REVERT(step)) {
      functionParams = [step.revertData || '0x']
    }

    return this.contracts.OVM_ExecutionManager.interface.encodeFunctionData(
      step.functionName,
      functionParams
    )
  }

  private encodeExpectedReturnData(step: TestStep): string {
    if (isTestStep_evm(step)) {
      return '0x'
    }

    if (isRevertFlagError(step.expectedReturnValue)) {
      return encodeRevertData(
        step.expectedReturnValue.flag,
        step.expectedReturnValue.data,
        step.expectedReturnValue.nuisanceGasLeft,
        step.expectedReturnValue.ovmGasRefund
      )
    }

458 459 460 461
    if (isTestStep_REVERT(step)) {
      return step.expectedReturnValue || '0x'
    }

Kelvin Fichter's avatar
Kelvin Fichter committed
462 463 464 465
    let returnData: any[] = []
    if (isTestStep_CALL(step)) {
      if (step.expectedReturnValue === '0x00') {
        return step.expectedReturnValue
ben-chain's avatar
ben-chain committed
466 467 468 469
      } else if (
        typeof step.expectedReturnValue === 'string' ||
        step.expectedReturnValue === undefined
      ) {
Kelvin Fichter's avatar
Kelvin Fichter committed
470 471 472 473
        returnData = [
          step.expectedReturnStatus,
          step.expectedReturnValue || '0x',
        ]
474
      } else {
ben-chain's avatar
ben-chain committed
475 476 477 478
        returnData = [
          step.expectedReturnValue.ovmSuccess,
          step.expectedReturnValue.returnData,
        ]
Kelvin Fichter's avatar
Kelvin Fichter committed
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
      }
    } else if (BigNumber.isBigNumber(step.expectedReturnValue)) {
      returnData = [step.expectedReturnValue.toHexString()]
    } else if (step.expectedReturnValue !== undefined) {
      if (step.expectedReturnValue === '0x00') {
        return step.expectedReturnValue
      } else {
        returnData = [step.expectedReturnValue]
      }
    }

    return this.contracts.OVM_ExecutionManager.interface.encodeFunctionResult(
      step.functionName,
      returnData
    )
  }
}