smock
is a utility package that can generate mock Solidity contracts (for testing). smock
hooks into a ethereumjs-vm
instance so that mock contract functions can be written entirely in JavaScript. smock
currently only supports buidler, but will be extended to support other testing frameworks.
Some nice benefits of hooking in at the VM level:
- Don't need to deploy any special contracts just for mocking!
- All of the calls are synchronous.
- Perform arbitrary javascript logic within your return value (return a function).
- It sounds cool.
smock
also contains smoddit
, another utility that allows you to modify the internal storage of contracts. We've found this to be quite useful in cases where many interactions occur within a single contract (typically to save gas).
You can easily install smock
via npm
:
npm install @eth-optimism/smock
Or via yarn
:
yarn add @eth-optimism/smock
smoddit
requires access to the internal storage layout of your smart contracts. The Solidity compiler exposes this via the storageLayout
flag, but buidler
does not enable this flag by default (and doesn't provide an easy way to do so). When using smoddit
with buidler
, you must therefore (for now) also import our compiler-storage-layout
plugin within your buidler.config.ts
. smoddit
will not work without this plugin until support for custom compiler flags is added to buidler
.
Here's an example buidler.config.ts
that shows how to import the plugin:
// buidler.config.ts
import { usePlugin, BuidlerConfig } from 'hardhat/config'
usePlugin(...)
usePlugin(...)
import '@eth-optimism/smock/buidler-plugins/compiler-storage-layout'
const config: BuidlerConfig = {
...
}
export default config
import { smockit } from '@eth-optimism/smock'
const smockit = async (
spec: ContractInterface | Contract | ContractFactory,
opts: {
provider?: any,
address?: string,
},
): Promise<MockContract>
import { smoddit } from '@eth-optimism/smock'
const smoddit = async (
name: string,
signer?: any
): Promise<ModifiableContractFactory>
interface MockContract extends Contract {
smocked: {
[functionName: string]: MockContractFunction
}
}
interface MockContractFunction {
calls: string[]
will: {
return: {
(): void
with: (returnValue?: MockReturnValue) => void
}
revert: {
(): void
with: (revertValue?: string) => void
}
resolve: 'return' | 'revert'
}
}
export type MockReturnValue =
| string
| Object
| any[]
| ((...params: any[]) => MockReturnValue)
interface ModifiableContractFactory extends ContractFactory {
deploy: (...args: any[]) => Promise<ModifiableContract>
}
interface ModifiableContract extends Contract {
smodify: {
put: (storage: any) => void
set: (storage: any) => void
reset: () => void
}
}
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.myFunction.will.return.with('Some return value!')
console.log(await MyMockContract.myFunction()) // 'Some return value!'
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
const MyOtherContractFactory = await ethers.getContractFactory('MyOtherContract')
const MyOtherContract = await MyOtherContract.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.return.with('Some return value!')
// Assuming that MyOtherContract.myOtherFunction calls MyContract.myFunction.
await MyOtherContract.myOtherFunction()
console.log(MyMockContract.smocked.myFunction.calls.length) // 1
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
const MyOtherContractFactory = await ethers.getContractFactory('MyOtherContract')
const MyOtherContract = await MyOtherContract.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.return.with('Some return value!')
// Assuming that MyOtherContract.myOtherFunction calls MyContract.myFunction with 'Hello World!'.
await MyOtherContract.myOtherFunction()
console.log(MyMockContract.smocked.myFunction.calls[0]) // 'Hello World!'
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.return()
console.log(await MyMockContract.myFunction()) // []
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.return.with({
valueA: 'Some value',
valueB: 1234,
valueC: true
})
console.log(await MyMockContract.myFunction()) // ['Some value', 1234, true]
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.return.with(() => {
return 'Some return value!'
})
console.log(await MyMockContract.myFunction()) // ['Some return value!']
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.return.with((myFunctionArgument: string) => {
return myFunctionArgument
})
console.log(await MyMockContract.myFunction('Some return value!')) // ['Some return value!']
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.revert()
console.log(await MyMockContract.myFunction()) // Revert!
import { ethers } from 'hardhat'
import { smockit } from '@eth-optimism/smock'
const MyContractFactory = await ethers.getContractFactory('MyContract')
const MyContract = await MyContractFactory.deploy(...)
// Smockit!
const MyMockContract = await smockit(MyContract)
MyMockContract.smocked.myFunction.will.revert.with('0x1234')
console.log(await MyMockContract.myFunction('Some return value!')) // Revert!
import { ethers } from 'hardhat'
import { smoddit } from '@eth-optimism/smock'
// Smoddit!
const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
import { ethers } from 'hardhat'
import { smoddit } from '@eth-optimism/smock'
// Smoddit!
const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({
myInternalUint256: 1234
})
console.log(await MyMockContract.getMyInternalUint256()) // 1234
import { ethers } from 'hardhat'
import { smoddit } from '@eth-optimism/smock'
// Smoddit!
const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({
myInternalStruct: {
valueA: 1234,
valueB: true
}
})
console.log(await MyMockContract.getMyInternalStruct()) // { valueA: 1234, valueB: true }
import { ethers } from 'hardhat'
import { smoddit } from '@eth-optimism/smock'
// Smoddit!
const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({
myInternalMapping: {
1234: 5678
}
})
console.log(await MyMockContract.getMyInternalMappingValue(1234)) // 5678
import { ethers } from 'hardhat'
import { smoddit } from '@eth-optimism/smock'
// Smoddit!
const MyModifiableContractFactory = await smoddit('MyContract')
const MyModifiableContract = await MyModifiableContractFactory.deploy(...)
MyModifiableContract.smodify.put({
myInternalNestedMapping: {
1234: {
4321: 5678
}
}
4BDB
span>
})
console.log(await MyMockContract.getMyInternalNestedMappingValue(1234, 4321)) // 5678