Commit b439747a authored by Kelvin Fichter's avatar Kelvin Fichter

Cleaned up test utils

parent 5c97a885
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.7.0;
/* Interface Imports */
import { iOVM_SafetyChecker } from "../../iOVM/execution/iOVM_SafetyChecker.sol";
contract OVM_SafetyChecker is iOVM_SafetyChecker {
function isBytecodeSafe(
bytes memory _bytecode
)
override
public
view
returns (
bool _safe
)
{
return true;
}
}
......@@ -21,6 +21,7 @@
"ethereum-waffle": "3.0.0",
"ethers": "5.0.0",
"fs-extra": "^9.0.1",
"lodash": "^4.17.20",
"mocha": "^8.1.1",
"ts-node": "^9.0.0",
"typescript": "^4.0.2"
......
import { expect } from '../../../setup'
/* External Imports */
import bre, { ethers } from '@nomiclabs/buidler'
import { Contract } from 'ethers'
/* Internal Imports */
import { getModifiableStorageFactory } from '../../../helpers/storage/contract-storage'
import { runExecutionManagerTest } from '../../../helpers/test-parsing/parse-tests'
import { runExecutionManagerTest } from '../../../helpers/test-utils/test-parsing'
import { NON_NULL_BYTES32, GAS_LIMIT } from '../../../helpers'
const getCodeSize = async (address: string): Promise<number> => {
const code = await ethers.provider.getCode(address)
return (code.length - 2) / 2
}
describe('OVM_StateManager', () => {
let OVM_ExecutionManager: Contract
let OVM_StateManager: Contract
let Helper_CodeContractForCalls: Contract
before(async () => {
const Factory__OVM_ExecutionManager = await getModifiableStorageFactory(
'OVM_ExecutionManager'
)
const Factory__OVM_StateManager = await getModifiableStorageFactory(
'OVM_StateManager'
)
const Factory__Helper_CodeContractForCalls = await getModifiableStorageFactory(
'Helper_CodeContractForCalls'
)
OVM_ExecutionManager = await Factory__OVM_ExecutionManager.deploy(
'0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c'
)
OVM_StateManager = await Factory__OVM_StateManager.deploy()
Helper_CodeContractForCalls = await Factory__Helper_CodeContractForCalls.deploy()
})
const DUMMY_OVM_ADDRESS_1 = '0x' + '12'.repeat(20)
const DUMMY_OVM_ADDRESS_2 = '0x' + '21'.repeat(20)
describe('the test suite', () => {
it('does the test suite', async () => {
runExecutionManagerTest(
{
name: 'Top level test',
preState: {
ExecutionManager: {
ovmStateManager: OVM_StateManager.address,
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2
}
},
StateManager: {
accounts: {
[DUMMY_OVM_ADDRESS_1]: {
codeHash: NON_NULL_BYTES32,
ethAddress: Helper_CodeContractForCalls.address
},
[DUMMY_OVM_ADDRESS_2]: {
codeHash: NON_NULL_BYTES32,
ethAddress: Helper_CodeContractForCalls.address
}
}
}
runExecutionManagerTest(
{
name: 'Top level test',
preState: {
ExecutionManager: {
ovmStateManager: "$OVM_STATE_MANAGER",
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2
}
},
StateManager: {
accounts: {
"$DUMMY_OVM_ADDRESS_1": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
},
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2 - (await getCodeSize(Helper_CodeContractForCalls.address)) * 100 * 2
}
"$DUMMY_OVM_ADDRESS_2": {
codeHash: NON_NULL_BYTES32,
ethAddress: "$OVM_CALL_HELPER"
}
}
}
},
parameters: [
{
name: 'Do two ovmCALLs to one address',
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2 - (332) * 100 * 1
}
},
parameters: [
{
name: 'Do an ovmCALL',
parameters: [
{
steps: [
}
},
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
DUMMY_OVM_ADDRESS_1,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
DUMMY_OVM_ADDRESS_2,
[
{
functionName: 'ovmADDRESS',
functionParams: [],
returnStatus: true,
returnValues: [DUMMY_OVM_ADDRESS_2]
},
{
functionName: 'ovmCALLER',
functionParams: [],
returnStatus: true,
returnValues: [DUMMY_OVM_ADDRESS_1]
}
]
],
functionName: 'ovmADDRESS',
functionParams: [],
returnStatus: true,
returnValues: []
returnValues: ["$DUMMY_OVM_ADDRESS_1"]
},
{
functionName: 'ovmCALLER',
functionParams: [],
returnStatus: true,
returnValues: ["$DUMMY_OVM_ADDRESS_1"]
}
]
],
returnStatus: true,
returnValues: []
}
},
]
}
]
],
returnStatus: true,
returnValues: []
}
]
}
]
},
{
name: 'Do two ovmCALLs to two different addresses',
postState: {
ExecutionManager: {
messageRecord: {
nuisanceGasLeft: GAS_LIMIT / 2 - (332) * 100 * 2
}
]
}
},
OVM_ExecutionManager,
OVM_StateManager
)
})
})
})
parameters: [
{
steps: [
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_1",
[
{
functionName: 'ovmCALL',
functionParams: [
GAS_LIMIT / 2,
"$DUMMY_OVM_ADDRESS_2",
[
{
functionName: 'ovmADDRESS',
functionParams: [],
returnStatus: true,
returnValues: ["$DUMMY_OVM_ADDRESS_2"]
},
{
functionName: 'ovmCALLER',
functionParams: [],
returnStatus: true,
returnValues: ["$DUMMY_OVM_ADDRESS_1"]
}
]
],
returnStatus: true,
returnValues: []
},
]
],
returnStatus: true,
returnValues: []
}
]
}
]
}
]
}
)
import * as path from 'path'
import { ethers } from "@nomiclabs/buidler"
import { ContractFactory } from 'ethers'
import { Interface } from 'ethers/lib/utils'
const getContractDefinition = (name: string): any => {
return require(path.join(__dirname, '../../../artifacts', `${name}.json`))
}
export const getContractInterface = (name: string): Interface => {
const definition = getContractDefinition(name)
return new ethers.utils.Interface(definition.abi)
}
// const ExecutionManager: ContractFactory = await ethers.getContractFactory('iOVM_ExecutionManager')
export const iEM = getContractInterface('iOVM_ExecutionManager')
// const CodeContract: ContractFactory = await ethers.getContractFactory('Helper_CodeContractForCalls')
const iCC = getContractInterface('Helper_CodeContractForCalls')
export interface TestCallGenerator {
generateCalldata(): string
generateExpectedReturnData(): string
parseActualReturnData(returned: string): any
}
export abstract class baseOVMCallTest implements TestCallGenerator {
executionManagerMethodName: string = 'EM METHOD NAME NOT SET'
arguments: any[] = []
expectedReturnValues: any[] = []
generateCalldata(): string {
return iEM.encodeFunctionData(
this.executionManagerMethodName,
this.arguments
)
}
generateExpectedReturnData(): string {
return iEM.encodeFunctionResult(
this.executionManagerMethodName,
this.expectedReturnValues
)
}
parseActualReturnData(returned: string) {
const decodedResult = iEM.decodeFunctionResult(
this.executionManagerMethodName,
returned
)
return 'call to ExeMgr.' + this.executionManagerMethodName + ' returned: \n' + decodedResult
}
}
export class ovmADDRESSTest extends baseOVMCallTest {
constructor(
expectedAddress: string
) {
super()
this.executionManagerMethodName = 'ovmADDRESS'
this.expectedReturnValues = [expectedAddress]
}
}
export class ovmCALLERTest extends baseOVMCallTest {
constructor(
expectedMsgSender: string
) {
super()
this.executionManagerMethodName = 'ovmCALLER'
this.expectedReturnValues = [expectedMsgSender]
}
}
const DEFAULT_GAS_LIMIT = 1_000_000
export class ovmCALLTest extends baseOVMCallTest {
constructor(
callee: string,
calleeTests: Array<TestCallGenerator>,
shouldCalleeSucceed: boolean = true,
gasLimit: number = DEFAULT_GAS_LIMIT
) {
super()
this.executionManagerMethodName = 'ovmCALL'
this.arguments = [
gasLimit,
callee,
iCC.encodeFunctionData(
'runSteps',
[{
callsToEM: calleeTests.map((testGenerator) => {
return testGenerator.generateCalldata()
}),
shouldRevert: !shouldCalleeSucceed
}]
)
]
this.expectedReturnValues = [
shouldCalleeSucceed,
iCC.encodeFunctionResult(
'runSteps',
[calleeTests.map((testGenerator) => {
return {
success: true, //TODO: figure out if we need this not to happen
data: testGenerator.generateExpectedReturnData()
}
})]
)
]
}
}
import { expect } from '../../setup'
import { Contract, BigNumber } from "ethers"
import { TestCallGenerator, ovmADDRESSTest, ovmCALLERTest, ovmCALLTest } from "./call-generator"
import { GAS_LIMIT } from '../constants'
type SolidityFunctionParameter = string | number | BigNumber
interface TestStep {
functionName: string
functionParams: Array<SolidityFunctionParameter | TestStep[]>
returnStatus: boolean
returnValues: any[]
}
interface TestParameters {
steps: TestStep[]
}
interface TestDefinition {
name: string
preState?: {
ExecutionManager?: any,
StateManager?: any
},
parameters: Array<TestParameters | TestDefinition>,
postState?: {
ExecutionManager?: any,
StateManager?: any
},
}
const isTestDefinition = (parameters: TestParameters | TestDefinition): parameters is TestDefinition => {
return (parameters as TestDefinition).name !== undefined
}
/*
const encodeTestStep = (
step: TestStep,
ovmExecutionManager: Contract,
helperCodeContract: Contract
): string => {
const params = step.functionParams.map((functionParam) => {
if (Array.isArray(functionParam)) {
return
}
})
return ovmExecutionManager.interface.encodeFunctionData(
step.functionName,
params
)
}
*/
const getCorrectGenerator = (step: TestStep): TestCallGenerator => {
switch (step.functionName) {
case 'ovmADDRESS':
return new ovmADDRESSTest(step.returnValues[0])
case 'ovmCALLER':
return new ovmCALLERTest(step.returnValues[0])
case 'ovmCALL':
return new ovmCALLTest(
step.functionParams[1] as string,
(step.functionParams[2] as TestStep[]).map((param) => {
return getCorrectGenerator(param)
}),
step.returnStatus,
step.functionParams[0] as number
)
default:
throw new Error('Input type not implemented.')
}
}
const getTestGenerator = (
step: TestStep
): TestCallGenerator => {
return getCorrectGenerator(step)
}
export const runExecutionManagerTest = (
test: TestDefinition,
ovmExecutionManager: Contract,
ovmStateManager: Contract
): void => {
test.preState = test.preState || {}
test.postState = test.postState || {}
describe(`Standard test: ${test.name}`, () => {
test.parameters.map((parameters) => {
if (isTestDefinition(parameters)) {
runExecutionManagerTest(
{
...parameters,
preState: {
...test.preState,
...parameters.preState
},
postState: {
...test.postState,
...parameters.postState
}
},
ovmExecutionManager,
ovmStateManager,
)
} else {
beforeEach(async () => {
await ovmExecutionManager.__setContractStorage(test.preState.ExecutionManager)
await ovmStateManager.__setContractStorage(test.preState.StateManager)
})
afterEach(async () => {
await ovmExecutionManager.__checkContractStorage({
...test.preState.ExecutionManager,
...test.postState.ExecutionManager
})
await ovmStateManager.__checkContractStorage({
...test.preState.StateManager,
...test.postState.StateManager
})
})
parameters.steps.map((step, idx) => {
it(`should run test: ${test.name} ${idx}`, async () => {
const testGenerator = getTestGenerator(step)
const callResult = await ovmExecutionManager.provider.call({
to: ovmExecutionManager.address,
data: testGenerator.generateCalldata(),
gasLimit: GAS_LIMIT
})
await ovmExecutionManager.signer.sendTransaction({
to: ovmExecutionManager.address,
data: testGenerator.generateCalldata(),
gasLimit: GAS_LIMIT
})
expect(callResult).to.equal(testGenerator.generateExpectedReturnData())
})
})
}
})
})
}
/* External Imports */
import { Contract } from 'ethers'
/* Internal Imports */
import { TestStep } from './test.types'
export interface TestCallGenerator {
getCalldata(): string
getReturnData(): string
}
export class DefaultTestGenerator implements TestCallGenerator {
constructor(
protected ovmExecutionManager: Contract,
protected ovmCallHelper: Contract,
protected step: TestStep
) {}
getFunctionParams(): any[] {
return this.step.functionParams
}
getReturnValues(): any[] {
return this.step.returnValues
}
getCalldata(): string {
return this.ovmExecutionManager.interface.encodeFunctionData(
this.step.functionName,
this.getFunctionParams()
)
}
getReturnData(): string {
return this.ovmExecutionManager.interface.encodeFunctionResult(
this.step.functionName,
this.getReturnValues()
)
}
}
export class ovmCALLGenerator extends DefaultTestGenerator {
getCalleeGenerators(): TestCallGenerator[] {
return (this.step.functionParams[2] as TestStep[]).map((step) => {
return getTestGenerator(
step,
this.ovmExecutionManager,
this.ovmCallHelper,
)
})
}
getFunctionParams(): any[] {
return [
this.step.functionParams[0],
this.step.functionParams[1],
this.ovmCallHelper.interface.encodeFunctionData(
'runSteps',
[
{
callsToEM: this.getCalleeGenerators().map((calleeGenerator) => {
return calleeGenerator.getCalldata()
}),
shouldRevert: !this.step.returnStatus
}
]
)
]
}
getReturnValues(): any[] {
return [
this.step.returnStatus,
this.ovmCallHelper.interface.encodeFunctionResult(
'runSteps',
[
this.getCalleeGenerators().map((calleeGenerator) => {
return {
success: true,
data: calleeGenerator.getReturnData()
}
})
]
)
]
}
}
export const getTestGenerator = (
step: TestStep,
ovmExecutionManager: Contract,
ovmCallHelper: Contract
): TestCallGenerator => {
switch (step.functionName) {
case 'ovmCALL':
return new ovmCALLGenerator(
ovmExecutionManager,
ovmCallHelper,
step
)
default:
return new DefaultTestGenerator(
ovmExecutionManager,
ovmCallHelper,
step
)
}
}
import { expect } from '../../setup'
/* External Imports */
import { Contract } from 'ethers'
import { cloneDeep } from 'lodash'
/* Internal Imports */
import { getModifiableStorageFactory } from '../storage/contract-storage'
import { GAS_LIMIT } from '../constants'
import { getTestGenerator } from './test-generation'
import { TestParameters, TestDefinition, isTestDefinition } from './test.types'
const setPlaceholderStrings = (
test: any,
ovmExecutionManager: Contract,
ovmStateManager: Contract,
ovmCallHelper: Contract
): any => {
const setPlaceholder = (
kv: string
): string => {
if (kv === '$OVM_EXECUTION_MANAGER') {
return ovmExecutionManager.address
} else if (kv === '$OVM_STATE_MANAGER') {
return ovmStateManager.address
} else if (kv === '$OVM_CALL_HELPER') {
return ovmCallHelper.address
} else if (kv.startsWith('$DUMMY_OVM_ADDRESS_')) {
return '0x' + kv.split('$DUMMY_OVM_ADDRESS_')[1].padStart(40, '0')
} else {
return kv
}
}
if (Array.isArray(test)) {
test = test.map((element) => {
return setPlaceholderStrings(
element,
ovmExecutionManager,
ovmStateManager,
ovmCallHelper
)
})
} else if (typeof test === 'object' && test !== null) {
for (const key of Object.keys(test)) {
const replacedKey = setPlaceholder(key)
if (replacedKey !== key) {
test[replacedKey] = test[key]
delete test[key]
}
test[replacedKey] = setPlaceholderStrings(
test[replacedKey],
ovmExecutionManager,
ovmStateManager,
ovmCallHelper
)
}
} else if (typeof test === 'string') {
test = setPlaceholder(test)
}
return test
}
const fixtureDeployContracts = async (): Promise <{
OVM_SafetyChecker: Contract,
OVM_StateManager: Contract,
OVM_ExecutionManager: Contract,
OVM_CallHelper: Contract
}> => {
const Factory__OVM_SafetyChecker = await getModifiableStorageFactory(
'OVM_SafetyChecker'
)
const Factory__OVM_StateManager = await getModifiableStorageFactory(
'OVM_StateManager'
)
const Factory__OVM_ExecutionManager = await getModifiableStorageFactory(
'OVM_ExecutionManager'
)
const Factory__Helper_CodeContractForCalls = await getModifiableStorageFactory(
'Helper_CodeContractForCalls'
)
const OVM_SafetyChecker = await Factory__OVM_SafetyChecker.deploy()
const OVM_StateManager = await Factory__OVM_StateManager.deploy()
const OVM_ExecutionManager = await Factory__OVM_ExecutionManager.deploy(OVM_SafetyChecker.address)
const OVM_CallHelper = await Factory__Helper_CodeContractForCalls.deploy()
return {
OVM_SafetyChecker,
OVM_StateManager,
OVM_ExecutionManager,
OVM_CallHelper,
}
}
export const runExecutionManagerTest = (
test: TestDefinition
): void => {
test.preState = test.preState || {}
test.postState = test.postState || {}
describe(`Standard test: ${test.name}`, () => {
test.parameters.map((parameters) => {
if (isTestDefinition(parameters)) {
runExecutionManagerTest(
{
...parameters,
preState: {
...test.preState,
...parameters.preState
},
postState: {
...test.postState,
...parameters.postState
}
}
)
} else {
let OVM_StateManager: Contract
let OVM_ExecutionManager: Contract
let OVM_CallHelper: Contract
beforeEach(async () => {
const contracts = await fixtureDeployContracts()
OVM_StateManager = contracts.OVM_StateManager
OVM_ExecutionManager = contracts.OVM_ExecutionManager
OVM_CallHelper = contracts.OVM_CallHelper
})
let replacedParams: TestParameters
let replacedTest: TestDefinition
beforeEach(async () => {
replacedParams = setPlaceholderStrings(
cloneDeep(parameters),
OVM_ExecutionManager,
OVM_StateManager,
OVM_CallHelper
)
replacedTest = setPlaceholderStrings(
cloneDeep(test),
OVM_ExecutionManager,
OVM_StateManager,
OVM_CallHelper
)
})
beforeEach(async () => {
await OVM_ExecutionManager.__setContractStorage(replacedTest.preState.ExecutionManager)
await OVM_StateManager.__setContractStorage(replacedTest.preState.StateManager)
})
afterEach(async () => {
await OVM_ExecutionManager.__checkContractStorage({
...replacedTest.preState.ExecutionManager,
...replacedTest.postState.ExecutionManager
})
await OVM_StateManager.__checkContractStorage({
...replacedTest.preState.StateManager,
...replacedTest.postState.StateManager
})
})
parameters.steps.map((step, idx) => {
it(`should run test: ${test.name} ${idx}`, async () => {
const testGenerator = getTestGenerator(
replacedParams.steps[idx],
OVM_ExecutionManager,
OVM_CallHelper
)
const callResult = await OVM_ExecutionManager.provider.call({
to: OVM_ExecutionManager.address,
data: testGenerator.getCalldata(),
gasLimit: GAS_LIMIT
})
await OVM_ExecutionManager.signer.sendTransaction({
to: OVM_ExecutionManager.address,
data: testGenerator.getCalldata(),
gasLimit: GAS_LIMIT
})
expect(callResult).to.equal(testGenerator.getReturnData())
})
})
}
})
})
}
/* External Imports */
import { BigNumber } from 'ethers'
export type SolidityFunctionParameter = string | number | BigNumber
export interface TestStep {
functionName: string
functionParams: Array<SolidityFunctionParameter | TestStep[]>
returnStatus: boolean
returnValues: any[]
}
export interface TestParameters {
steps: TestStep[]
}
export interface TestDefinition {
name: string
preState?: {
ExecutionManager?: any,
StateManager?: any
},
parameters: Array<TestParameters | TestDefinition>,
postState?: {
ExecutionManager?: any,
StateManager?: any
},
}
export const isTestDefinition = (
parameters: TestParameters | TestDefinition
): parameters is TestDefinition => {
return (parameters as TestDefinition).name !== undefined
}
/* External Imports */
import chai = require('chai')
import Mocha from 'mocha'
import { solidity } from 'ethereum-waffle'
chai.use(solidity)
const should = chai.should()
const expect = chai.expect
export { should, expect }
export { should, expect, Mocha }
......@@ -5602,7 +5602,7 @@ lodash@4.17.14:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4:
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.4:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment