Skip to content

Commit

Permalink
Move logic from hardhat patches (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
yivlad authored Nov 28, 2022
1 parent af4ce45 commit c5b5c29
Show file tree
Hide file tree
Showing 27 changed files with 1,664 additions and 4,669 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-walls-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomiclabs/hardhat-waffle": patch
---

Introduce skipEstimateGas and injectCallHistory fields to hardhat config
13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,31 @@
"@types/chai": "^4.2.0",
"@types/fs-extra": "^5.1.0",
"@types/mocha": "^9.1.0",
"@types/node": "^12.0.0",
"@types/node": "^18.11.9",
"@typescript-eslint/eslint-plugin": "4.29.2",
"@typescript-eslint/parser": "4.29.2",
"chai": "^4.2.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-import": "2.24.1",
"eslint-plugin-prettier": "3.4.0",
"ethereum-waffle": "^3.2.0",
"ethereum-waffle": "^4.0.7",
"ethers": "^5.0.0",
"fs-extra": "^10.1.0",
"hardhat": "^2.0.0",
"mocha": "^10.0.0",
"prettier": "2.4.1",
"rimraf": "^3.0.2",
"ts-node": "^8.1.0",
"typescript": "~4.5.2",
"@types/sinon-chai": "^3.2.3",
"@types/web3": "1.0.19",
"@changesets/cli": "^2.25.2"
},
"peerDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.0",
"ethereum-waffle": "^3.2.0",
"ethereum-waffle": "*",
"ethers": "^5.0.0",
"hardhat": "^2.0.0"
},
"dependencies": {
"@types/sinon-chai": "^3.2.3",
"@types/web3": "1.0.19"
}
}
50 changes: 0 additions & 50 deletions src/fixtures.ts

This file was deleted.

62 changes: 59 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,85 @@
import "@nomiclabs/hardhat-ethers";
import type { MockProvider } from "ethereum-waffle";
import type { providers, Signer } from "ethers";
import { extendEnvironment } from "hardhat/config";
import { lazyObject } from "hardhat/plugins";

import { getDeployMockContract, hardhatDeployContract } from "./deploy";
import { getLinkFunction } from "./link";
import { initializeWaffleMatchers } from "./matchers";
import "./type-extensions";
import { skipEstimateGas } from "./skip-estimate-gas";
import { injectCallHistory } from "./inject-call-history";

declare module "hardhat/types" {
export interface HardhatUserConfig {
waffle?: {
/**
* If true, the call history will be injected into the Hardhat Runtime Environment.
* This will allow you to use matchers `calledOnContract` and `calledOnContractWith`.
*
* @default false
*/
injectCallHistory?: boolean;
/**
* Allows to skip estimateGas step and return specific hex value when executing a transaction.
* Can be useful for speeding up tests and getting better error messages.
*
* @example "0xB71B00"
* @default undefined
*/
skipEstimateGas?: string;
};
}

export interface HardhatConfig {
waffle?: {
injectCallHistory?: boolean;
skipEstimateGas?: string;
};
}
}

extendEnvironment((hre) => {
// We can't actually implement a MockProvider because of its private
// properties, so we cast it here 😢
hre.waffle = lazyObject(() => {
const { WaffleMockProviderAdapter } = require("./waffle-provider-adapter");

const { hardhatCreateFixtureLoader } = require("./fixtures");

const hardhatWaffleProvider = new WaffleMockProviderAdapter(
hre.network
) as any;

// eslint-disable-next-line import/no-extraneous-dependencies
const { waffleChai } = require("@ethereum-waffle/chai");
// TODO: next line requires @ethereum-waffle/provider - do we want it to be this way?
// eslint-disable-next-line import/no-extraneous-dependencies
const { createFixtureLoader } = require("@ethereum-waffle/provider");

const hardhatCreateFixtureLoader = (
provider: MockProvider,
overrideSigners?: Signer[],
overrideProvider?: providers.JsonRpcProvider
) => {
return createFixtureLoader(overrideSigners, overrideProvider ?? provider);
};

if (hre.config.waffle?.skipEstimateGas !== undefined) {
skipEstimateGas(
hardhatWaffleProvider,
hre.config.waffle?.skipEstimateGas
);
}

if (hre.config.waffle?.injectCallHistory === true) {
injectCallHistory(hardhatWaffleProvider);
}

return {
provider: hardhatWaffleProvider,
deployContract: hardhatDeployContract.bind(undefined, hre),
deployMockContract: getDeployMockContract(),
solidity: require("./waffle-chai").waffleChai,
solidity: waffleChai,
createFixtureLoader: hardhatCreateFixtureLoader.bind(
undefined,
hardhatWaffleProvider
Expand Down
88 changes: 88 additions & 0 deletions src/inject-call-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type { RecordedCall } from "@ethereum-waffle/provider";
import { utils } from "ethers";

/**
* Injects call history into hardhat provider,
* making it possible to use matchers like calledOnContract
*/

class CallHistory {
public recordedCalls: RecordedCall[] = [];

public addUniqueCall(call: RecordedCall) {
if (
this.recordedCalls.find(
(c) => c.address === call.address && c.data === call.data
) === undefined
) {
this.recordedCalls.push(call);
}
}

public clearAll() {
this.recordedCalls = [];
}
}

function toRecordedCall(message: any): RecordedCall {
return {
address: message.to?.buf
? utils.getAddress(utils.hexlify(message.to.buf))
: undefined,
data: message.data ? utils.hexlify(message.data) : "0x",
};
}

function getHardhatVMEventEmitter(hardhatWaffleProvider: any) {
const vm =
hardhatWaffleProvider?._hardhatNetwork.provider?._wrapped._wrapped?._wrapped
?._node?._vmTracer?._vm;

/**
* There were changes related to the location of event emitter introduced
* in Hardhat version 2.11.0.
*/
return vm?.evm?.events ?? vm;
}

let injected = false;

export const injectCallHistory = (hardhatWaffleProvider: any) => {
if (injected) {
return;
}

const callHistory = new CallHistory();
hardhatWaffleProvider.clearCallHistory = () => {
callHistory.clearAll();
};

let beforeMessageListener: (message: any) => void | undefined;
const init =
hardhatWaffleProvider?._hardhatNetwork?.provider?._wrapped?._wrapped
?._wrapped?._init;
if (!init) {
throw new Error("Could not inject call history into hardhat provider");
}
hardhatWaffleProvider._hardhatNetwork.provider._wrapped._wrapped._wrapped._init =
async function () {
await init.apply(this);
if (typeof beforeMessageListener === "function") {
// has to be here because of weird behaviour of init function, which requires us to re-register the handler.
getHardhatVMEventEmitter(hardhatWaffleProvider)?.off?.(
"beforeMessage",
beforeMessageListener
);
}
beforeMessageListener = (message: any) => {
callHistory.addUniqueCall(toRecordedCall(message));
};
hardhatWaffleProvider.callHistory = callHistory.recordedCalls;
getHardhatVMEventEmitter(hardhatWaffleProvider)?.on?.(
"beforeMessage",
beforeMessageListener
);
};

injected = true;
};
3 changes: 2 additions & 1 deletion src/matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export function initializeWaffleMatchers(projectRoot: string) {
});

const chai = require(chaiPath);
const { waffleChai } = require("./waffle-chai");
// eslint-disable-next-line import/no-extraneous-dependencies
const { waffleChai } = require("@ethereum-waffle/chai");

chai.use(waffleChai);
} catch {
Expand Down
69 changes: 69 additions & 0 deletions src/skip-estimate-gas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { BigNumber } from "ethers";

let processRequest: any;

export function skipEstimateGas(
hardhatWaffleProvider: any,
estimateResult: string
) {
let estimateGasResult: BigNumber;
try {
estimateGasResult = BigNumber.from(estimateResult);
} catch {
throw new Error(
`The value of the skipEstimateGas (${estimateResult}) in \n` +
"hardhat config property must be a valid BigNumber string"
);
}
const init =
hardhatWaffleProvider._hardhatNetwork.provider._wrapped._wrapped._wrapped
._init;
hardhatWaffleProvider._hardhatNetwork.provider._wrapped._wrapped._wrapped._init =
async function () {
await init.apply(this);
if (
getHardhatVMEventEmitter(hardhatWaffleProvider)?.listenerCount(
"beforeMessage"
) < 2
) {
overrideProcessRequest(hardhatWaffleProvider, estimateGasResult);
}
};
}

function overrideProcessRequest(provider: any, estimateGasResult: BigNumber) {
const curProcessRequest =
provider._hardhatNetwork.provider._wrapped._wrapped._wrapped._ethModule
.processRequest;

if (curProcessRequest !== processRequest) {
const originalProcess =
provider._hardhatNetwork.provider._wrapped._wrapped._wrapped._ethModule.processRequest.bind(
provider._hardhatNetwork.provider._wrapped._wrapped._wrapped._ethModule
);
provider._hardhatNetwork.provider._wrapped._wrapped._wrapped._ethModule.processRequest =
(method: string, params: any[]) => {
if (method === "eth_estimateGas") {
return estimateGasResult.toHexString();
} else {
return originalProcess(method, params);
}
};

processRequest =
provider._hardhatNetwork.provider._wrapped._wrapped._wrapped._ethModule
.processRequest;
}
}

function getHardhatVMEventEmitter(provider: any) {
const vm =
provider?._hardhatNetwork.provider?._wrapped._wrapped?._wrapped?._node
?._vmTracer?._vm;

/**
* There were changes related to the location of event emitter introduced
* in Hardhat version 2.11.0.
*/
return vm?.evm?.events ?? vm;
}
Loading

0 comments on commit c5b5c29

Please sign in to comment.