Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eight-dancers-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@eth-optimism/smock": minor
---

Adds support for hardhat ^2.2.0, required because of move to ethereumjs-vm v5.
6 changes: 3 additions & 3 deletions packages/smock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
"bn.js": "^5.2.0"
},
"devDependencies": {
"@nomiclabs/ethereumjs-vm": "4.2.2",
"@nomiclabs/ethereumjs-vm": "^4.2.2",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/lodash": "^4.14.161",
"chai": "^4.3.0",
"ethereum-waffle": "^3.3.0",
"ethers": "^5.0.32",
"ethers": "^5.0.31",
"glob": "^7.1.6",
"hardhat": "^2.1.1",
"hardhat": "^2.2.1",
"lodash": "^4.17.20",
"prettier": "^2.2.1"
}
Expand Down
33 changes: 33 additions & 0 deletions packages/smock/src/common/hardhat-common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* Imports: External */
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { HardhatNetworkProvider } from 'hardhat/internal/hardhat-network/provider/provider'
import { fromHexString, toHexString } from '@eth-optimism/core-utils'

/**
* Finds the "base" Ethereum provider of the current hardhat environment.
Expand Down Expand Up @@ -44,3 +45,35 @@ export const findBaseHardhatProvider = (
// https://github.com/nomiclabs/hardhat/blob/master/packages/hardhat-core/src/internal/hardhat-network/provider/provider.ts
return provider as any
}

/**
* Converts a string into the fancy new address thing that ethereumjs-vm v5 expects while also
* maintaining backwards compatibility with ethereumjs-vm v4.
* @param address String address to convert into the fancy new address type.
* @returns Fancified address.
*/
export const toFancyAddress = (address: string): any => {
const fancyAddress = fromHexString(address)
;(fancyAddress as any).buf = fromHexString(address)
;(fancyAddress as any).toString = (encoding?: any) => {
if (encoding === undefined) {
return address.toLowerCase()
} else {
return fromHexString(address).toString(encoding)
}
}
return fancyAddress
}

/**
* Same as toFancyAddress but in the opposite direction.
* @param fancyAddress Fancy address to turn into a string.
* @returns Way more boring address.
*/
export const fromFancyAddress = (fancyAddress: any): string => {
if (fancyAddress.buf) {
return toHexString(fancyAddress.buf)
} else {
return toHexString(fancyAddress)
}
}
25 changes: 18 additions & 7 deletions packages/smock/src/smockit/binding.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
/* Imports: External */
import { TransactionExecutionError } from 'hardhat/internal/hardhat-network/provider/errors'
import { HardhatNetworkProvider } from 'hardhat/internal/hardhat-network/provider/provider'
import { decodeRevertReason } from 'hardhat/internal/hardhat-network/stack-traces/revert-reasons'
import { VmError } from '@nomiclabs/ethereumjs-vm/dist/exceptions'
import { toHexString, fromHexString } from '@eth-optimism/core-utils'
import BN from 'bn.js'

// Handle hardhat ^2.2.0
let TransactionExecutionError: any
try {
// tslint:disable-next-line
TransactionExecutionError = require('hardhat/internal/hardhat-network/provider/errors')
.TransactionExecutionError
} catch (err) {
// tslint:disable-next-line
TransactionExecutionError = require('hardhat/internal/core/providers/errors')
.TransactionExecutionError
}

/* Imports: Internal */
import { MockContract, SmockedVM } from './types'
import { fromFancyAddress, toFancyAddress } from '../common'

/**
* Checks to see if smock has been initialized already. Basically just checking to see if we've
Expand Down Expand Up @@ -52,7 +63,7 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => {
return
}

const target = toHexString(message.to).toLowerCase()
const target = fromFancyAddress(message.to)

// Check if the target address is a smocked contract.
if (!(target in vm._smockState.mocks)) {
Expand All @@ -77,7 +88,7 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => {
// later creates a contract at that address. Not sure how to handle this case. Very open to
// ideas.
if (result.createdAddress) {
const created = toHexString(result.createdAddress).toLowerCase()
const created = fromFancyAddress(result.createdAddress)
if (created in vm._smockState.mocks) {
delete vm._smockState.mocks[created]
}
Expand All @@ -92,7 +103,7 @@ const initializeSmock = (provider: HardhatNetworkProvider): void => {
// contracts never create new sub-calls (meaning this `afterMessage` event corresponds directly
// to a `beforeMessage` event emitted during a call to a smock contract).
const message = vm._smockState.messages.pop()
const target = toHexString(message.to).toLowerCase()
const target = fromFancyAddress(message.to)

// Not sure if this can ever actually happen? Just being safe.
if (!(target in vm._smockState.mocks)) {
Expand Down Expand Up @@ -157,7 +168,7 @@ export const bindSmock = async (
}

const vm: SmockedVM = (provider as any)._node._vm
const pStateManager = vm.pStateManager
const pStateManager = vm.pStateManager || vm.stateManager

// Add mock to our list of mocks currently attached to the VM.
vm._smockState.mocks[mock.address.toLowerCase()] = mock
Expand All @@ -166,7 +177,7 @@ export const bindSmock = async (
// Solidity will sometimes throw if it's calling something without code (I forget the exact
// scenario that causes this throw).
await pStateManager.putContractCode(
fromHexString(mock.address),
toFancyAddress(mock.address),
Buffer.from('00', 'hex')
)
}
13 changes: 11 additions & 2 deletions packages/smock/src/smockit/smockit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { toHexString, fromHexString } from '@eth-optimism/core-utils'
/* Imports: Internal */
import {
isArtifact,
isContract,
isContractFactory,
isInterface,
MockContract,
MockContractFunction,
MockReturnValue,
Expand Down Expand Up @@ -33,13 +36,19 @@ const makeContractInterfaceFromSpec = async (
return spec.interface
} else if (spec instanceof ethers.utils.Interface) {
return spec
} else if (isInterface(spec)) {
return spec as any
} else if (isContractFactory(spec)) {
return (spec as any).interface
} else if (isContract(spec)) {
return (spec as any).interface
} else if (isArtifact(spec)) {
return new ethers.utils.Interface(spec.abi)
} else if (typeof spec === 'string') {
try {
return new ethers.utils.Interface(spec)
} catch (err) {
return (await hre.ethers.getContractFactory(spec)).interface
return (await (hre as any).ethers.getContractFactory(spec)).interface
}
} else {
return new ethers.utils.Interface(spec)
Expand Down Expand Up @@ -150,7 +159,7 @@ export const smockit = async (
const contract = new ethers.Contract(
opts.address || makeRandomAddress(),
await makeContractInterfaceFromSpec(spec),
opts.provider || hre.ethers.provider // TODO: Probably check that this exists.
opts.provider || (hre as any).ethers.provider // TODO: Probably check that this exists.
) as MockContract

// Start by smocking the fallback.
Expand Down
30 changes: 29 additions & 1 deletion packages/smock/src/smockit/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ export interface SmockedVM {

on: (event: string, callback: Function) => void

pStateManager: {
stateManager?: {
putContractCode: (address: Buffer, code: Buffer) => Promise<void>
}

pStateManager?: {
putContractCode: (address: Buffer, code: Buffer) => Promise<void>
}
}
Expand Down Expand Up @@ -90,6 +94,30 @@ export const isMockContract = (obj: any): obj is MockContract => {
)
}

export const isInterface = (obj: any): boolean => {
return (
obj &&
obj.functions !== undefined &&
obj.errors !== undefined &&
obj.structs !== undefined &&
obj.events !== undefined &&
Array.isArray(obj.fragments)
)
}

export const isContract = (obj: any): boolean => {
return (
obj &&
obj.functions !== undefined &&
obj.estimateGas !== undefined &&
obj.callStatic !== undefined
)
}

export const isContractFactory = (obj: any): boolean => {
return obj && obj.interface !== undefined && obj.deploy !== undefined
}

export const isArtifact = (obj: any): obj is Artifact => {
return (
obj &&
Expand Down
11 changes: 6 additions & 5 deletions packages/smock/src/smoddit/smoddit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fromHexString } from '@eth-optimism/core-utils'
import { ModifiableContract, ModifiableContractFactory } from './types'
import { getStorageLayout, getStorageSlots } from './storage'
import { toHexString32 } from '../utils'
import { findBaseHardhatProvider } from '../common'
import { findBaseHardhatProvider, toFancyAddress } from '../common'

/**
* Creates a modifiable contract factory.
Expand All @@ -29,10 +29,11 @@ export const smoddit = async (
}

// Pull out a reference to the vm's state manager.
const pStateManager = (provider as any)._node._vm.pStateManager
const vm: any = (provider as any)._node._vm
const pStateManager = vm.pStateManager || vm.stateManager

const layout = await getStorageLayout(name)
const factory = (await hre.ethers.getContractFactory(
const factory = (await (hre as any).ethers.getContractFactory(
name,
signer
)) as ModifiableContractFactory
Expand All @@ -50,7 +51,7 @@ export const smoddit = async (
const slots = getStorageSlots(layout, storage)
for (const slot of slots) {
await pStateManager.putContractStorage(
fromHexString(contract.address),
toFancyAddress(contract.address),
fromHexString(slot.hash.toLowerCase()),
fromHexString(slot.value)
)
Expand All @@ -67,7 +68,7 @@ export const smoddit = async (
if (
toHexString32(
await pStateManager.getContractStorage(
fromHexString(contract.address),
toFancyAddress(contract.address),
fromHexString(slot.hash.toLowerCase())
)
) !== slot.value
Expand Down
4 changes: 3 additions & 1 deletion packages/smock/test/smockit/function-manipulation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Imports: External */
import { ethers } from 'hardhat'
import hre from 'hardhat'
import { expect } from 'chai'
import { toPlainObject } from 'lodash'
import { BigNumber } from 'ethers'
Expand All @@ -8,6 +8,8 @@ import { BigNumber } from 'ethers'
import { MockContract, smockit } from '../../src'

describe('[smock]: function manipulation tests', () => {
const ethers = (hre as any).ethers

let mock: MockContract
beforeEach(async () => {
mock = await smockit('TestHelpers_BasicReturnContract')
Expand Down
10 changes: 6 additions & 4 deletions packages/smock/test/smockit/smock-initialization.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* Imports: External */
import { ethers, artifacts } from 'hardhat'
import hre from 'hardhat'
import { expect } from 'chai'

/* Imports: Internal */
import { smockit, isMockContract } from '../../src'

describe('[smock]: initialization tests', () => {
const ethers = (hre as any).ethers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did hre stop injecting the type to the global runtime?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure what happened here. TS started complaining when I updated to the latest hardhat version.


describe('initialization: ethers objects', () => {
it('should be able to create a SmockContract from an ethers ContractFactory', async () => {
const spec = await ethers.getContractFactory('TestHelpers_EmptyContract')
Expand Down Expand Up @@ -46,7 +48,7 @@ describe('[smock]: initialization tests', () => {
})

it('should be able to create a SmockContract from a JSON contract artifact object', async () => {
const artifact = await artifacts.readArtifact(
const artifact = await hre.artifacts.readArtifact(
'TestHelpers_BasicReturnContract'
)
const spec = artifact
Expand All @@ -56,7 +58,7 @@ describe('[smock]: initialization tests', () => {
})

it('should be able to create a SmockContract from a JSON contract ABI object', async () => {
const artifact = await artifacts.readArtifact(
const artifact = await hre.artifacts.readArtifact(
'TestHelpers_BasicReturnContract'
)
const spec = artifact.abi
Expand All @@ -66,7 +68,7 @@ describe('[smock]: initialization tests', () => {
})

it('should be able to create a SmockContract from a JSON contract ABI string', async () => {
const artifact = await artifacts.readArtifact(
const artifact = await hre.artifacts.readArtifact(
'TestHelpers_BasicReturnContract'
)
const spec = JSON.stringify(artifact.abi)
Expand Down
Loading