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
18 changes: 18 additions & 0 deletions v-next/example-project/contracts/Counter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ contract CounterTest {
require(counter.x() == 0, "Initial value should be 0");
}

function testFailInitialValue() public view {
require(counter.x() == 1, "Initial value should be 1");
}

function testFuzzInc(uint8 x) public {
for (uint8 i = 0; i < x; i++) {
counter.inc();
}
require(counter.x() == x, "Value after calling inc x times should be x");
}

function testFailFuzzInc(uint8 x) public {
for (uint8 i = 0; i < x; i++) {
counter.inc();
}
require(counter.x() == x + 1, "Value after calling inc x times should be x + 1");
}

// function invariant() public pure {
// assert(true);
// }
Expand All @@ -47,6 +58,13 @@ contract FailingCounterTest {
);
}

function testFailFuzzInc(uint8 x) public {
for (uint8 i = 0; i < x; i++) {
counter.inc();
}
require(counter.x() == x, "Value after calling inc x times should be x");
}

// function invariant() public pure {
// assert(false);
// }
Expand Down
3 changes: 3 additions & 0 deletions v-next/example-project/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ const config: HardhatUserConfig = {
"forge-std/=npm/[email protected]/src/",
],
},
solidityTest: {
testFail: true,
},
};

export default config;
109 changes: 109 additions & 0 deletions v-next/hardhat/src/internal/builtin-plugins/solidity-test/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type { HardhatUserConfig } from "../../../config.js";
import type { HardhatConfig } from "../../../types/config.js";
import type { HardhatUserConfigValidationError } from "../../../types/hooks.js";

import {
unionType,
validateUserConfigZodType,
} from "@ignored/hardhat-vnext-zod-utils";
import { z } from "zod";

const solidityTestUserConfigType = z.object({
timeout: z.number().optional(),
fsPermissions: z
.object({
readWrite: z.array(z.string()).optional(),
read: z.array(z.string()).optional(),
write: z.array(z.string()).optional(),
})
.optional(),
trace: z.boolean().optional(),
testFail: z.boolean().optional(),
labels: z
.array(
z.object({
address: z.string().startsWith("0x"),
label: z.string(),
}),
)
.optional(),
isolate: z.boolean().optional(),
ffi: z.boolean().optional(),
sender: z.string().startsWith("0x").optional(),
txOrigin: z.string().startsWith("0x").optional(),
initialBalance: z.bigint().optional(),
blockBaseFeePerGas: z.bigint().optional(),
blockCoinbase: z.string().startsWith("0x").optional(),
blockTimestamp: z.bigint().optional(),
blockDifficulty: z.bigint().optional(),
blockGasLimit: z.bigint().optional(),
disableBlockGasLimit: z.boolean().optional(),
memoryLimit: z.bigint().optional(),
ethRpcUrl: z.string().optional(),
forkBlockNumber: z.bigint().optional(),
rpcEndpoints: z.record(z.string()).optional(),
rpcCachePath: z.string().optional(),
rpcStorageCaching: z
.object({
chains: unionType(
[z.enum(["All", "None"]), z.array(z.string())],
"Expected `All`, `None` or a list of chain names to cache",
),
endpoints: unionType(
[
z.enum(["All", "Remote"]),
z.object({
source: z.string(),
}),
],
"Expected `All`, `Remote` or a RegExp object matching endpoints to cacche",
),
})
.optional(),
promptTimeout: z.number().optional(),
fuzz: z
.object({
failurePersistDir: z.string().optional(),
failurePersistFile: z.string().optional(),
runs: z.number().optional(),
maxTestRejects: z.number().optional(),
seed: z.string().optional(),
dictionaryWeight: z.number().optional(),
includeStorage: z.boolean().optional(),
includePushBytes: z.boolean().optional(),
})
.optional(),
invariant: z
.object({
failurePersistDir: z.string().optional(),
runs: z.number().optional(),
depth: z.number().optional(),
failOnRevert: z.boolean().optional(),
callOverride: z.boolean().optional(),
dictionaryWeight: z.number().optional(),
includeStorage: z.boolean().optional(),
includePushBytes: z.boolean().optional(),
shrinkRunLimit: z.number().optional(),
})
.optional(),
});

const userConfigType = z.object({
solidityTest: solidityTestUserConfigType.optional(),
});

export function validateSolidityTestUserConfig(
userConfig: unknown,
): HardhatUserConfigValidationError[] {
return validateUserConfigZodType(userConfig, userConfigType);
}

export async function resolveSolidityTestUserConfig(
userConfig: HardhatUserConfig,
resolvedConfig: HardhatConfig,
): Promise<HardhatConfig> {
return {
...resolvedConfig,
solidityTest: userConfig.solidityTest ?? {},
};
}
101 changes: 100 additions & 1 deletion v-next/hardhat/src/internal/builtin-plugins/solidity-test/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,109 @@
import type { RunOptions } from "./runner.js";
import type { ArtifactsManager } from "../../../types/artifacts.js";
import type { Artifact } from "@ignored/edr";
import type { SolidityTestConfig } from "../../../types/config.js";
import type {
Artifact,
SolidityTestRunnerConfigArgs,
CachedChains,
CachedEndpoints,
PathPermission,
StorageCachingConfig,
AddressLabel,
} from "@ignored/edr";

import { HardhatError } from "@ignored/hardhat-vnext-errors";
import { exists } from "@ignored/hardhat-vnext-utils/fs";
import { hexStringToBytes } from "@ignored/hardhat-vnext-utils/hex";
import { resolveFromRoot } from "@ignored/hardhat-vnext-utils/path";

function hexStringToBuffer(hexString: string): Buffer {
return Buffer.from(hexStringToBytes(hexString));
}

export function solidityTestConfigToRunOptions(
config: SolidityTestConfig,
): RunOptions {
return config;
}

export function solidityTestConfigToSolidityTestRunnerConfigArgs(
projectRoot: string,
config: SolidityTestConfig,
): SolidityTestRunnerConfigArgs {
const fsPermissions: PathPermission[] | undefined = [
config.fsPermissions?.readWrite?.map((path) => ({ access: 0, path })) ?? [],
config.fsPermissions?.read?.map((path) => ({ access: 0, path })) ?? [],
config.fsPermissions?.write?.map((path) => ({ access: 0, path })) ?? [],
].flat(1);

const labels: AddressLabel[] | undefined = config.labels?.map(
({ address, label }) => ({
address: hexStringToBuffer(address),
label,
}),
);

let rpcStorageCaching: StorageCachingConfig | undefined;
if (config.rpcStorageCaching !== undefined) {
let chains: CachedChains | string[];
if (Array.isArray(config.rpcStorageCaching.chains)) {
chains = config.rpcStorageCaching.chains;
} else {
const rpcStorageCachingChains: "All" | "None" =
config.rpcStorageCaching.chains;
switch (rpcStorageCachingChains) {
case "All":
chains = 0;
break;
case "None":
chains = 1;
break;
}
}
let endpoints: CachedEndpoints | string;
if (config.rpcStorageCaching.endpoints instanceof RegExp) {
endpoints = config.rpcStorageCaching.endpoints.source;
} else {
const rpcStorageCachingEndpoints: "All" | "Remote" =
config.rpcStorageCaching.endpoints;
switch (rpcStorageCachingEndpoints) {
case "All":
endpoints = 0;
break;
case "Remote":
endpoints = 1;
break;
}
}
rpcStorageCaching = {
chains,
endpoints,
};
}

const sender: Buffer | undefined =
config.sender === undefined ? undefined : hexStringToBuffer(config.sender);
const txOrigin: Buffer | undefined =
config.txOrigin === undefined
? undefined
: hexStringToBuffer(config.txOrigin);
const blockCoinbase: Buffer | undefined =
config.blockCoinbase === undefined
? undefined
: hexStringToBuffer(config.blockCoinbase);

return {
projectRoot,
...config,
fsPermissions,
labels,
sender,
txOrigin,
blockCoinbase,
rpcStorageCaching,
};
}

export async function getArtifacts(
hardhatArtifacts: ArtifactsManager,
): Promise<Artifact[]> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ConfigHooks } from "../../../../types/hooks.js";

import {
resolveSolidityTestUserConfig,
validateSolidityTestUserConfig,
} from "../config.js";

export default async (): Promise<Partial<ConfigHooks>> => {
const handlers: Partial<ConfigHooks> = {
validateUserConfig: async (userConfig) =>
validateSolidityTestUserConfig(userConfig),
resolveUserConfig: async (
userConfig,
resolveConfigurationVariable,
next,
) => {
const resolvedConfig = await next(
userConfig,
resolveConfigurationVariable,
);

return resolveSolidityTestUserConfig(userConfig, resolvedConfig);
},
};

return handlers;
};
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import type { HardhatPlugin } from "../../../types/plugins.js";

import { ArgumentType } from "../../../types/arguments.js";
import { task } from "../../core/config.js";

import "./type-extensions.js";

const hardhatPlugin: HardhatPlugin = {
id: "builtin:solidity-tests",
hookHandlers: {
config: import.meta.resolve("./hook-handlers/config.js"),
},
tasks: [
task(["test", "solidity"], "Run the Solidity tests")
.setAction(import.meta.resolve("./task-action.js"))
.addOption({
name: "timeout",
description:
"The maximum time in milliseconds to wait for all the test suites to finish",
type: ArgumentType.INT,
defaultValue: 60 * 60 * 1000,
})
.addFlag({
name: "noCompile",
description: "Don't compile the project before running the tests",
Expand Down
28 changes: 14 additions & 14 deletions v-next/hardhat/src/internal/builtin-plugins/solidity-test/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ function getEdrContext(): EdrContext {
export interface RunOptions {
/**
* The maximum time in milliseconds to wait for all the test suites to finish.
*
* If not provided, the default is 1 hour.
*/
timeout?: number;
}
Expand Down Expand Up @@ -62,18 +60,20 @@ export function run(

const remainingSuites = new Set(testSuiteIds.map(formatArtifactId));

// NOTE: The timeout prevents the situation in which the stream is never
// closed. This can happen if we receive fewer suite results than the
// number of test suites. The timeout is set to 1 hour.
const duration = options?.timeout ?? 60 * 60 * 1000;
const timeout = setTimeout(() => {
controller.error(
new HardhatError(HardhatError.ERRORS.SOLIDITY_TESTS.RUNNER_TIMEOUT, {
duration,
suites: Array.from(remainingSuites).join(", "),
}),
);
}, duration);
let timeout: NodeJS.Timeout | undefined;
if (options?.timeout !== undefined) {
timeout = setTimeout(() => {
controller.error(
new HardhatError(
HardhatError.ERRORS.SOLIDITY_TESTS.RUNNER_TIMEOUT,
{
duration: options.timeout,
suites: Array.from(remainingSuites).join(", "),
},
),
);
}, options.timeout);
}

// TODO: Just getting the context here to get it initialized, but this
// is not currently tied to the `runSolidityTests` function.
Expand Down
Loading
Loading