From 135cad9730578f5914fb0c95a3a3e7cb707776c6 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Thu, 6 Feb 2020 10:22:23 +0100 Subject: [PATCH 01/99] Added simple JSON-RPC server task. --- packages/buidler-core/package.json | 3 +- .../buidler-core/src/builtin-tasks/jsonrpc.ts | 166 ++++++++++++++++++ .../src/builtin-tasks/task-names.ts | 2 + .../src/internal/core/errors-list.ts | 12 ++ .../src/internal/core/providers/http.ts | 20 +++ .../src/internal/core/tasks/builtin-tasks.ts | 4 + 6 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 packages/buidler-core/src/builtin-tasks/jsonrpc.ts diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index f357aa8852..bca6086a89 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -68,6 +68,7 @@ "time-require": "^0.1.2" }, "dependencies": { + "@nomiclabs/ethereumjs-vm": "^4.1.1", "@types/bn.js": "^4.11.5", "@types/lru-cache": "^5.1.0", "abort-controller": "^3.0.0", @@ -75,6 +76,7 @@ "bip39": "^3.0.2", "chalk": "^2.4.2", "ci-info": "^2.0.0", + "concat-stream": "^2.0.0", "debug": "^4.1.1", "deepmerge": "^2.1.0", "download": "^7.1.0", @@ -86,7 +88,6 @@ "ethereumjs-common": "^1.3.2", "ethereumjs-tx": "^2.1.1", "ethereumjs-util": "^6.1.0", - "@nomiclabs/ethereumjs-vm": "^4.1.1", "find-up": "^2.1.0", "fp-ts": "1.19.3", "fs-extra": "^7.0.1", diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts new file mode 100644 index 0000000000..b165c42253 --- /dev/null +++ b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts @@ -0,0 +1,166 @@ +import chalk from "chalk"; +import debug from "debug"; +import http, { IncomingMessage, ServerResponse } from "http"; + +import { BUIDLER_NAME } from "../internal/constants"; +import { task } from "../internal/core/config/config-env"; +import { BuidlerError } from "../internal/core/errors"; +import { ERRORS, getErrorCode } from "../internal/core/errors-list"; +import { + isValidJsonRequest, + JsonRpcRequest +} from "../internal/core/providers/http"; +import { EthereumProvider } from "../types"; + +import { TASK_JSONRPC } from "./task-names"; + +const log = debug("buidler:core:tasks:jsonrpc"); + +interface ServerConfig { + hostname: string; + port: number; +} + +class Server { + private _config: ServerConfig; + private _ethereum: EthereumProvider; + + constructor(config: ServerConfig, ethereum: EthereumProvider) { + this._ethereum = ethereum; + this._config = config; + } + + public listen = (): Promise => { + return new Promise((resolve, reject) => { + const server = http.createServer(this._handleRequest); + + server.listen(this._config.port, this._config.hostname, () => { + console.log( + `Server running at http://${this._config.hostname}:${this._config.port}/` + ); + }); + + process.once("SIGINT", async () => { + log(`Stopping JSON-RPC server`); + + resolve(0); + }); + + process.once("uncaughtException", reject); + }); + }; + + private _handleRequest = async ( + req: IncomingMessage, + res: ServerResponse + ) => { + try { + const rpcReq: JsonRpcRequest = await this._readRequest(req); + + console.log(rpcReq.method); + + const rpcResp = await this._ethereum.send(rpcReq.method, rpcReq.params); + + res.statusCode = 200; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(rpcResp)); + } catch (error) { + this._handleError(error); + + res.statusCode = 500; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(error.message)); + } + }; + + private _readRequest = (req: IncomingMessage): Promise => { + return new Promise((resolve, reject) => { + const dataParts: Buffer[] = []; + + req + .on("data", (chunk: Buffer) => dataParts.push(chunk)) + .on("end", () => resolve(Buffer.concat(dataParts))) + .on("error", reject); + }).then( + (buf): JsonRpcRequest => { + const raw = buf.toString("UTF8"); + const json: any = JSON.parse(raw); + + if (!isValidJsonRequest(json)) { + throw new Error(`Invalid JSON-RPC request: ${raw}`); + } + + return json; + } + ); + }; + + private _handleError = (error: any) => { + let showStackTraces = process.argv.includes("--show-stack-traces"); + let isBuidlerError = false; + + if (BuidlerError.isBuidlerError(error)) { + isBuidlerError = true; + console.error(chalk.red(`Error ${error.message}`)); + } else if (error instanceof Error) { + console.error( + chalk.red(`An unexpected error occurred: ${error.message}`) + ); + showStackTraces = true; + } else { + console.error(chalk.red("An unexpected error occurred.")); + showStackTraces = true; + } + + console.log(""); + + if (showStackTraces) { + console.error(error.stack); + } else { + if (!isBuidlerError) { + console.error( + `If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug` + ); + } + + if (BuidlerError.isBuidlerError(error)) { + const link = `https://buidler.dev/${getErrorCode( + error.errorDescriptor + )}`; + + console.error( + `For more info go to ${link} or run ${BUIDLER_NAME} with --show-stack-traces` + ); + } else { + console.error( + `For more info run ${BUIDLER_NAME} with --show-stack-traces` + ); + } + } + }; +} + +export default function() { + task(TASK_JSONRPC, "Starts a buidler JSON-RPC server").setAction( + async (_, { ethereum }) => { + const hostname = "localhost"; + const port = 8545; + + log(`Starting JSON-RPC server on port ${port}`); + + try { + const srv = new Server({ hostname, port }, ethereum); + + process.exitCode = await srv.listen(); + } catch (error) { + throw new BuidlerError( + ERRORS.BUILTIN_TASKS.JSONRPC_SERVER_ERROR, + { + error: error.message + }, + error + ); + } + } + ); +} diff --git a/packages/buidler-core/src/builtin-tasks/task-names.ts b/packages/buidler-core/src/builtin-tasks/task-names.ts index 44e3df5e89..2153c96ee4 100644 --- a/packages/buidler-core/src/builtin-tasks/task-names.ts +++ b/packages/buidler-core/src/builtin-tasks/task-names.ts @@ -23,6 +23,8 @@ export const TASK_HELP = "help"; export const TASK_RUN = "run"; +export const TASK_JSONRPC = "jsonrpc"; + export const TASK_TEST = "test"; export const TASK_TEST_RUN_MOCHA_TESTS = "test:run-mocha-tests"; diff --git a/packages/buidler-core/src/internal/core/errors-list.ts b/packages/buidler-core/src/internal/core/errors-list.ts index 38660be06f..a323491e24 100644 --- a/packages/buidler-core/src/internal/core/errors-list.ts +++ b/packages/buidler-core/src/internal/core/errors-list.ts @@ -586,6 +586,18 @@ Please check Buidler's output for more details.` description: `Buidler flatten doesn't support cyclic dependencies. We recommend not using this kind of dependencies.` + }, + JSONRPC_SERVER_ERROR: { + number: 604, + message: "Error running JSON-RPC server: %error%", + title: "Error running JSON-RPC server", + description: `There was error while starting the JSON-RPC HTTP server.` + }, + JSONRPC_HANDLER_ERROR: { + number: 605, + message: "Error handling JSON-RPC request: %error%", + title: "Error handling JSON-RPC request", + description: `Handling an incoming JSON-RPC request resulted in an error.` } }, ARTIFACTS: { diff --git a/packages/buidler-core/src/internal/core/providers/http.ts b/packages/buidler-core/src/internal/core/providers/http.ts index 19a646afc9..2d6e5cc887 100644 --- a/packages/buidler-core/src/internal/core/providers/http.ts +++ b/packages/buidler-core/src/internal/core/providers/http.ts @@ -125,6 +125,26 @@ export class HttpProvider extends EventEmitter { } } +export function isValidJsonRequest(payload: any): boolean { + if (payload.jsonrpc !== "2.0") { + return false; + } + + if (typeof payload.id !== "number" && typeof payload.id !== "string") { + return false; + } + + if (typeof payload.method !== "string") { + return false; + } + + if (payload.params !== undefined && !Array.isArray(payload.params)) { + return false; + } + + return true; +} + export function isValidJsonResponse(payload: any) { if (payload.jsonrpc !== "2.0") { return false; diff --git a/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts b/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts index a848fd2b28..dbf4c28154 100644 --- a/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts +++ b/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts @@ -27,6 +27,10 @@ export default function() { path.join(__dirname, "..", "..", "..", "builtin-tasks", "run") ); + loadPluginFile( + path.join(__dirname, "..", "..", "..", "builtin-tasks", "jsonrpc") + ); + loadPluginFile( path.join(__dirname, "..", "..", "..", "builtin-tasks", "test") ); From 85e9cbd83cc526f2089ee3b7e609efe3f0d5cde6 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Thu, 6 Feb 2020 12:02:37 +0100 Subject: [PATCH 02/99] Updated body parsing and error handling logic. --- packages/buidler-core/package.json | 2 +- .../buidler-core/src/builtin-tasks/jsonrpc.ts | 79 +++++++++++++------ .../src/internal/core/providers/http.ts | 15 +++- 3 files changed, 70 insertions(+), 26 deletions(-) diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index bca6086a89..c0879078c4 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -76,7 +76,6 @@ "bip39": "^3.0.2", "chalk": "^2.4.2", "ci-info": "^2.0.0", - "concat-stream": "^2.0.0", "debug": "^4.1.1", "deepmerge": "^2.1.0", "download": "^7.1.0", @@ -99,6 +98,7 @@ "mocha": "^5.2.0", "node-fetch": "^2.6.0", "qs": "^6.7.0", + "raw-body": "^2.4.1", "semver": "^6.3.0", "solc": "0.5.15", "solidity-parser-antlr": "^0.4.2", diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts index b165c42253..944fcccfae 100644 --- a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts +++ b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts @@ -1,15 +1,19 @@ import chalk from "chalk"; import debug from "debug"; import http, { IncomingMessage, ServerResponse } from "http"; +import getRawBody from "raw-body"; -import { BUIDLER_NAME } from "../internal/constants"; +import { BUIDLER_NAME, BUIDLEREVM_NETWORK_NAME } from "../internal/constants"; import { task } from "../internal/core/config/config-env"; import { BuidlerError } from "../internal/core/errors"; import { ERRORS, getErrorCode } from "../internal/core/errors-list"; +import { createProvider } from "../internal/core/providers/construction"; import { isValidJsonRequest, - JsonRpcRequest + JsonRpcRequest, + JsonRpcResponse } from "../internal/core/providers/http"; +import { lazyObject } from "../internal/util/lazy"; import { EthereumProvider } from "../types"; import { TASK_JSONRPC } from "./task-names"; @@ -32,7 +36,7 @@ class Server { public listen = (): Promise => { return new Promise((resolve, reject) => { - const server = http.createServer(this._handleRequest); + const server = http.createServer(this._httpHandler); server.listen(this._config.port, this._config.hostname, () => { console.log( @@ -50,16 +54,20 @@ class Server { }); }; - private _handleRequest = async ( - req: IncomingMessage, - res: ServerResponse - ) => { + private _httpHandler = async (req: IncomingMessage, res: ServerResponse) => { + let rpcReq: JsonRpcRequest | undefined; try { - const rpcReq: JsonRpcRequest = await this._readRequest(req); + rpcReq = await this._readRequest(req); console.log(rpcReq.method); - const rpcResp = await this._ethereum.send(rpcReq.method, rpcReq.params); + const result = await this._ethereum.send(rpcReq.method, rpcReq.params); + + const rpcResp: JsonRpcResponse = { + jsonrpc: "2.0", + id: rpcReq.id, + result + }; res.statusCode = 200; res.setHeader("Content-Type", "application/json"); @@ -69,25 +77,33 @@ class Server { res.statusCode = 500; res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(error.message)); + + if (rpcReq === undefined) { + res.end(JSON.stringify(error.message)); + return; + } + + const rpcResp: JsonRpcResponse = { + jsonrpc: "2.0", + id: rpcReq.id, + error: { + code: -1, + message: error.message + } + }; + + res.end(JSON.stringify(rpcResp)); } }; private _readRequest = (req: IncomingMessage): Promise => { - return new Promise((resolve, reject) => { - const dataParts: Buffer[] = []; - - req - .on("data", (chunk: Buffer) => dataParts.push(chunk)) - .on("end", () => resolve(Buffer.concat(dataParts))) - .on("error", reject); - }).then( + return getRawBody(req).then( (buf): JsonRpcRequest => { - const raw = buf.toString("UTF8"); - const json: any = JSON.parse(raw); + const text = buf.toString(); + const json = JSON.parse(text); if (!isValidJsonRequest(json)) { - throw new Error(`Invalid JSON-RPC request: ${raw}`); + throw new Error(`Invalid JSON-RPC request: ${text}`); } return json; @@ -142,17 +158,36 @@ class Server { export default function() { task(TASK_JSONRPC, "Starts a buidler JSON-RPC server").setAction( - async (_, { ethereum }) => { + async (_, { config }) => { const hostname = "localhost"; const port = 8545; log(`Starting JSON-RPC server on port ${port}`); try { + log("Creating BuidlerRuntimeEnvironment"); + + const networkName = BUIDLEREVM_NETWORK_NAME; + const networkConfig = config.networks[networkName]; + + const ethereum = lazyObject(() => { + log(`Creating buidlerevm provider for JSON-RPC sever`); + return createProvider( + networkName, + networkConfig, + config.solc.version, + config.paths + ); + }); + const srv = new Server({ hostname, port }, ethereum); process.exitCode = await srv.listen(); } catch (error) { + if (BuidlerError.isBuidlerError(error)) { + throw error; + } + throw new BuidlerError( ERRORS.BUILTIN_TASKS.JSONRPC_SERVER_ERROR, { diff --git a/packages/buidler-core/src/internal/core/providers/http.ts b/packages/buidler-core/src/internal/core/providers/http.ts index 2d6e5cc887..b8fe359eb0 100644 --- a/packages/buidler-core/src/internal/core/providers/http.ts +++ b/packages/buidler-core/src/internal/core/providers/http.ts @@ -71,7 +71,7 @@ export class HttpProvider extends EventEmitter { private async _fetchJsonRpcResponse( request: JsonRpcRequest ): Promise { - const { default: fetch } = await import("node-fetch"); + const { default: fetch, FetchError } = await import("node-fetch"); try { const response = await fetch(this._url, { @@ -85,11 +85,20 @@ export class HttpProvider extends EventEmitter { } }); - const json = await response.json(); + const text = await response.text(); + let json: any; + try { + json = JSON.parse(text); + } catch (error) { + throw new FetchError( + `invalid json response body at ${this._url} reason: ${error.message}`, + "invalid-json" + ); + } if (!isValidJsonResponse(json)) { throw new BuidlerError(ERRORS.NETWORK.INVALID_JSON_RESPONSE, { - response: await response.text() + response: text }); } From a9ab1af8d0e72e6754ac78b7dbec11dd9315ca4b Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 7 Feb 2020 12:09:30 +0100 Subject: [PATCH 03/99] Refactored JSON-RPC server into manageable components. --- .../buidler-core/src/builtin-tasks/jsonrpc.ts | 195 +++--------------- .../internal/buidler-evm/jsonrpc/handler.ts | 166 +++++++++++++++ .../internal/buidler-evm/jsonrpc/server.ts | 46 +++++ .../src/internal/core/providers/http.ts | 8 +- 4 files changed, 248 insertions(+), 167 deletions(-) create mode 100644 packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts create mode 100644 packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts index 944fcccfae..0a64ef76f0 100644 --- a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts +++ b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts @@ -1,188 +1,53 @@ -import chalk from "chalk"; import debug from "debug"; -import http, { IncomingMessage, ServerResponse } from "http"; -import getRawBody from "raw-body"; -import { BUIDLER_NAME, BUIDLEREVM_NETWORK_NAME } from "../internal/constants"; +import { + JsonRpcServer, + JsonRpcServerConfig +} from "../internal/buidler-evm/jsonrpc/server"; +import { BUIDLEREVM_NETWORK_NAME } from "../internal/constants"; import { task } from "../internal/core/config/config-env"; import { BuidlerError } from "../internal/core/errors"; -import { ERRORS, getErrorCode } from "../internal/core/errors-list"; +import { ERRORS } from "../internal/core/errors-list"; import { createProvider } from "../internal/core/providers/construction"; -import { - isValidJsonRequest, - JsonRpcRequest, - JsonRpcResponse -} from "../internal/core/providers/http"; import { lazyObject } from "../internal/util/lazy"; -import { EthereumProvider } from "../types"; +import { EthereumProvider, ResolvedBuidlerConfig } from "../types"; import { TASK_JSONRPC } from "./task-names"; const log = debug("buidler:core:tasks:jsonrpc"); -interface ServerConfig { - hostname: string; - port: number; -} - -class Server { - private _config: ServerConfig; - private _ethereum: EthereumProvider; - - constructor(config: ServerConfig, ethereum: EthereumProvider) { - this._ethereum = ethereum; - this._config = config; - } - - public listen = (): Promise => { - return new Promise((resolve, reject) => { - const server = http.createServer(this._httpHandler); - - server.listen(this._config.port, this._config.hostname, () => { - console.log( - `Server running at http://${this._config.hostname}:${this._config.port}/` - ); - }); - - process.once("SIGINT", async () => { - log(`Stopping JSON-RPC server`); - - resolve(0); - }); - - process.once("uncaughtException", reject); - }); - }; - - private _httpHandler = async (req: IncomingMessage, res: ServerResponse) => { - let rpcReq: JsonRpcRequest | undefined; - try { - rpcReq = await this._readRequest(req); - - console.log(rpcReq.method); - - const result = await this._ethereum.send(rpcReq.method, rpcReq.params); - - const rpcResp: JsonRpcResponse = { - jsonrpc: "2.0", - id: rpcReq.id, - result - }; - - res.statusCode = 200; - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(rpcResp)); - } catch (error) { - this._handleError(error); - - res.statusCode = 500; - res.setHeader("Content-Type", "application/json"); - - if (rpcReq === undefined) { - res.end(JSON.stringify(error.message)); - return; - } - - const rpcResp: JsonRpcResponse = { - jsonrpc: "2.0", - id: rpcReq.id, - error: { - code: -1, - message: error.message - } - }; - - res.end(JSON.stringify(rpcResp)); - } - }; - - private _readRequest = (req: IncomingMessage): Promise => { - return getRawBody(req).then( - (buf): JsonRpcRequest => { - const text = buf.toString(); - const json = JSON.parse(text); - - if (!isValidJsonRequest(json)) { - throw new Error(`Invalid JSON-RPC request: ${text}`); - } - - return json; - } +function _createBuidlerRuntimeEnvironment( + config: ResolvedBuidlerConfig +): EthereumProvider { + log("Creating BuidlerRuntimeEnvironment"); + + const networkName = BUIDLEREVM_NETWORK_NAME; + const networkConfig = config.networks[networkName]; + + return lazyObject(() => { + log(`Creating buidlerevm provider for JSON-RPC sever`); + return createProvider( + networkName, + networkConfig, + config.solc.version, + config.paths ); - }; - - private _handleError = (error: any) => { - let showStackTraces = process.argv.includes("--show-stack-traces"); - let isBuidlerError = false; - - if (BuidlerError.isBuidlerError(error)) { - isBuidlerError = true; - console.error(chalk.red(`Error ${error.message}`)); - } else if (error instanceof Error) { - console.error( - chalk.red(`An unexpected error occurred: ${error.message}`) - ); - showStackTraces = true; - } else { - console.error(chalk.red("An unexpected error occurred.")); - showStackTraces = true; - } - - console.log(""); - - if (showStackTraces) { - console.error(error.stack); - } else { - if (!isBuidlerError) { - console.error( - `If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug` - ); - } - - if (BuidlerError.isBuidlerError(error)) { - const link = `https://buidler.dev/${getErrorCode( - error.errorDescriptor - )}`; - - console.error( - `For more info go to ${link} or run ${BUIDLER_NAME} with --show-stack-traces` - ); - } else { - console.error( - `For more info run ${BUIDLER_NAME} with --show-stack-traces` - ); - } - } - }; + }); } export default function() { task(TASK_JSONRPC, "Starts a buidler JSON-RPC server").setAction( async (_, { config }) => { - const hostname = "localhost"; - const port = 8545; - - log(`Starting JSON-RPC server on port ${port}`); - try { - log("Creating BuidlerRuntimeEnvironment"); - - const networkName = BUIDLEREVM_NETWORK_NAME; - const networkConfig = config.networks[networkName]; - - const ethereum = lazyObject(() => { - log(`Creating buidlerevm provider for JSON-RPC sever`); - return createProvider( - networkName, - networkConfig, - config.solc.version, - config.paths - ); - }); + const serverConfig: JsonRpcServerConfig = { + hostname: "localhost", + port: 8545, + ethereum: _createBuidlerRuntimeEnvironment(config) + }; - const srv = new Server({ hostname, port }, ethereum); + const server = new JsonRpcServer(serverConfig); - process.exitCode = await srv.listen(); + process.exitCode = await server.listen(); } catch (error) { if (BuidlerError.isBuidlerError(error)) { throw error; diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts new file mode 100644 index 0000000000..9f51463928 --- /dev/null +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -0,0 +1,166 @@ +import chalk from "chalk"; +import debug from "debug"; +import { IncomingMessage, ServerResponse } from "http"; +import getRawBody from "raw-body"; + +import { EthereumProvider } from "../../../types"; +import { BUIDLER_NAME } from "../../constants"; +import { BuidlerError } from "../../core/errors"; +import { ERRORS, getErrorCode } from "../../core/errors-list"; +import { + isValidJsonRequest, + isValidJsonResponse, + JsonRpcRequest, + JsonRpcResponse +} from "../../core/providers/http"; +import { + BuidlerEVMProviderError, + InternalError, + InvalidJsonInputError, + InvalidRequestError +} from "../provider/errors"; + +const log = debug("buidler:core:buidler-evm:jsonrpc"); + +export default class JsonRpcHandler { + private _ethereum: EthereumProvider; + + constructor(ethereum: EthereumProvider) { + this._ethereum = ethereum; + } + + public requestListener = async ( + req: IncomingMessage, + res: ServerResponse + ) => { + let rpcReq: JsonRpcRequest | undefined; + let rpcResp: JsonRpcResponse | undefined; + + try { + rpcReq = await this._readRequest(req); + + rpcResp = await this._handleRequest(rpcReq); + } catch (error) { + rpcResp = await this._handleError(error); + } + + if (rpcReq !== undefined) { + rpcResp.id = rpcReq.id; + } + + // Validate the RPC response. + if (!isValidJsonResponse(rpcResp)) { + rpcResp = await this._handleError(new InternalError("Internal error")); + } + + res.statusCode = 200; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(rpcResp)); + }; + + private _readRequest = async ( + req: IncomingMessage + ): Promise => { + const buf = await getRawBody(req); + const text = buf.toString(); + let json; + + try { + json = JSON.parse(text); + } catch (error) { + throw new InvalidJsonInputError(`Parse error: ${error.message}`); + } + + if (!isValidJsonRequest(json)) { + throw new InvalidRequestError("Invalid request"); + } + + return json; + }; + + private _handleRequest = async ( + req: JsonRpcRequest + ): Promise => { + console.log(req.method); + + const result = await this._ethereum.send(req.method, req.params); + + return { + jsonrpc: "2.0", + id: req.id, + result + }; + }; + + private _handleError = async (error: any): Promise => { + this._printError(error); + + const rpcResp: JsonRpcResponse = { + jsonrpc: "2.0", + error: { + code: error.code, + message: error.message + } + }; + + if (!(error instanceof BuidlerEVMProviderError)) { + rpcResp.error = new InternalError("Internal error"); + } + + return rpcResp; + }; + + private _printError = (error: any) => { + let showStackTraces = process.argv.includes("--show-stack-traces"); + let isBuidlerError = false; + + if (error instanceof BuidlerEVMProviderError) { + const wrappedError = new BuidlerError( + ERRORS.BUILTIN_TASKS.JSONRPC_HANDLER_ERROR, + { + error: error.message + }, + error + ); + + console.error(chalk.red(`Error ${wrappedError.message}`)); + } else if (BuidlerError.isBuidlerError(error)) { + isBuidlerError = true; + console.error(chalk.red(`Error ${error.message}`)); + } else if (error instanceof Error) { + console.error( + chalk.red(`An unexpected error occurred: ${error.message}`) + ); + showStackTraces = true; + } else { + console.error(chalk.red("An unexpected error occurred.")); + showStackTraces = true; + } + + console.log(""); + + if (showStackTraces) { + console.error(error.stack); + } else { + if (!isBuidlerError) { + console.error( + `If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug` + ); + } + + if (BuidlerError.isBuidlerError(error)) { + const link = `https://buidler.dev/${getErrorCode( + error.errorDescriptor + )}`; + + console.error( + `For more info go to ${link} or run ${BUIDLER_NAME} with --show-stack-traces` + ); + } else { + console.error( + `For more info run ${BUIDLER_NAME} with --show-stack-traces` + ); + } + } + }; +} diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts new file mode 100644 index 0000000000..921dea1e2f --- /dev/null +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -0,0 +1,46 @@ +import debug from "debug"; +import * as http from "http"; + +import { EthereumProvider } from "../../../types"; + +import JsonRpcHandler from "./handler"; + +const log = debug("buidler:core:buidler-evm:jsonrpc"); + +export interface JsonRpcServerConfig { + hostname: string; + port: number; + + ethereum: EthereumProvider; +} + +export class JsonRpcServer { + private _config: JsonRpcServerConfig; + + constructor(config: JsonRpcServerConfig) { + this._config = config; + } + + public listen = (): Promise => { + return new Promise((resolve, reject) => { + const { hostname, port, ethereum } = this._config; + + log(`Starting JSON-RPC server on port ${port}`); + + const handler = new JsonRpcHandler(ethereum); + const server = http.createServer(handler.requestListener); + + process.once("SIGINT", async () => { + log(`Stopping JSON-RPC server`); + + resolve(0); + }); + + process.once("uncaughtException", reject); + + server.listen(port, hostname, () => { + console.log(`Started JSON-RPC server at http://${hostname}:${port}/`); + }); + }); + }; +} diff --git a/packages/buidler-core/src/internal/core/providers/http.ts b/packages/buidler-core/src/internal/core/providers/http.ts index b8fe359eb0..6a18d93070 100644 --- a/packages/buidler-core/src/internal/core/providers/http.ts +++ b/packages/buidler-core/src/internal/core/providers/http.ts @@ -18,7 +18,7 @@ interface SuccessfulJsonRpcResponse { interface FailedJsonRpcResponse { jsonrpc: string; - id: number; + id?: number; error: { code: number; message: string; @@ -159,7 +159,11 @@ export function isValidJsonResponse(payload: any) { return false; } - if (typeof payload.id !== "number" && typeof payload.id !== "string") { + if ( + typeof payload.id !== "number" && + typeof payload.id !== "string" && + payload.id !== undefined + ) { return false; } From 66cfdc5ffd28cff9235aef7fb66506875074c366 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 7 Feb 2020 15:56:46 +0100 Subject: [PATCH 04/99] Addressed PR comments. + Added configuration parameters for http server host and port. + Renamed ethereum provider to provider. + Updated which errors are thrown when fetching JSON response. --- .../buidler-core/src/builtin-tasks/jsonrpc.ts | 31 +++++++++++----- .../internal/buidler-evm/jsonrpc/handler.ts | 8 ++-- .../internal/buidler-evm/jsonrpc/server.ts | 6 +-- .../src/internal/core/providers/http.ts | 37 +++++++++---------- 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts index 0a64ef76f0..b3653d0a22 100644 --- a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts +++ b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts @@ -5,7 +5,7 @@ import { JsonRpcServerConfig } from "../internal/buidler-evm/jsonrpc/server"; import { BUIDLEREVM_NETWORK_NAME } from "../internal/constants"; -import { task } from "../internal/core/config/config-env"; +import { task, types } from "../internal/core/config/config-env"; import { BuidlerError } from "../internal/core/errors"; import { ERRORS } from "../internal/core/errors-list"; import { createProvider } from "../internal/core/providers/construction"; @@ -16,10 +16,10 @@ import { TASK_JSONRPC } from "./task-names"; const log = debug("buidler:core:tasks:jsonrpc"); -function _createBuidlerRuntimeEnvironment( +function _createBuidlerEVMProvider( config: ResolvedBuidlerConfig ): EthereumProvider { - log("Creating BuidlerRuntimeEnvironment"); + log("Creating BuidlerEVM Provider"); const networkName = BUIDLEREVM_NETWORK_NAME; const networkConfig = config.networks[networkName]; @@ -36,13 +36,25 @@ function _createBuidlerRuntimeEnvironment( } export default function() { - task(TASK_JSONRPC, "Starts a buidler JSON-RPC server").setAction( - async (_, { config }) => { + task(TASK_JSONRPC, "Starts a buidler JSON-RPC server") + .addOptionalParam( + "hostname", + "The host to which to bind to for new connections", + "localhost", + types.string + ) + .addOptionalParam( + "port", + "The port on which to listen for new connections", + 8545, + types.int + ) + .setAction(async ({ hostname, port }, { config }) => { try { const serverConfig: JsonRpcServerConfig = { - hostname: "localhost", - port: 8545, - ethereum: _createBuidlerRuntimeEnvironment(config) + hostname, + port, + provider: _createBuidlerEVMProvider(config) }; const server = new JsonRpcServer(serverConfig); @@ -61,6 +73,5 @@ export default function() { error ); } - } - ); + }); } diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 9f51463928..497fc27ba2 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -23,10 +23,10 @@ import { const log = debug("buidler:core:buidler-evm:jsonrpc"); export default class JsonRpcHandler { - private _ethereum: EthereumProvider; + private _provider: EthereumProvider; - constructor(ethereum: EthereumProvider) { - this._ethereum = ethereum; + constructor(provider: EthereumProvider) { + this._provider = provider; } public requestListener = async ( @@ -83,7 +83,7 @@ export default class JsonRpcHandler { ): Promise => { console.log(req.method); - const result = await this._ethereum.send(req.method, req.params); + const result = await this._provider.send(req.method, req.params); return { jsonrpc: "2.0", diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 921dea1e2f..342e7db2f9 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -11,7 +11,7 @@ export interface JsonRpcServerConfig { hostname: string; port: number; - ethereum: EthereumProvider; + provider: EthereumProvider; } export class JsonRpcServer { @@ -23,11 +23,11 @@ export class JsonRpcServer { public listen = (): Promise => { return new Promise((resolve, reject) => { - const { hostname, port, ethereum } = this._config; + const { hostname, port, provider } = this._config; log(`Starting JSON-RPC server on port ${port}`); - const handler = new JsonRpcHandler(ethereum); + const handler = new JsonRpcHandler(provider); const server = http.createServer(handler.requestListener); process.once("SIGINT", async () => { diff --git a/packages/buidler-core/src/internal/core/providers/http.ts b/packages/buidler-core/src/internal/core/providers/http.ts index 6a18d93070..f004683602 100644 --- a/packages/buidler-core/src/internal/core/providers/http.ts +++ b/packages/buidler-core/src/internal/core/providers/http.ts @@ -71,7 +71,7 @@ export class HttpProvider extends EventEmitter { private async _fetchJsonRpcResponse( request: JsonRpcRequest ): Promise { - const { default: fetch, FetchError } = await import("node-fetch"); + const { default: fetch } = await import("node-fetch"); try { const response = await fetch(this._url, { @@ -85,24 +85,7 @@ export class HttpProvider extends EventEmitter { } }); - const text = await response.text(); - let json: any; - try { - json = JSON.parse(text); - } catch (error) { - throw new FetchError( - `invalid json response body at ${this._url} reason: ${error.message}`, - "invalid-json" - ); - } - - if (!isValidJsonResponse(json)) { - throw new BuidlerError(ERRORS.NETWORK.INVALID_JSON_RESPONSE, { - response: text - }); - } - - return json; + return parseJsonResponse(await response.text()); } catch (error) { if (error.code === "ECONNREFUSED") { throw new BuidlerError( @@ -134,6 +117,22 @@ export class HttpProvider extends EventEmitter { } } +export function parseJsonResponse(text: string): JsonRpcResponse { + try { + const json = JSON.parse(text); + + if (!isValidJsonResponse(json)) { + throw new Error(); + } + + return json; + } catch (error) { + throw new BuidlerError(ERRORS.NETWORK.INVALID_JSON_RESPONSE, { + response: text + }); + } +} + export function isValidJsonRequest(payload: any): boolean { if (payload.jsonrpc !== "2.0") { return false; From 457d18fe0d9eaf0a32828ffb3348f97483d67c74 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 7 Feb 2020 16:31:18 +0100 Subject: [PATCH 05/99] Addressed PR comments. * Added configuration parameters for http server host and port. * Renamed ethereum provider to provider. * Updated which errors are thrown when fetching JSON response. * Removed unneeded exception handling. --- .../internal/buidler-evm/jsonrpc/handler.ts | 2 +- .../internal/buidler-evm/jsonrpc/server.ts | 2 - .../src/internal/core/providers/http.ts | 97 ++----------------- .../buidler-core/src/internal/util/jsonrpc.ts | 93 ++++++++++++++++++ .../providers/http.ts => util/jsonrpc.ts} | 4 +- 5 files changed, 102 insertions(+), 96 deletions(-) create mode 100644 packages/buidler-core/src/internal/util/jsonrpc.ts rename packages/buidler-core/test/internal/{core/providers/http.ts => util/jsonrpc.ts} (96%) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 497fc27ba2..16682993f9 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -12,7 +12,7 @@ import { isValidJsonResponse, JsonRpcRequest, JsonRpcResponse -} from "../../core/providers/http"; +} from "../../util/jsonrpc"; import { BuidlerEVMProviderError, InternalError, diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 342e7db2f9..a10f82d57c 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -36,8 +36,6 @@ export class JsonRpcServer { resolve(0); }); - process.once("uncaughtException", reject); - server.listen(port, hostname, () => { console.log(`Started JSON-RPC server at http://${hostname}:${port}/`); }); diff --git a/packages/buidler-core/src/internal/core/providers/http.ts b/packages/buidler-core/src/internal/core/providers/http.ts index f004683602..aa2f7d95fe 100644 --- a/packages/buidler-core/src/internal/core/providers/http.ts +++ b/packages/buidler-core/src/internal/core/providers/http.ts @@ -1,38 +1,19 @@ import { EventEmitter } from "events"; +import { + FailedJsonRpcResponse, + JsonRpcRequest, + JsonRpcResponse, + parseJsonResponse +} from "../../util/jsonrpc"; import { BuidlerError } from "../errors"; import { ERRORS } from "../errors-list"; -export interface JsonRpcRequest { - jsonrpc: string; - method: string; - params: any[]; - id: number; -} - -interface SuccessfulJsonRpcResponse { - jsonrpc: string; - id: number; - result: any; -} - -interface FailedJsonRpcResponse { - jsonrpc: string; - id?: number; - error: { - code: number; - message: string; - data?: any; - }; -} - interface ProviderError extends Error { code?: number; data?: any; } -export type JsonRpcResponse = SuccessfulJsonRpcResponse | FailedJsonRpcResponse; - function isErrorResponse(response: any): response is FailedJsonRpcResponse { return typeof response.error !== "undefined"; } @@ -116,69 +97,3 @@ export class HttpProvider extends EventEmitter { }; } } - -export function parseJsonResponse(text: string): JsonRpcResponse { - try { - const json = JSON.parse(text); - - if (!isValidJsonResponse(json)) { - throw new Error(); - } - - return json; - } catch (error) { - throw new BuidlerError(ERRORS.NETWORK.INVALID_JSON_RESPONSE, { - response: text - }); - } -} - -export function isValidJsonRequest(payload: any): boolean { - if (payload.jsonrpc !== "2.0") { - return false; - } - - if (typeof payload.id !== "number" && typeof payload.id !== "string") { - return false; - } - - if (typeof payload.method !== "string") { - return false; - } - - if (payload.params !== undefined && !Array.isArray(payload.params)) { - return false; - } - - return true; -} - -export function isValidJsonResponse(payload: any) { - if (payload.jsonrpc !== "2.0") { - return false; - } - - if ( - typeof payload.id !== "number" && - typeof payload.id !== "string" && - payload.id !== undefined - ) { - return false; - } - - if (payload.result === undefined && payload.error === undefined) { - return false; - } - - if (payload.error !== undefined) { - if (typeof payload.error.code !== "number") { - return false; - } - - if (typeof payload.error.message !== "string") { - return false; - } - } - - return true; -} diff --git a/packages/buidler-core/src/internal/util/jsonrpc.ts b/packages/buidler-core/src/internal/util/jsonrpc.ts new file mode 100644 index 0000000000..8118cdb934 --- /dev/null +++ b/packages/buidler-core/src/internal/util/jsonrpc.ts @@ -0,0 +1,93 @@ +import { BuidlerError } from "../core/errors"; +import { ERRORS } from "../core/errors-list"; + +export interface JsonRpcRequest { + jsonrpc: string; + method: string; + params: any[]; + id: number; +} + +interface SuccessfulJsonRpcResponse { + jsonrpc: string; + id: number; + result: any; +} + +export interface FailedJsonRpcResponse { + jsonrpc: string; + id?: number; + error: { + code: number; + message: string; + data?: any; + }; +} + +export type JsonRpcResponse = SuccessfulJsonRpcResponse | FailedJsonRpcResponse; + +export function parseJsonResponse(text: string): JsonRpcResponse { + try { + const json = JSON.parse(text); + + if (!isValidJsonResponse(json)) { + throw new Error(); + } + + return json; + } catch (error) { + throw new BuidlerError(ERRORS.NETWORK.INVALID_JSON_RESPONSE, { + response: text + }); + } +} + +export function isValidJsonRequest(payload: any): boolean { + if (payload.jsonrpc !== "2.0") { + return false; + } + + if (typeof payload.id !== "number" && typeof payload.id !== "string") { + return false; + } + + if (typeof payload.method !== "string") { + return false; + } + + if (payload.params !== undefined && !Array.isArray(payload.params)) { + return false; + } + + return true; +} + +export function isValidJsonResponse(payload: any) { + if (payload.jsonrpc !== "2.0") { + return false; + } + + if ( + typeof payload.id !== "number" && + typeof payload.id !== "string" && + payload.id !== undefined + ) { + return false; + } + + if (payload.result === undefined && payload.error === undefined) { + return false; + } + + if (payload.error !== undefined) { + if (typeof payload.error.code !== "number") { + return false; + } + + if (typeof payload.error.message !== "string") { + return false; + } + } + + return true; +} diff --git a/packages/buidler-core/test/internal/core/providers/http.ts b/packages/buidler-core/test/internal/util/jsonrpc.ts similarity index 96% rename from packages/buidler-core/test/internal/core/providers/http.ts rename to packages/buidler-core/test/internal/util/jsonrpc.ts index e0fe6c5623..7522bcc2dc 100644 --- a/packages/buidler-core/test/internal/core/providers/http.ts +++ b/packages/buidler-core/test/internal/util/jsonrpc.ts @@ -1,8 +1,8 @@ import { assert } from "chai"; -import { isValidJsonResponse } from "../../../../src/internal/core/providers/http"; +import { isValidJsonResponse } from "../../../internal/util/jsonrpc"; -describe("HttpProvider", function() { +describe("JSON-RPC", function() { describe("JSON-RPC response validation", function() { describe("Invalid responses", function() { it("Should validate the jsonrpc field", function() { From 3ddad21afc8b55e8a18a10c3a30d7a21db629012 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 7 Feb 2020 16:35:31 +0100 Subject: [PATCH 06/99] Included reading the buffer inside of try/catch. --- .../buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 16682993f9..d62e8bdb76 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -61,11 +61,12 @@ export default class JsonRpcHandler { private _readRequest = async ( req: IncomingMessage ): Promise => { - const buf = await getRawBody(req); - const text = buf.toString(); let json; try { + const buf = await getRawBody(req); + const text = buf.toString(); + json = JSON.parse(text); } catch (error) { throw new InvalidJsonInputError(`Parse error: ${error.message}`); From 31090f381794128161c2b9a8edb0bfdeed3c6f2c Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Tue, 11 Feb 2020 14:51:18 +0100 Subject: [PATCH 07/99] Refactored tests to support multiple providers under test. Updated handler to correct failing tests. --- .../internal/buidler-evm/jsonrpc/handler.ts | 59 +- .../internal/buidler-evm/jsonrpc/server.ts | 52 +- .../internal/buidler-evm/provider/errors.ts | 20 + .../src/internal/buidler-evm/provider/node.ts | 8 +- .../stack-traces/solidity-errors.ts | 4 +- .../buidler-evm/helpers/assertions.ts | 38 +- .../buidler-evm/helpers/useProvider.ts | 49 +- .../buidler-evm/provider/modules/eth.ts | 3141 +++++++++-------- .../buidler-evm/provider/modules/evm.ts | 880 ++--- .../buidler-evm/provider/modules/net.ts | 48 +- .../buidler-evm/provider/modules/web3.ts | 44 +- .../test/internal/util/jsonrpc.ts | 5 +- 12 files changed, 2235 insertions(+), 2113 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index d62e8bdb76..fb90619f5b 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -4,9 +4,8 @@ import { IncomingMessage, ServerResponse } from "http"; import getRawBody from "raw-body"; import { EthereumProvider } from "../../../types"; -import { BUIDLER_NAME } from "../../constants"; import { BuidlerError } from "../../core/errors"; -import { ERRORS, getErrorCode } from "../../core/errors-list"; +import { ERRORS } from "../../core/errors-list"; import { isValidJsonRequest, isValidJsonResponse, @@ -44,15 +43,16 @@ export default class JsonRpcHandler { rpcResp = await this._handleError(error); } - if (rpcReq !== undefined) { - rpcResp.id = rpcReq.id; - } - // Validate the RPC response. if (!isValidJsonResponse(rpcResp)) { + // Malformed response coming from the provider, report to user as an internal error. rpcResp = await this._handleError(new InternalError("Internal error")); } + if (rpcReq !== undefined) { + rpcResp.id = rpcReq.id; + } + res.statusCode = 200; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(rpcResp)); @@ -96,26 +96,24 @@ export default class JsonRpcHandler { private _handleError = async (error: any): Promise => { this._printError(error); - const rpcResp: JsonRpcResponse = { + // In case of non-buidler error, treat it as internal and associate the appropriate error code. + if (!BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { + error = new InternalError(error.message); + } + + return { jsonrpc: "2.0", error: { code: error.code, message: error.message } }; - - if (!(error instanceof BuidlerEVMProviderError)) { - rpcResp.error = new InternalError("Internal error"); - } - - return rpcResp; }; private _printError = (error: any) => { - let showStackTraces = process.argv.includes("--show-stack-traces"); - let isBuidlerError = false; - - if (error instanceof BuidlerEVMProviderError) { + if (BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { + // Report the error to console in the format of other BuidlerErrors (wrappedError.message), + // while preserving the stack from the originating error (error.stack). const wrappedError = new BuidlerError( ERRORS.BUILTIN_TASKS.JSONRPC_HANDLER_ERROR, { @@ -126,42 +124,17 @@ export default class JsonRpcHandler { console.error(chalk.red(`Error ${wrappedError.message}`)); } else if (BuidlerError.isBuidlerError(error)) { - isBuidlerError = true; console.error(chalk.red(`Error ${error.message}`)); } else if (error instanceof Error) { console.error( chalk.red(`An unexpected error occurred: ${error.message}`) ); - showStackTraces = true; } else { console.error(chalk.red("An unexpected error occurred.")); - showStackTraces = true; } console.log(""); - if (showStackTraces) { - console.error(error.stack); - } else { - if (!isBuidlerError) { - console.error( - `If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug` - ); - } - - if (BuidlerError.isBuidlerError(error)) { - const link = `https://buidler.dev/${getErrorCode( - error.errorDescriptor - )}`; - - console.error( - `For more info go to ${link} or run ${BUIDLER_NAME} with --show-stack-traces` - ); - } else { - console.error( - `For more info run ${BUIDLER_NAME} with --show-stack-traces` - ); - } - } + console.error(error.stack); }; } diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index a10f82d57c..31eb938d4d 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -1,7 +1,8 @@ import debug from "debug"; -import * as http from "http"; +import http, { Server } from "http"; import { EthereumProvider } from "../../../types"; +import { HttpProvider } from "../../core/providers/http"; import JsonRpcHandler from "./handler"; @@ -16,29 +17,58 @@ export interface JsonRpcServerConfig { export class JsonRpcServer { private _config: JsonRpcServerConfig; + private _server: Server; constructor(config: JsonRpcServerConfig) { this._config = config; - } - public listen = (): Promise => { - return new Promise((resolve, reject) => { - const { hostname, port, provider } = this._config; + const handler = new JsonRpcHandler(config.provider); - log(`Starting JSON-RPC server on port ${port}`); + this._server = http.createServer(handler.requestListener); + } - const handler = new JsonRpcHandler(provider); - const server = http.createServer(handler.requestListener); + public getProvider = (name = "json-rpc"): EthereumProvider => { + const { address, port } = this._server.address(); + + return new HttpProvider(`http://${address}:${port}/`, name); + }; + public listen = (): Promise => { + return new Promise(async resolve => { process.once("SIGINT", async () => { - log(`Stopping JSON-RPC server`); + await this.close(); resolve(0); }); - server.listen(port, hostname, () => { - console.log(`Started JSON-RPC server at http://${hostname}:${port}/`); + await this.start(); + }); + }; + + public start = async () => { + return new Promise(resolve => { + log(`Starting JSON-RPC server on port ${this._config.port}`); + this._server.listen(this._config.port, this._config.hostname, () => { + // We get the address and port directly from the server in order to handle random port allocation with `0`. + const { address, port } = this._server.address(); + + console.log(`Started JSON-RPC server at http://${address}:${port}/`); + + resolve(); + }); + }); + }; + + public close = async () => { + return new Promise(resolve => { + this._server.on("close", () => { + log("JSON-RPC server closed"); + + resolve(); }); + + log("Closing JSON-RPC server"); + this._server.close(); }); }; } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts b/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts index 439ee95ec4..368c600f18 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts @@ -16,8 +16,22 @@ // -32003 Transaction rejected Transaction creation failed non-standard export class BuidlerEVMProviderError extends Error { + public static isBuidlerEVMProviderError( + other: any + ): other is BuidlerEVMProviderError { + return ( + other !== undefined && + other !== null && + other._isBuidlerEVMProviderError === true + ); + } + + private readonly _isBuidlerEVMProviderError: boolean; + constructor(message: string, public readonly code: number) { super(message); + + this._isBuidlerEVMProviderError = true; } } @@ -57,6 +71,12 @@ export class InvalidInputError extends BuidlerEVMProviderError { } } +export class TransactionExecutionError extends BuidlerEVMProviderError { + constructor(message: string) { + super(message, -32003); + } +} + export class MethodNotSupportedError extends BuidlerEVMProviderError { constructor(message: string) { super(message, -32004); diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index a4d84ff2be..faca4d07f0 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -38,7 +38,11 @@ import { SolidityTracer } from "../stack-traces/solidityTracer"; import { VMTracer } from "../stack-traces/vm-tracer"; import { Blockchain } from "./blockchain"; -import { InternalError, InvalidInputError } from "./errors"; +import { + InternalError, + InvalidInputError, + TransactionExecutionError +} from "./errors"; import { getCurrentTimestamp } from "./utils"; const log = debug("buidler:core:buidler-evm:node"); @@ -77,8 +81,6 @@ export interface TransactionParams { nonce: BN; } -export class TransactionExecutionError extends Error {} - export interface TxBlockResult { receipt: TxReceipt; createAddresses: Buffer | undefined; diff --git a/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts b/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts index c7c96a6b57..b5e5908cc2 100644 --- a/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts +++ b/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts @@ -1,6 +1,8 @@ import { bufferToHex } from "ethereumjs-util"; import { inspect } from "util"; +import { TransactionExecutionError } from "../provider/errors"; + import { decodeRevertReason } from "./revert-reasons"; import { CONSTRUCTOR_FUNCTION_NAME, @@ -237,7 +239,7 @@ function getMessageFromLastStackTraceEntry( } } -export class SolidityError extends Error { +export class SolidityError extends TransactionExecutionError { public readonly stackTrace: SolidityStackTrace; constructor(message: string, stackTrace: SolidityStackTrace) { diff --git a/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts b/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts index 287d0270d6..9ae3e25084 100644 --- a/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts +++ b/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts @@ -1,12 +1,6 @@ import { assert } from "chai"; import { BN, bufferToHex } from "ethereumjs-util"; -import { - BuidlerEVMProviderError, - InvalidArgumentsError, - InvalidInputError, - MethodNotSupportedError -} from "../../../../src/internal/buidler-evm/provider/errors"; import { rpcQuantity, RpcTransactionRequestInput @@ -17,15 +11,13 @@ import { RpcTransactionOutput, RpcTransactionReceiptOutput } from "../../../../src/internal/buidler-evm/provider/output"; -import { BuidlerEVMProvider } from "../../../../src/internal/buidler-evm/provider/provider"; import { EthereumProvider } from "../../../../src/types"; export async function assertBuidlerEVMProviderError( - provider: BuidlerEVMProvider, + provider: EthereumProvider, method: string, params: any[] = [], message?: string, - exceptionType?: typeof BuidlerEVMProviderError, code?: number ) { let res: any; @@ -36,12 +28,6 @@ export async function assertBuidlerEVMProviderError( assert.equal(error.code, code); } - if (exceptionType !== undefined) { - assert.instanceOf(error, exceptionType); - } else { - assert.instanceOf(error, BuidlerEVMProviderError); - } - if (message !== undefined) { assert.include(error.message, message); } @@ -50,12 +36,12 @@ export async function assertBuidlerEVMProviderError( } assert.fail( - `Method ${method} should have thrown ${exceptionType} but returned ${res}` + `Method ${method} should have thrown [${code}] ${message} but returned ${res}` ); } export async function assertNotSupported( - provider: BuidlerEVMProvider, + provider: EthereumProvider, method: string ) { return assertBuidlerEVMProviderError( @@ -63,13 +49,12 @@ export async function assertNotSupported( method, [], `Method ${method} is not supported`, - MethodNotSupportedError, -32004 ); } export async function assertInvalidArgumentsError( - provider: BuidlerEVMProvider, + provider: EthereumProvider, method: string, params: any[] = [], message?: string @@ -79,13 +64,12 @@ export async function assertInvalidArgumentsError( method, params, message, - InvalidArgumentsError, -32602 ); } export async function assertInvalidInputError( - provider: BuidlerEVMProvider, + provider: EthereumProvider, method: string, params: any[] = [], message?: string @@ -95,7 +79,6 @@ export async function assertInvalidInputError( method, params, message, - InvalidInputError, -32000 ); } @@ -109,7 +92,7 @@ export function assertQuantity( } export async function assertNodeBalances( - provider: BuidlerEVMProvider, + provider: EthereumProvider, expectedBalances: Array ) { const accounts: string[] = await provider.send("eth_accounts"); @@ -122,10 +105,9 @@ export async function assertNodeBalances( } export async function assertTransactionFailure( - provider: BuidlerEVMProvider, + provider: EthereumProvider, txData: RpcTransactionRequestInput, - message?: string, - exceptionType?: typeof BuidlerEVMProviderError + message?: string ) { try { await provider.send("eth_sendTransaction", [txData]); @@ -134,10 +116,6 @@ export async function assertTransactionFailure( assert.include(error.message, message); } - if (exceptionType !== undefined) { - assert.instanceOf(error, exceptionType); - } - return; } diff --git a/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts b/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts index 88462b5e53..204f50d570 100644 --- a/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts +++ b/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts @@ -1,16 +1,41 @@ import Common from "ethereumjs-common"; import { BN } from "ethereumjs-util"; +import { JsonRpcServer } from "../../../../internal/buidler-evm/jsonrpc/server"; import { BuidlerNode } from "../../../../src/internal/buidler-evm/provider/node"; import { BuidlerEVMProvider } from "../../../../src/internal/buidler-evm/provider/provider"; +import { EthereumProvider } from "../../../../types"; declare module "mocha" { interface Context { - provider: BuidlerEVMProvider; + provider: EthereumProvider; common: Common; } } +export const PROVIDERS = [ + { + name: "BuidlerEVM", + useProvider: () => { + useProvider(); + } + }, + { + name: "JSON-RPC", + useProvider: () => { + useProvider( + DEFAULT_HARDFORK, + DEFAULT_NETWORK_NAME, + DEFAULT_CHAIN_ID, + DEFAULT_NETWORK_ID, + DEFAULT_BLOCK_GAS_LIMIT, + DEFAULT_ACCOUNTS, + true + ); + } + } +]; + export const DEFAULT_HARDFORK = "istanbul"; export const DEFAULT_NETWORK_NAME = "TestNet"; export const DEFAULT_CHAIN_ID = 123; @@ -28,6 +53,7 @@ export const DEFAULT_ACCOUNTS = [ balance: new BN(10).pow(new BN(18)) } ]; +export const DEFAULT_USE_JSON_RPC = false; export function useProvider( hardfork = DEFAULT_HARDFORK, @@ -35,7 +61,8 @@ export function useProvider( chainId = DEFAULT_CHAIN_ID, networkId = DEFAULT_NETWORK_ID, blockGasLimit = DEFAULT_BLOCK_GAS_LIMIT, - accounts = DEFAULT_ACCOUNTS + accounts = DEFAULT_ACCOUNTS, + useJsonRpc = DEFAULT_USE_JSON_RPC ) { beforeEach("Initialize provider", async function() { // We create two Nodes here, and don't use this one. @@ -62,10 +89,28 @@ export function useProvider( true, accounts ); + + if (useJsonRpc) { + this.server = new JsonRpcServer({ + port: 0, + hostname: "localhost", + provider: this.provider + }); + + await this.server.start(); + + this.provider = this.server.getProvider(); + } }); afterEach("Remove provider", async function() { delete this.common; delete this.provider; + + if (useJsonRpc) { + await this.server.close(); + + delete this.server; + } }); } diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index c33f79d438..a257a7c73e 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -9,7 +9,6 @@ import { zeroAddress } from "ethereumjs-util"; -import { InvalidInputError } from "../../../../../src/internal/buidler-evm/provider/errors"; import { COINBASE_ADDRESS, TransactionParams @@ -20,7 +19,7 @@ import { RpcTransactionOutput, RpcTransactionReceiptOutput } from "../../../../../src/internal/buidler-evm/provider/output"; -import { BuidlerEVMProvider } from "../../../../../src/internal/buidler-evm/provider/provider"; +import { EthereumProvider } from "../../../../../types"; import { assertInvalidInputError, assertNodeBalances, @@ -37,7 +36,7 @@ import { DEFAULT_ACCOUNTS, DEFAULT_BLOCK_GAS_LIMIT, DEFAULT_CHAIN_ID, - useProvider + PROVIDERS } from "../../helpers/useProvider"; const DEFAULT_ACCOUNTS_ADDRESSES = DEFAULT_ACCOUNTS.map(account => @@ -51,7 +50,7 @@ const DEFAULT_ACCOUNTS_BALANCES = DEFAULT_ACCOUNTS.map( const PRECOMPILES_COUNT = 8; async function sendTxToZeroAddress( - provider: BuidlerEVMProvider + provider: EthereumProvider ): Promise { const accounts = await provider.send("eth_accounts"); @@ -67,7 +66,7 @@ async function sendTxToZeroAddress( } async function deployContract( - provider: BuidlerEVMProvider, + provider: EthereumProvider, deploymentCode: string ) { const hash = await provider.send("eth_sendTransaction", [ @@ -86,7 +85,7 @@ async function deployContract( } async function sendTransactionFromTxParams( - provider: BuidlerEVMProvider, + provider: EthereumProvider, txParams: TransactionParams ) { return provider.send("eth_sendTransaction", [ @@ -120,1712 +119,1740 @@ function getSignedTxHash( } describe("Eth module", function() { - setCWD(); - useProvider(); + PROVIDERS.forEach(provider => { + describe(`Provider ${provider.name}`, function() { + setCWD(); + provider.useProvider(); - describe("eth_accounts", async function() { - it("should return the genesis accounts in lower case", async function() { - const accounts = await this.provider.send("eth_accounts"); + describe("eth_accounts", async function() { + it("should return the genesis accounts in lower case", async function() { + const accounts = await this.provider.send("eth_accounts"); - assert.deepEqual(accounts, DEFAULT_ACCOUNTS_ADDRESSES); - }); - }); - - describe("eth_blockNumber", async function() { - it("should return the current block number as QUANTITY", async function() { - let blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 0); - - await sendTxToZeroAddress(this.provider); - - blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 1); - - await sendTxToZeroAddress(this.provider); - - blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 2); - - await sendTxToZeroAddress(this.provider); + assert.deepEqual(accounts, DEFAULT_ACCOUNTS_ADDRESSES); + }); + }); - blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 3); - }); + describe("eth_blockNumber", async function() { + it("should return the current block number as QUANTITY", async function() { + let blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 0); - it("Shouldn increase if a transaction gets to execute and fails", async function() { - let blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 0); + await sendTxToZeroAddress(this.provider); - try { - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: "0x0000000000000000000000000000000000000001", - gas: numberToRpcQuantity(21000), // Address 1 is a precompile, so this will OOG - gasPrice: numberToRpcQuantity(1) - } - ]); + blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 1); - assert.fail("Tx should have failed"); - } catch (e) { - assert.notInclude(e.message, "Tx should have failed"); - } + await sendTxToZeroAddress(this.provider); - blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 1); - }); + blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 2); - it("Shouldn't increase if a call is made", async function() { - let blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 0); - - await this.provider.send("eth_call", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: "0x0000000000000000000000000000000000000000", - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - } - ]); - - blockNumber = await this.provider.send("eth_blockNumber"); - assertQuantity(blockNumber, 0); - }); - }); + await sendTxToZeroAddress(this.provider); - describe("eth_call", async function() { - it("Should return the value returned by the contract", async function() { - const contractAddress = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` - ); - - const result = await this.provider.send("eth_call", [ - { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i } - ]); - - assert.equal( - result, - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - await this.provider.send("eth_sendTransaction", [ - { - to: contractAddress, - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: `${EXAMPLE_CONTRACT.selectors.modifiesState}000000000000000000000000000000000000000000000000000000000000000a` - } - ]); - - const result2 = await this.provider.send("eth_call", [ - { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i } - ]); - - assert.equal( - result2, - "0x000000000000000000000000000000000000000000000000000000000000000a" - ); - }); + blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 3); + }); - it("Should return the value returned by the contract using an unknown account as from", async function() { - const from = "0x1234567890123456789012345678901234567890"; - - const contractAddress = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` - ); - - const result = await this.provider.send("eth_call", [ - { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i, from } - ]); - - assert.equal( - result, - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - await this.provider.send("eth_sendTransaction", [ - { - to: contractAddress, - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: `${EXAMPLE_CONTRACT.selectors.modifiesState}000000000000000000000000000000000000000000000000000000000000000a` - } - ]); - - const result2 = await this.provider.send("eth_call", [ - { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i, from } - ]); - - assert.equal( - result2, - "0x000000000000000000000000000000000000000000000000000000000000000a" - ); - }); + it("Shouldn't increase if a transaction gets to execute and fails", async function() { + let blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 0); - it("Should return an empty buffer if called an non-contract account", async function() { - const result = await this.provider.send("eth_call", [ - { - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: EXAMPLE_CONTRACT.selectors.i - } - ]); + try { + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: "0x0000000000000000000000000000000000000001", + gas: numberToRpcQuantity(21000), // Address 1 is a precompile, so this will OOG + gasPrice: numberToRpcQuantity(1) + } + ]); - assert.equal(result, "0x"); - }); - }); + assert.fail("Tx should have failed"); + } catch (e) { + assert.notInclude(e.message, "Tx should have failed"); + } - describe("eth_chainId", async function() { - it("should return the chain id as QUANTITY", async function() { - assertQuantity( - await this.provider.send("eth_chainId"), - this.common.chainId() - ); - }); - }); + blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 1); + }); - describe("eth_coinbase", async function() { - it("should return the the hardcoded coinbase address", async function() { - assert.equal( - await this.provider.send("eth_coinbase"), - bufferToHex(COINBASE_ADDRESS) - ); - }); - }); + it("Shouldn't increase if a call is made", async function() { + let blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 0); + + await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: "0x0000000000000000000000000000000000000000", + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + } + ]); + + blockNumber = await this.provider.send("eth_blockNumber"); + assertQuantity(blockNumber, 0); + }); + }); - describe("eth_compileLLL", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_compileLLL"); - }); - }); + describe("eth_call", async function() { + it("Should return the value returned by the contract", async function() { + const contractAddress = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); - describe("eth_compileSerpent", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_compileSerpent"); - }); - }); + const result = await this.provider.send("eth_call", [ + { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i } + ]); - describe("eth_compileSolidity", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_compileSolidity"); - }); - }); + assert.equal( + result, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); - describe("eth_estimateGas", async function() { - it("should estimate the gas for a transfer", async function() { - const estimation = await this.provider.send("eth_estimateGas", [ - { - from: zeroAddress(), - to: zeroAddress() - } - ]); + await this.provider.send("eth_sendTransaction", [ + { + to: contractAddress, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: `${EXAMPLE_CONTRACT.selectors.modifiesState}000000000000000000000000000000000000000000000000000000000000000a` + } + ]); + + const result2 = await this.provider.send("eth_call", [ + { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i } + ]); + + assert.equal( + result2, + "0x000000000000000000000000000000000000000000000000000000000000000a" + ); + }); - assert.isTrue(new BN(toBuffer(estimation)).lten(23000)); - }); - }); + it("Should return the value returned by the contract using an unknown account as from", async function() { + const from = "0x1234567890123456789012345678901234567890"; - describe("eth_gasPrice", async function() { - it("should return a fixed gas price", async function() { - assertQuantity(await this.provider.send("eth_gasPrice"), 8e9); - }); - }); + const contractAddress = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); - describe("eth_getBalance", async function() { - it("Should return 0 for random accounts", async function() { - assertQuantity( - await this.provider.send("eth_getBalance", [zeroAddress()]), - 0 - ); - - assertQuantity( - await this.provider.send("eth_getBalance", [ - "0x0000000000000000000000000000000000000001" - ]), - 0 - ); - - assertQuantity( - await this.provider.send("eth_getBalance", [ - "0x0001231287316387168230000000000000000001" - ]), - 0 - ); - }); + const result = await this.provider.send("eth_call", [ + { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i, from } + ]); - it("Should return the initial balance for the genesis accounts", async function() { - await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); - }); + assert.equal( + result, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); - it("Should return the updated balance after a transaction is made", async function() { - await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); - - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[1], - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - } - ]); - - await assertNodeBalances(this.provider, [ - DEFAULT_ACCOUNTS_BALANCES[0].subn(1 + 21000), - DEFAULT_ACCOUNTS_BALANCES[1].addn(1) - ]); - - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[1], - value: numberToRpcQuantity(2), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(2) - } - ]); - - await assertNodeBalances(this.provider, [ - DEFAULT_ACCOUNTS_BALANCES[0].subn(1 + 21000 + 2 + 21000 * 2), - DEFAULT_ACCOUNTS_BALANCES[1].addn(1 + 2) - ]); - }); + await this.provider.send("eth_sendTransaction", [ + { + to: contractAddress, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: `${EXAMPLE_CONTRACT.selectors.modifiesState}000000000000000000000000000000000000000000000000000000000000000a` + } + ]); + + const result2 = await this.provider.send("eth_call", [ + { to: contractAddress, data: EXAMPLE_CONTRACT.selectors.i, from } + ]); + + assert.equal( + result2, + "0x000000000000000000000000000000000000000000000000000000000000000a" + ); + }); - it("Should return the original balance after a call is made", async function() { - await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); + it("Should return an empty buffer if called an non-contract account", async function() { + const result = await this.provider.send("eth_call", [ + { + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.i + } + ]); - await this.provider.send("eth_call", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[1], - value: numberToRpcQuantity(1) - } - ]); + assert.equal(result, "0x"); + }); + }); - await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); + describe("eth_chainId", async function() { + it("should return the chain id as QUANTITY", async function() { + assertQuantity( + await this.provider.send("eth_chainId"), + this.common.chainId() + ); + }); + }); - await this.provider.send("eth_call", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[1], - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - value: numberToRpcQuantity(1) - } - ]); + describe("eth_coinbase", async function() { + it("should return the the hardcoded coinbase address", async function() { + assert.equal( + await this.provider.send("eth_coinbase"), + bufferToHex(COINBASE_ADDRESS) + ); + }); + }); - await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); - }); + describe("eth_compileLLL", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_compileLLL"); + }); + }); - it("should assign the block reward to the coinbase address", async function() { - const coinbase = await this.provider.send("eth_coinbase"); + describe("eth_compileSerpent", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_compileSerpent"); + }); + }); - assertQuantity(await this.provider.send("eth_getBalance", [coinbase]), 0); + describe("eth_compileSolidity", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_compileSolidity"); + }); + }); - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0] - } - ]); + describe("eth_estimateGas", async function() { + it("should estimate the gas for a transfer", async function() { + const estimation = await this.provider.send("eth_estimateGas", [ + { + from: zeroAddress(), + to: zeroAddress() + } + ]); - const balance = new BN( - toBuffer(await this.provider.send("eth_getBalance", [coinbase])) - ); + assert.isTrue(new BN(toBuffer(estimation)).lten(23000)); + }); + }); - assert.isTrue(balance.gtn(0)); + describe("eth_gasPrice", async function() { + it("should return a fixed gas price", async function() { + assertQuantity(await this.provider.send("eth_gasPrice"), 8e9); + }); + }); - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0] - } - ]); + describe("eth_getBalance", async function() { + it("Should return 0 for random accounts", async function() { + assertQuantity( + await this.provider.send("eth_getBalance", [zeroAddress()]), + 0 + ); - const balance2 = new BN( - toBuffer(await this.provider.send("eth_getBalance", [coinbase])) - ); + assertQuantity( + await this.provider.send("eth_getBalance", [ + "0x0000000000000000000000000000000000000001" + ]), + 0 + ); - assert.isTrue(balance2.gt(balance)); - }); - }); + assertQuantity( + await this.provider.send("eth_getBalance", [ + "0x0001231287316387168230000000000000000001" + ]), + 0 + ); + }); - describe("eth_getBlockByHash", async function() { - it("should return null for non-existing blocks", async function() { - assert.isNull( - await this.provider.send("eth_getBlockByHash", [ - "0x0000000000000000000000000000000000000000000000000000000000000001", - false - ]) - ); - - assert.isNull( - await this.provider.send("eth_getBlockByHash", [ - "0x0000000000000000000000000000000000000000000000000000000000000123", - true - ]) - ); - }); + it("Should return the initial balance for the genesis accounts", async function() { + await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); + }); - it("Should return the block with transaction hashes if the second argument is false", async function() { - const txHash = await sendTxToZeroAddress(this.provider); - const txOutput: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByHash", - [txHash] - ); - - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByHash", - [txOutput.blockHash, false] - ); - - assert.equal(block.hash, txOutput.blockHash); - assertQuantity(block.number, 1); - assert.equal(block.transactions.length, 1); - assert.include(block.transactions as string[], txHash); - assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); - assert.isEmpty(block.uncles); - }); + it("Should return the updated balance after a transaction is made", async function() { + await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); + + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + } + ]); + + await assertNodeBalances(this.provider, [ + DEFAULT_ACCOUNTS_BALANCES[0].subn(1 + 21000), + DEFAULT_ACCOUNTS_BALANCES[1].addn(1) + ]); + + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + value: numberToRpcQuantity(2), + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(2) + } + ]); + + await assertNodeBalances(this.provider, [ + DEFAULT_ACCOUNTS_BALANCES[0].subn(1 + 21000 + 2 + 21000 * 2), + DEFAULT_ACCOUNTS_BALANCES[1].addn(1 + 2) + ]); + }); - it("Should return the block with the complete transactions if the second argument is true", async function() { - const txHash = await sendTxToZeroAddress(this.provider); - const txOutput: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByHash", - [txHash] - ); - - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByHash", - [txOutput.blockHash, true] - ); - - assert.equal(block.hash, txOutput.blockHash); - assertQuantity(block.number, 1); - assert.equal(block.transactions.length, 1); - assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); - assert.deepEqual(block.transactions[0] as RpcTransactionOutput, txOutput); - assert.isEmpty(block.uncles); - }); - }); + it("Should return the original balance after a call is made", async function() { + await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); - describe("eth_getBlockByNumber", async function() { - describe("eth_getBlockByNumber", async function() { - it("Should return the genesis block for number 0", async function() { - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(0), - false - ]); - - assert.equal( - block.parentHash, - "0x0000000000000000000000000000000000000000000000000000000000000000" - ); - - assertQuantity(block.number, 0); - assert.isEmpty(block.transactions); - }); + await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + value: numberToRpcQuantity(1) + } + ]); - it("Should return null for unknown blocks", async function() { - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(2), - false - ]); + await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); - assert.isNull(block); + await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + value: numberToRpcQuantity(1) + } + ]); - const block2 = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(1), - true - ]); + await assertNodeBalances(this.provider, DEFAULT_ACCOUNTS_BALANCES); + }); - assert.isNull(block2); - }); + it("should assign the block reward to the coinbase address", async function() { + const coinbase = await this.provider.send("eth_coinbase"); - it("Should return the new blocks", async function() { - const genesisBlock: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(0), false] - ); - - const txHash = await sendTxToZeroAddress(this.provider); - - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(1), false] - ); - - assertQuantity(block.number, 1); - assert.equal(block.transactions.length, 1); - assert.equal(block.parentHash, genesisBlock.hash); - assert.include(block.transactions as string[], txHash); - assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); - assert.isEmpty(block.uncles); - }); + assertQuantity( + await this.provider.send("eth_getBalance", [coinbase]), + 0 + ); - it("should return the complete transactions if the second argument is true", async function() { - const genesisBlock: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(0), false] - ); - - const txHash = await sendTxToZeroAddress(this.provider); - - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(1), true] - ); - - assertQuantity(block.number, 1); - assert.equal(block.transactions.length, 1); - assert.equal(block.parentHash, genesisBlock.hash); - assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); - assert.isEmpty(block.uncles); - - const txOutput = block.transactions[0] as RpcTransactionOutput; - assert.equal(txOutput.hash, txHash); - assert.equal(block.hash, txOutput.blockHash); - assert.equal(block.number, txOutput.blockNumber); - assert.equal(txOutput.transactionIndex, numberToRpcQuantity(0)); - - assert.deepEqual( - txOutput, - await this.provider.send("eth_getTransactionByHash", [txHash]) - ); - }); + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0] + } + ]); - it("should return the right block total difficulty", async function() { - const genesisBlock: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(0), false] - ); + const balance = new BN( + toBuffer(await this.provider.send("eth_getBalance", [coinbase])) + ); - assertQuantity(genesisBlock.totalDifficulty, 1); - assertQuantity(genesisBlock.difficulty, 1); + assert.isTrue(balance.gtn(0)); - await sendTxToZeroAddress(this.provider); + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0] + } + ]); - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(1), false] - ); + const balance2 = new BN( + toBuffer(await this.provider.send("eth_getBalance", [coinbase])) + ); - assertQuantity( - block.totalDifficulty, - quantityToNumber(block.difficulty) + 1 - ); + assert.isTrue(balance2.gt(balance)); + }); }); - }); - }); - - describe("eth_getBlockTransactionCountByHash", async function() { - it("should return null for non-existing blocks", async function() { - assert.isNull( - await this.provider.send("eth_getBlockTransactionCountByHash", [ - "0x1111111111111111111111111111111111111111111111111111111111111111" - ]) - ); - }); - - it("Should return 0 for the genesis block", async function() { - const genesisBlock: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(0), false] - ); - - assertQuantity( - await this.provider.send("eth_getBlockTransactionCountByHash", [ - genesisBlock.hash - ]), - 0 - ); - }); - - it("Should return 1 for others", async function() { - const txhash = await sendTxToZeroAddress(this.provider); - const txOutput: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByHash", - [txhash] - ); + describe("eth_getBlockByHash", async function() { + it("should return null for non-existing blocks", async function() { + assert.isNull( + await this.provider.send("eth_getBlockByHash", [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + false + ]) + ); - assertQuantity( - await this.provider.send("eth_getBlockTransactionCountByHash", [ - txOutput.blockHash - ]), - 1 - ); - }); - }); + assert.isNull( + await this.provider.send("eth_getBlockByHash", [ + "0x0000000000000000000000000000000000000000000000000000000000000123", + true + ]) + ); + }); - describe("eth_getBlockTransactionCountByNumber", async function() { - it("should return null for non-existing blocks", async function() { - assert.isNull( - await this.provider.send("eth_getBlockTransactionCountByNumber", [ - numberToRpcQuantity(1) - ]) - ); - }); + it("Should return the block with transaction hashes if the second argument is false", async function() { + const txHash = await sendTxToZeroAddress(this.provider); + const txOutput: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByHash", + [txHash] + ); - it("Should return 0 for the genesis block", async function() { - assertQuantity( - await this.provider.send("eth_getBlockTransactionCountByNumber", [ - numberToRpcQuantity(0) - ]), - 0 - ); - }); + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByHash", + [txOutput.blockHash, false] + ); - it("Should return 1 for others", async function() { - await sendTxToZeroAddress(this.provider); + assert.equal(block.hash, txOutput.blockHash); + assertQuantity(block.number, 1); + assert.equal(block.transactions.length, 1); + assert.include(block.transactions as string[], txHash); + assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); + assert.isEmpty(block.uncles); + }); - assertQuantity( - await this.provider.send("eth_getBlockTransactionCountByNumber", [ - numberToRpcQuantity(1) - ]), - 1 - ); - }); - }); + it("Should return the block with the complete transactions if the second argument is true", async function() { + const txHash = await sendTxToZeroAddress(this.provider); + const txOutput: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByHash", + [txHash] + ); - describe("eth_getCode", async function() { - it("Should return an empty buffer for non-contract accounts", async function() { - assert.equal( - await this.provider.send("eth_getCode", [zeroAddress()]), - "0x" - ); - }); + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByHash", + [txOutput.blockHash, true] + ); - it("Should return an empty buffer for precompiles", async function() { - for (let i = 1; i <= PRECOMPILES_COUNT; i++) { - const precompileNumber = i.toString(16); - const zero = zeroAddress(); - - assert.equal( - await this.provider.send("eth_getCode", [ - zero.substr(0, zero.length - precompileNumber.length) + - precompileNumber - ]), - "0x" - ); - } - }); + assert.equal(block.hash, txOutput.blockHash); + assertQuantity(block.number, 1); + assert.equal(block.transactions.length, 1); + assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); + assert.deepEqual( + block.transactions[0] as RpcTransactionOutput, + txOutput + ); + assert.isEmpty(block.uncles); + }); + }); - it("Should return the deployed code", async function() { - // This a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to - // the stack, stores that in memory, and then returns the first byte from memory. - // This deploys a contract which a single byte of code, 0x41. - const contractAddress = await deployContract( - this.provider, - "0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3" - ); - - assert.equal( - await this.provider.send("eth_getCode", [contractAddress]), - "0x41" - ); - }); - }); + describe("eth_getBlockByNumber", async function() { + describe("eth_getBlockByNumber", async function() { + it("Should return the genesis block for number 0", async function() { + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(0), + false + ]); - describe("eth_getCompilers", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getCompilers"); - }); - }); + assert.equal( + block.parentHash, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ); - describe("block filters", function() { - it("Supports block filters", async function() { - const filterId = await this.provider.send("eth_newBlockFilter", []); - assert.isString(filterId); - }); + assertQuantity(block.number, 0); + assert.isEmpty(block.transactions); + }); - it("Supports uninstalling an existing filter", async function() { - const filterId = await this.provider.send("eth_newBlockFilter", []); - const uninstalled = await this.provider.send("eth_uninstallFilter", [ - filterId - ]); + it("Should return null for unknown blocks", async function() { + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(2), + false + ]); - assert.isTrue(uninstalled); - }); + assert.isNull(block); - it("doesn't fail on uninstalling a non-existent filter", async function() { - const uninstalled = await this.provider.send("eth_uninstallFilter", [ - "0x1" - ]); + const block2 = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(1), + true + ]); - assert.isFalse(uninstalled); - }); + assert.isNull(block2); + }); - it("should start returning at least one block", async function() { - const filterId = await this.provider.send("eth_newBlockFilter", []); - const blockHashes = await this.provider.send("eth_getFilterChanges", [ - filterId - ]); + it("Should return the new blocks", async function() { + const genesisBlock: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(0), false] + ); - assert.isNotEmpty(blockHashes); - }); + const txHash = await sendTxToZeroAddress(this.provider); - it("should not return the same block twice", async function() { - const filterId = await this.provider.send("eth_newBlockFilter", []); + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(1), false] + ); - await this.provider.send("eth_getFilterChanges", [filterId]); + assertQuantity(block.number, 1); + assert.equal(block.transactions.length, 1); + assert.equal(block.parentHash, genesisBlock.hash); + assert.include(block.transactions as string[], txHash); + assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); + assert.isEmpty(block.uncles); + }); - const blockHashes = await this.provider.send("eth_getFilterChanges", [ - filterId - ]); + it("should return the complete transactions if the second argument is true", async function() { + const genesisBlock: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(0), false] + ); - assert.isEmpty(blockHashes); - }); + const txHash = await sendTxToZeroAddress(this.provider); - it("should return new blocks", async function() { - const filterId = await this.provider.send("eth_newBlockFilter", []); + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(1), true] + ); - const initialHashes = await this.provider.send("eth_getFilterChanges", [ - filterId - ]); + assertQuantity(block.number, 1); + assert.equal(block.transactions.length, 1); + assert.equal(block.parentHash, genesisBlock.hash); + assert.equal(block.miner, bufferToHex(COINBASE_ADDRESS)); + assert.isEmpty(block.uncles); + + const txOutput = block.transactions[0] as RpcTransactionOutput; + assert.equal(txOutput.hash, txHash); + assert.equal(block.hash, txOutput.blockHash); + assert.equal(block.number, txOutput.blockNumber); + assert.equal(txOutput.transactionIndex, numberToRpcQuantity(0)); + + assert.deepEqual( + txOutput, + await this.provider.send("eth_getTransactionByHash", [txHash]) + ); + }); - assert.lengthOf(initialHashes, 1); + it("should return the right block total difficulty", async function() { + const genesisBlock: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(0), false] + ); - const empty = await this.provider.send("eth_getFilterChanges", [ - filterId - ]); + assertQuantity(genesisBlock.totalDifficulty, 1); + assertQuantity(genesisBlock.difficulty, 1); - assert.isEmpty(empty); + await sendTxToZeroAddress(this.provider); - await this.provider.send("evm_mine", []); - await this.provider.send("evm_mine", []); - await this.provider.send("evm_mine", []); + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(1), false] + ); - const blockHashes = await this.provider.send("eth_getFilterChanges", [ - filterId - ]); + assertQuantity( + block.totalDifficulty, + quantityToNumber(block.difficulty) + 1 + ); + }); + }); + }); - assert.lengthOf(blockHashes, 3); - }); - }); + describe("eth_getBlockTransactionCountByHash", async function() { + it("should return null for non-existing blocks", async function() { + assert.isNull( + await this.provider.send("eth_getBlockTransactionCountByHash", [ + "0x1111111111111111111111111111111111111111111111111111111111111111" + ]) + ); + }); - describe("eth_getFilterLogs", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getFilterLogs"); - }); - }); + it("Should return 0 for the genesis block", async function() { + const genesisBlock: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(0), false] + ); - describe("eth_getLogs", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getLogs"); - }); - }); + assertQuantity( + await this.provider.send("eth_getBlockTransactionCountByHash", [ + genesisBlock.hash + ]), + 0 + ); + }); - describe("eth_getProof", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getProof"); - }); - }); + it("Should return 1 for others", async function() { + const txhash = await sendTxToZeroAddress(this.provider); - describe("eth_getStorageAt", async function() { - describe("Imitating Ganache", function() { - describe("When a slot has not been written into", function() { - it("Should return `0x0`, despite it not making any sense at all", async function() { - const exampleContract = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` + const txOutput: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByHash", + [txhash] ); - assert.strictEqual( - await this.provider.send("eth_getStorageAt", [ - exampleContract, - numberToRpcQuantity(3) + assertQuantity( + await this.provider.send("eth_getBlockTransactionCountByHash", [ + txOutput.blockHash ]), - "0x0" + 1 ); + }); + }); - assert.strictEqual( - await this.provider.send("eth_getStorageAt", [ - exampleContract, - numberToRpcQuantity(4) - ]), - "0x0" + describe("eth_getBlockTransactionCountByNumber", async function() { + it("should return null for non-existing blocks", async function() { + assert.isNull( + await this.provider.send("eth_getBlockTransactionCountByNumber", [ + numberToRpcQuantity(1) + ]) ); + }); - assert.strictEqual( - await this.provider.send("eth_getStorageAt", [ - DEFAULT_ACCOUNTS_ADDRESSES[0], + it("Should return 0 for the genesis block", async function() { + assertQuantity( + await this.provider.send("eth_getBlockTransactionCountByNumber", [ numberToRpcQuantity(0) ]), - "0x0" + 0 ); }); - }); - describe("When a slot has been written into", function() { - describe("When 32 bytes where written", function() { - it("Should return a 32-byte DATA string", async function() { - const exampleContract = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` - ); + it("Should return 1 for others", async function() { + await sendTxToZeroAddress(this.provider); - assert.strictEqual( - await this.provider.send("eth_getStorageAt", [ - exampleContract, - numberToRpcQuantity(2) - ]), - "0x1234567890123456789012345678901234567890123456789012345678901234" - ); - }); + assertQuantity( + await this.provider.send("eth_getBlockTransactionCountByNumber", [ + numberToRpcQuantity(1) + ]), + 1 + ); }); + }); - describe("When less than 32 bytes where written", function() { - it("Should return a DATA string with the same amount bytes that have been written", async function() { - const exampleContract = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` - ); - - // We return as the EthereumJS VM stores it. This has been checked - // against remix - - let newState = - "000000000000000000000000000000000000000000000000000000000000007b"; + describe("eth_getCode", async function() { + it("Should return an empty buffer for non-contract accounts", async function() { + assert.equal( + await this.provider.send("eth_getCode", [zeroAddress()]), + "0x" + ); + }); - await this.provider.send("eth_sendTransaction", [ - { - to: exampleContract, - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: EXAMPLE_CONTRACT.selectors.modifiesState + newState - } - ]); + it("Should return an empty buffer for precompiles", async function() { + for (let i = 1; i <= PRECOMPILES_COUNT; i++) { + const precompileNumber = i.toString(16); + const zero = zeroAddress(); - assert.strictEqual( - await this.provider.send("eth_getStorageAt", [ - exampleContract, - numberToRpcQuantity(0) + assert.equal( + await this.provider.send("eth_getCode", [ + zero.substr(0, zero.length - precompileNumber.length) + + precompileNumber ]), - "0x7b" + "0x" ); + } + }); - newState = - "000000000000000000000000000000000000000000000000000000000000007c"; + it("Should return the deployed code", async function() { + // This a deployment transaction that pushes 0x41 (i.e. ascii A) followed by 31 0s to + // the stack, stores that in memory, and then returns the first byte from memory. + // This deploys a contract which a single byte of code, 0x41. + const contractAddress = await deployContract( + this.provider, + "0x7f410000000000000000000000000000000000000000000000000000000000000060005260016000f3" + ); - await this.provider.send("eth_sendTransaction", [ - { - to: exampleContract, - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: EXAMPLE_CONTRACT.selectors.modifiesState + newState - } - ]); + assert.equal( + await this.provider.send("eth_getCode", [contractAddress]), + "0x41" + ); + }); + }); - assert.strictEqual( - await this.provider.send("eth_getStorageAt", [ - exampleContract, - numberToRpcQuantity(0) - ]), - "0x7c" - ); - }); + describe("eth_getCompilers", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_getCompilers"); }); }); - }); - }); - describe("eth_getTransactionByBlockHashAndIndex", async function() { - it("should return null for non-existing blocks", async function() { - assert.isNull( - await this.provider.send("eth_getTransactionByBlockHashAndIndex", [ - "0x1231231231231231231231231231231231231231231231231231231231231231", - numberToRpcQuantity(0) - ]) - ); - }); + describe("block filters", function() { + it("Supports block filters", async function() { + const filterId = await this.provider.send("eth_newBlockFilter", []); + assert.isString(filterId); + }); - it("should return null for existing blocks but non-existing indexes", async function() { - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(0), - false - ]); - - assert.isNull( - await this.provider.send("eth_getTransactionByBlockHashAndIndex", [ - block.hash, - numberToRpcQuantity(0) - ]) - ); - - assert.isNull( - await this.provider.send("eth_getTransactionByBlockHashAndIndex", [ - block.hash, - numberToRpcQuantity(0) - ]) - ); - }); + it("Supports uninstalling an existing filter", async function() { + const filterId = await this.provider.send("eth_newBlockFilter", []); + const uninstalled = await this.provider.send("eth_uninstallFilter", [ + filterId + ]); - it("should return the right info for the existing ones", async function() { - const txParams1: TransactionParams = { - to: toBuffer(zeroAddress()), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer("0xaa"), - nonce: new BN(0), - value: new BN(123), - gasLimit: new BN(25000), - gasPrice: new BN(23912) - }; - - const txHash = await sendTransactionFromTxParams( - this.provider, - txParams1 - ); - - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(1), - false - ]); - - const tx: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByBlockHashAndIndex", - [block.hash, numberToRpcQuantity(0)] - ); - - assertTransaction(tx, txHash, txParams1, 1, block.hash, 0); - - const txParams2: TransactionParams = { - to: toBuffer(zeroAddress()), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer([]), - nonce: new BN(1), - value: new BN(123), - gasLimit: new BN(80000), - gasPrice: new BN(239) - }; - - const txHash2 = await sendTransactionFromTxParams( - this.provider, - txParams2 - ); - - const block2 = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(2), - false - ]); - - const tx2: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByBlockHashAndIndex", - [block2.hash, numberToRpcQuantity(0)] - ); - - assertTransaction(tx2, txHash2, txParams2, 2, block2.hash, 0); - }); - }); + assert.isTrue(uninstalled); + }); - describe("eth_getTransactionByBlockNumberAndIndex", async function() { - it("should return null for non-existing blocks", async function() { - assert.isNull( - await this.provider.send("eth_getTransactionByBlockNumberAndIndex", [ - numberToRpcQuantity(1), - numberToRpcQuantity(0) - ]) - ); - }); + it("doesn't fail on uninstalling a non-existent filter", async function() { + const uninstalled = await this.provider.send("eth_uninstallFilter", [ + "0x1" + ]); - it("should return null for existing blocks but non-existing indexes", async function() { - assert.isNull( - await this.provider.send("eth_getTransactionByBlockNumberAndIndex", [ - numberToRpcQuantity(0), - numberToRpcQuantity(0) - ]) - ); - - assert.isNull( - await this.provider.send("eth_getTransactionByBlockNumberAndIndex", [ - numberToRpcQuantity(1), - numberToRpcQuantity(0) - ]) - ); - }); + assert.isFalse(uninstalled); + }); - it("should return the right info for the existing ones", async function() { - const txParams1: TransactionParams = { - to: toBuffer(zeroAddress()), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer("0xaa"), - nonce: new BN(0), - value: new BN(123), - gasLimit: new BN(25000), - gasPrice: new BN(23912) - }; - - const txHash = await sendTransactionFromTxParams( - this.provider, - txParams1 - ); - - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(1), - false - ]); - - const tx: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByBlockNumberAndIndex", - [numberToRpcQuantity(1), numberToRpcQuantity(0)] - ); - - assertTransaction(tx, txHash, txParams1, 1, block.hash, 0); - - const txParams2: TransactionParams = { - to: toBuffer(zeroAddress()), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer([]), - nonce: new BN(1), - value: new BN(123), - gasLimit: new BN(80000), - gasPrice: new BN(239) - }; - - const txHash2 = await sendTransactionFromTxParams( - this.provider, - txParams2 - ); - - const block2 = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(2), - false - ]); - - const tx2: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByBlockNumberAndIndex", - [numberToRpcQuantity(2), numberToRpcQuantity(0)] - ); - - assertTransaction(tx2, txHash2, txParams2, 2, block2.hash, 0); - }); - }); + it("should start returning at least one block", async function() { + const filterId = await this.provider.send("eth_newBlockFilter", []); + const blockHashes = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); - describe("eth_getTransactionByHash", async function() { - it("should return null for unknown txs", async function() { - assert.isNull( - await this.provider.send("eth_getTransactionByHash", [ - "0x1234567890123456789012345678901234567890123456789012345678902134" - ]) - ); - - assert.isNull( - await this.provider.send("eth_getTransactionByHash", [ - "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - ]) - ); - }); + assert.isNotEmpty(blockHashes); + }); - it("should return the right info for the existing ones", async function() { - const txParams1: TransactionParams = { - to: toBuffer(zeroAddress()), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer("0xaa"), - nonce: new BN(0), - value: new BN(123), - gasLimit: new BN(25000), - gasPrice: new BN(23912) - }; - - const txHash = await sendTransactionFromTxParams( - this.provider, - txParams1 - ); - - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(1), - false - ]); - - const tx: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByHash", - [txHash] - ); - - assertTransaction(tx, txHash, txParams1, 1, block.hash, 0); - - const txParams2: TransactionParams = { - to: toBuffer(zeroAddress()), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer([]), - nonce: new BN(1), - value: new BN(123), - gasLimit: new BN(80000), - gasPrice: new BN(239) - }; - - const txHash2 = await sendTransactionFromTxParams( - this.provider, - txParams2 - ); - - const block2 = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(2), - false - ]); - - const tx2: RpcTransactionOutput = await this.provider.send( - "eth_getTransactionByHash", - [txHash2] - ); - - assertTransaction(tx2, txHash2, txParams2, 2, block2.hash, 0); - }); + it("should not return the same block twice", async function() { + const filterId = await this.provider.send("eth_newBlockFilter", []); - it("should return the transaction if it gets to execute and failed", async function() { - const txParams: TransactionParams = { - to: toBuffer([]), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer("0x60006000fd"), - nonce: new BN(0), - value: new BN(123), - gasLimit: new BN(250000), - gasPrice: new BN(23912) - }; - - const txHash = getSignedTxHash(txParams, 0); - - // Revert. This a deployment transaction that immediately reverts without a reason - await assertTransactionFailure( - this.provider, - { - from: bufferToHex(txParams.from), - data: bufferToHex(txParams.data), - nonce: numberToRpcQuantity(txParams.nonce), - value: numberToRpcQuantity(txParams.value), - gas: numberToRpcQuantity(txParams.gasLimit), - gasPrice: numberToRpcQuantity(txParams.gasPrice) - }, - "Transaction reverted without a reason" - ); - - const tx = await this.provider.send("eth_getTransactionByHash", [txHash]); - const block = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(1), - false - ]); - - assertTransaction(tx, txHash, txParams, 1, block.hash, 0); - }); - }); + await this.provider.send("eth_getFilterChanges", [filterId]); - describe("eth_getTransactionCount", async function() { - it("Should return 0 for random accounts", async function() { - assertQuantity( - await this.provider.send("eth_getTransactionCount", [zeroAddress()]), - 0 - ); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - "0x0000000000000000000000000000000000000001" - ]), - 0 - ); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - "0x0001231287316387168230000000000000000001" - ]), - 0 - ); - }); + const blockHashes = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); - it("Should return the updated count after a transaction is made", async function() { - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[0] - ]), - 0 - ); - - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[1], - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - } - ]); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[0] - ]), - 1 - ); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[1] - ]), - 0 - ); - - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[1], - to: DEFAULT_ACCOUNTS_ADDRESSES[1], - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - } - ]); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[0] - ]), - 1 - ); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[1] - ]), - 1 - ); - }); + assert.isEmpty(blockHashes); + }); - it("Should not be affected by calls", async function() { - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[0] - ]), - 0 - ); - - await this.provider.send("eth_call", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[1], - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - } - ]); - - assertQuantity( - await this.provider.send("eth_getTransactionCount", [ - DEFAULT_ACCOUNTS_ADDRESSES[0] - ]), - 0 - ); - }); - }); + it("should return new blocks", async function() { + const filterId = await this.provider.send("eth_newBlockFilter", []); - describe("eth_getTransactionReceipt", async function() { - it("should return null for unknown txs", async function() { - const receipt = await this.provider.send("eth_getTransactionReceipt", [ - "0x1234567876543234567876543456765434567aeaeaed67616732632762762373" - ]); + const initialHashes = await this.provider.send( + "eth_getFilterChanges", + [filterId] + ); - assert.isNull(receipt); - }); + assert.lengthOf(initialHashes, 1); - it("should return the right values for successful txs", async function() { - const contractAddress = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` - ); - - const txHash = await this.provider.send("eth_sendTransaction", [ - { - to: contractAddress, - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: `${EXAMPLE_CONTRACT.selectors.modifiesState}000000000000000000000000000000000000000000000000000000000000000a` - } - ]); - - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(2), false] - ); - - const receipt: RpcTransactionReceiptOutput = await this.provider.send( - "eth_getTransactionReceipt", - [txHash] - ); - - assert.equal(receipt.blockHash, block.hash); - assertQuantity(receipt.blockNumber, 2); - assert.isNull(receipt.contractAddress); - assert.equal(receipt.cumulativeGasUsed, receipt.gasUsed); - assert.equal(receipt.from, DEFAULT_ACCOUNTS_ADDRESSES[0]); - assertQuantity(receipt.status, 1); - assert.equal(receipt.logs.length, 1); - assert.equal(receipt.to, contractAddress); - assert.equal(receipt.transactionHash, txHash); - assertQuantity(receipt.transactionIndex, 0); - - const log = receipt.logs[0]; - - assert.isFalse(log.removed); - assertQuantity(log.logIndex, 0); - assertQuantity(log.transactionIndex, 0); - assert.equal(log.transactionHash, txHash); - assert.equal(log.blockHash, block.hash); - assertQuantity(log.blockNumber, 2); - assert.equal(log.address, contractAddress); - - // The new value of i is not indexed - assert.equal( - log.data, - "0x000000000000000000000000000000000000000000000000000000000000000a" - ); - - assert.deepEqual(log.topics, [ - EXAMPLE_CONTRACT.topics.StateModified[0], - "0x0000000000000000000000000000000000000000000000000000000000000000" - ]); - }); + const empty = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); - it("should return the receipt for txs that were executed and failed", async function() { - const txParams: TransactionParams = { - to: toBuffer([]), - from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), - data: toBuffer("0x60006000fd"), - nonce: new BN(0), - value: new BN(123), - gasLimit: new BN(250000), - gasPrice: new BN(23912) - }; - - const txHash = getSignedTxHash(txParams, 0); - - // Revert. This a deployment transaction that immediately reverts without a reason - await assertTransactionFailure( - this.provider, - { - from: bufferToHex(txParams.from), - data: bufferToHex(txParams.data), - nonce: numberToRpcQuantity(txParams.nonce), - value: numberToRpcQuantity(txParams.value), - gas: numberToRpcQuantity(txParams.gasLimit), - gasPrice: numberToRpcQuantity(txParams.gasPrice) - }, - "Transaction reverted without a reason" - ); - - const receipt = await this.provider.send("eth_getTransactionReceipt", [ - txHash - ]); - - assert.isNotNull(receipt); - }); - }); + assert.isEmpty(empty); - describe("eth_getUncleByBlockHashAndIndex", async function() { - it("is not supported", async function() { - await assertNotSupported( - this.provider, - "eth_getUncleByBlockHashAndIndex" - ); - }); - }); + await this.provider.send("evm_mine", []); + await this.provider.send("evm_mine", []); + await this.provider.send("evm_mine", []); - describe("eth_getUncleByBlockNumberAndIndex", async function() { - it("is not supported", async function() { - await assertNotSupported( - this.provider, - "eth_getUncleByBlockNumberAndIndex" - ); - }); - }); + const blockHashes = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); - describe("eth_getUncleCountByBlockHash", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getUncleCountByBlockHash"); - }); - }); + assert.lengthOf(blockHashes, 3); + }); + }); - describe("eth_getUncleCountByBlockNumber", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getUncleCountByBlockNumber"); - }); - }); + describe("eth_getFilterLogs", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_getFilterLogs"); + }); + }); - describe("eth_getWork", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getWork"); - }); - }); + describe("eth_getLogs", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_getLogs"); + }); + }); - describe("eth_hashrate", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_hashrate"); - }); - }); + describe("eth_getProof", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_getProof"); + }); + }); - describe("eth_mining", async function() { - it("should return false", async function() { - assert.deepEqual(await this.provider.send("eth_mining"), false); - }); - }); + describe("eth_getStorageAt", async function() { + describe("Imitating Ganache", function() { + describe("When a slot has not been written into", function() { + it("Should return `0x0`, despite it not making any sense at all", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + assert.strictEqual( + await this.provider.send("eth_getStorageAt", [ + exampleContract, + numberToRpcQuantity(3) + ]), + "0x0" + ); + + assert.strictEqual( + await this.provider.send("eth_getStorageAt", [ + exampleContract, + numberToRpcQuantity(4) + ]), + "0x0" + ); + + assert.strictEqual( + await this.provider.send("eth_getStorageAt", [ + DEFAULT_ACCOUNTS_ADDRESSES[0], + numberToRpcQuantity(0) + ]), + "0x0" + ); + }); + }); - describe("eth_newPendingTransactionFilter", async function() { - it("is not supported", async function() { - await assertNotSupported( - this.provider, - "eth_newPendingTransactionFilter" - ); - }); - }); + describe("When a slot has been written into", function() { + describe("When 32 bytes where written", function() { + it("Should return a 32-byte DATA string", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + assert.strictEqual( + await this.provider.send("eth_getStorageAt", [ + exampleContract, + numberToRpcQuantity(2) + ]), + "0x1234567890123456789012345678901234567890123456789012345678901234" + ); + }); + }); + + describe("When less than 32 bytes where written", function() { + it("Should return a DATA string with the same amount bytes that have been written", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + // We return as the EthereumJS VM stores it. This has been checked + // against remix + + let newState = + "000000000000000000000000000000000000000000000000000000000000007b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.strictEqual( + await this.provider.send("eth_getStorageAt", [ + exampleContract, + numberToRpcQuantity(0) + ]), + "0x7b" + ); + + newState = + "000000000000000000000000000000000000000000000000000000000000007c"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.strictEqual( + await this.provider.send("eth_getStorageAt", [ + exampleContract, + numberToRpcQuantity(0) + ]), + "0x7c" + ); + }); + }); + }); + }); + }); - describe("eth_pendingTransactions", async function() { - it("should return an empty array, as there is no pending transactions support", async function() { - assert.deepEqual(await this.provider.send("eth_pendingTransactions"), []); - }); - }); + describe("eth_getTransactionByBlockHashAndIndex", async function() { + it("should return null for non-existing blocks", async function() { + assert.isNull( + await this.provider.send("eth_getTransactionByBlockHashAndIndex", [ + "0x1231231231231231231231231231231231231231231231231231231231231231", + numberToRpcQuantity(0) + ]) + ); + }); - describe("eth_protocolVersion", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_protocolVersion"); - }); - }); + it("should return null for existing blocks but non-existing indexes", async function() { + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(0), + false + ]); - describe("eth_sendRawTransaction", async function() { - it("Should throw if the data isn't a proper transaction", async function() { - await assertInvalidInputError( - this.provider, - "eth_sendRawTransaction", - ["0x123456"], - "Invalid transaction" - ); - }); + assert.isNull( + await this.provider.send("eth_getTransactionByBlockHashAndIndex", [ + block.hash, + numberToRpcQuantity(0) + ]) + ); - it("Should throw if the signature is invalid", async function() { - await assertInvalidInputError( - this.provider, - "eth_sendRawTransaction", - [ - // This transaction was obtained with eth_sendTransaction, and its r value was wiped - "0xf3808501dcd6500083015f9080800082011a80a00dbd1a45b7823be518540ca77afb7178a470b8054281530a6cdfd0ad3328cf96" - ], - "Invalid transaction signature" - ); - }); + assert.isNull( + await this.provider.send("eth_getTransactionByBlockHashAndIndex", [ + block.hash, + numberToRpcQuantity(0) + ]) + ); + }); - it("Should throw if the signature is invalid but for another chain (EIP155)", async function() { - await assertInvalidInputError( - this.provider, - "eth_sendRawTransaction", - [ - "0xf86e820a0f843b9aca0083030d40941aad5e821c667e909c16a49363ca48f672b46c5d88169866e539efe0008025a07bc6a357d809c9d27f8f5a826861e7f9b4b7c9cff4f91f894b88e98212069b3da05dbadbdfa67bab1d76d2d81e33d90162d508431362331f266dd6aa0cb4b525aa" - ], - "Incompatible EIP155-based" - ); - }); + it("should return the right info for the existing ones", async function() { + const txParams1: TransactionParams = { + to: toBuffer(zeroAddress()), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer("0xaa"), + nonce: new BN(0), + value: new BN(123), + gasLimit: new BN(25000), + gasPrice: new BN(23912) + }; + + const txHash = await sendTransactionFromTxParams( + this.provider, + txParams1 + ); - it("Should send the raw transaction", async function() { - // This test is a copy of: Should work with just from and data - - const hash = await this.provider.send("eth_sendRawTransaction", [ - "0xf853808501dcd6500083015f9080800082011aa09c8def73818f79b6493b7a3f7ce47b557694ca195d1b54bb74e3d98990041b44a00dbd1a45b7823be518540ca77afb7178a470b8054281530a6cdfd0ad3328cf96" - ]); - - const receipt = await this.provider.send("eth_getTransactionReceipt", [ - hash - ]); - - const receiptFromGeth = { - blockHash: - "0x01490da2af913e9a868430b7b4c5060fc29cbdb1692bb91d3c72c734acd73bc8", - blockNumber: "0x6", - contractAddress: "0x6ea84fcbef576d66896dc2c32e139b60e641170c", - cumulativeGasUsed: "0xcf0c", - from: "0xda4585f6e68ed1cdfdad44a08dbe3979ec74ad8f", - gasUsed: "0xcf0c", - logs: [], - logsBloom: - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - status: "0x1", - to: null, - transactionHash: - "0xbd24cbe9c1633b98e61d93619230341141d2cff49470ed6afa739cee057fd0aa", - transactionIndex: "0x0" - }; - - assertReceiptMatchesGethOne(receipt, receiptFromGeth, 1); - }); - }); + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(1), + false + ]); - describe("eth_sendTransaction", async function() { - // Because of the way we are testing this (i.e. integration testing) it's almost impossible to - // fully test this method in a reasonable amount of time. This is because it executes the core - // of Ethereum: its state transition function. - // - // We have mostly test about logic added on top of that, and will add new ones whenever - // suitable. This is approximately the same as assuming that ethereumjs-vm is correct, which - // seems reasonable, and if it weren't we should address the issues there. - - describe("Params validation", function() { - it("Should fail if the account is not managed by the provider", async function() { - await assertTransactionFailure( - this.provider, - { - from: zeroAddress(), - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - }, - "unknown account", - InvalidInputError - ); + const tx: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByBlockHashAndIndex", + [block.hash, numberToRpcQuantity(0)] + ); + + assertTransaction(tx, txHash, txParams1, 1, block.hash, 0); + + const txParams2: TransactionParams = { + to: toBuffer(zeroAddress()), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer([]), + nonce: new BN(1), + value: new BN(123), + gasLimit: new BN(80000), + gasPrice: new BN(239) + }; + + const txHash2 = await sendTransactionFromTxParams( + this.provider, + txParams2 + ); + + const block2 = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(2), + false + ]); + + const tx2: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByBlockHashAndIndex", + [block2.hash, numberToRpcQuantity(0)] + ); + + assertTransaction(tx2, txHash2, txParams2, 2, block2.hash, 0); + }); }); - it("Should fail if sending to the null address without data", async function() { - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0] - }, - "contract creation without any data provided", - InvalidInputError - ); - - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - }, - "contract creation without any data provided", - InvalidInputError - ); - - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: "0x", - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - }, - "contract creation without any data provided", - InvalidInputError - ); + describe("eth_getTransactionByBlockNumberAndIndex", async function() { + it("should return null for non-existing blocks", async function() { + assert.isNull( + await this.provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [numberToRpcQuantity(1), numberToRpcQuantity(0)] + ) + ); + }); + + it("should return null for existing blocks but non-existing indexes", async function() { + assert.isNull( + await this.provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [numberToRpcQuantity(0), numberToRpcQuantity(0)] + ) + ); + + assert.isNull( + await this.provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [numberToRpcQuantity(1), numberToRpcQuantity(0)] + ) + ); + }); + + it("should return the right info for the existing ones", async function() { + const txParams1: TransactionParams = { + to: toBuffer(zeroAddress()), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer("0xaa"), + nonce: new BN(0), + value: new BN(123), + gasLimit: new BN(25000), + gasPrice: new BN(23912) + }; + + const txHash = await sendTransactionFromTxParams( + this.provider, + txParams1 + ); + + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(1), + false + ]); + + const tx: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [numberToRpcQuantity(1), numberToRpcQuantity(0)] + ); + + assertTransaction(tx, txHash, txParams1, 1, block.hash, 0); + + const txParams2: TransactionParams = { + to: toBuffer(zeroAddress()), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer([]), + nonce: new BN(1), + value: new BN(123), + gasLimit: new BN(80000), + gasPrice: new BN(239) + }; + + const txHash2 = await sendTransactionFromTxParams( + this.provider, + txParams2 + ); + + const block2 = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(2), + false + ]); + + const tx2: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByBlockNumberAndIndex", + [numberToRpcQuantity(2), numberToRpcQuantity(0)] + ); + + assertTransaction(tx2, txHash2, txParams2, 2, block2.hash, 0); + }); }); - }); - it("Should work with just from and data", async function() { - const hash = await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: "0x00" - } - ]); - - const receipt = await this.provider.send("eth_getTransactionReceipt", [ - hash - ]); - - const receiptFromGeth = { - blockHash: - "0x01490da2af913e9a868430b7b4c5060fc29cbdb1692bb91d3c72c734acd73bc8", - blockNumber: "0x6", - contractAddress: "0x6ea84fcbef576d66896dc2c32e139b60e641170c", - cumulativeGasUsed: "0xcf0c", - from: "0xda4585f6e68ed1cdfdad44a08dbe3979ec74ad8f", - gasUsed: "0xcf0c", - logs: [], - logsBloom: - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - status: "0x1", - to: null, - transactionHash: - "0xbd24cbe9c1633b98e61d93619230341141d2cff49470ed6afa739cee057fd0aa", - transactionIndex: "0x0" - }; - - assertReceiptMatchesGethOne(receipt, receiptFromGeth, 1); - }); + describe("eth_getTransactionByHash", async function() { + it("should return null for unknown txs", async function() { + assert.isNull( + await this.provider.send("eth_getTransactionByHash", [ + "0x1234567890123456789012345678901234567890123456789012345678902134" + ]) + ); - it("Should throw if the transaction fails", async function() { - // Not enough gas - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: zeroAddress(), - gas: numberToRpcQuantity(1) - }, - "Transaction requires at least 21000 gas but got 1" - ); - - // Not enough balance - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: zeroAddress(), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(DEFAULT_ACCOUNTS_BALANCES[0]) - }, - "sender doesn't have enough funds to send tx" - ); - - // Gas is larger than block gas limit - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: zeroAddress(), - gas: numberToRpcQuantity(DEFAULT_BLOCK_GAS_LIMIT + 1) - }, - `Transaction gas limit is ${DEFAULT_BLOCK_GAS_LIMIT + - 1} and exceeds block gas limit of ${DEFAULT_BLOCK_GAS_LIMIT}` - ); - - // Invalid opcode. We try to deploy a contract with an invalid opcode in the deployment code - // The transaction gets executed anyway, so the account is updated - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: "0xAA" - }, - "Transaction failed: revert" - ); - - // Out of gas. This a deployment transaction that pushes 0x00 multiple times - // The transaction gets executed anyway, so the account is updated. - // - // Note: this test is pretty fragile, as the tx needs to have enough gas - // to pay for the calldata, but not enough to execute. This costs changed - // with istanbul, and may change again in the future. - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: - "0x6000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000", - gas: numberToRpcQuantity(53500) - }, - "out of gas" - ); - - // Invalid nonce - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - nonce: numberToRpcQuantity(1) - }, - "Invalid nonce. Expected 2 but got 1" - ); - - // Revert. This a deployment transaction that immediately reverts without a reason - // The transaction gets executed anyway, so the account is updated - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: "0x60006000fd" - }, - "Transaction reverted without a reason" - ); - - // This is a contract that reverts with A in its constructor - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: - "0x6080604052348015600f57600080fd5b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260018152602001807f410000000000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fdfe" - }, - "revert A" - ); - }); + assert.isNull( + await this.provider.send("eth_getTransactionByHash", [ + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ]) + ); + }); - it("Should fail if a successful tx is sent more than once", async function() { - const hash = await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - nonce: numberToRpcQuantity(0) - } - ]); - - await assertTransactionFailure( - this.provider, - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - nonce: numberToRpcQuantity(0) - }, - `known transaction: ${bufferToHex(hash)}` - ); - }); + it("should return the right info for the existing ones", async function() { + const txParams1: TransactionParams = { + to: toBuffer(zeroAddress()), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer("0xaa"), + nonce: new BN(0), + value: new BN(123), + gasLimit: new BN(25000), + gasPrice: new BN(23912) + }; + + const txHash = await sendTransactionFromTxParams( + this.provider, + txParams1 + ); - it("should accept a failed transaction if it eventually becomes valid", async function() { - const txParams = { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - nonce: numberToRpcQuantity(1) - }; - - // This transaction is invalid now, because of its nonce - await assertTransactionFailure(this.provider, txParams); - - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - to: DEFAULT_ACCOUNTS_ADDRESSES[0], - nonce: numberToRpcQuantity(0) - } - ]); - - // The transaction is now valid - const hash = await this.provider.send("eth_sendTransaction", [txParams]); - - // It should throw now - await assertTransactionFailure( - this.provider, - txParams, - `known transaction: ${bufferToHex(hash)}` - ); - }); - }); + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(1), + false + ]); - describe("eth_sign", async function() { - // TODO: Test this. Note that it's implementation is tested in one of - // our provider wrappers, but re-test it here anyway. - }); + const tx: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByHash", + [txHash] + ); - describe("eth_signTransaction", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_signTransaction"); - }); - }); + assertTransaction(tx, txHash, txParams1, 1, block.hash, 0); - describe("eth_signTypedData", async function() { - // TODO: Test this. Note that it just forwards to/from eth-sign-util - }); + const txParams2: TransactionParams = { + to: toBuffer(zeroAddress()), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer([]), + nonce: new BN(1), + value: new BN(123), + gasLimit: new BN(80000), + gasPrice: new BN(239) + }; - describe("eth_submitHashrate", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_submitHashrate"); - }); - }); + const txHash2 = await sendTransactionFromTxParams( + this.provider, + txParams2 + ); - describe("eth_submitWork", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_submitWork"); - }); - }); + const block2 = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(2), + false + ]); - describe("eth_subscribe", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_subscribe"); - }); - }); + const tx2: RpcTransactionOutput = await this.provider.send( + "eth_getTransactionByHash", + [txHash2] + ); - describe("eth_syncing", async function() { - it("Should return false", async function() { - assert.deepEqual(await this.provider.send("eth_syncing"), false); - }); - }); + assertTransaction(tx2, txHash2, txParams2, 2, block2.hash, 0); + }); + + it("should return the transaction if it gets to execute and failed", async function() { + const txParams: TransactionParams = { + to: toBuffer([]), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer("0x60006000fd"), + nonce: new BN(0), + value: new BN(123), + gasLimit: new BN(250000), + gasPrice: new BN(23912) + }; + + const txHash = getSignedTxHash(txParams, 0); + + // Revert. This a deployment transaction that immediately reverts without a reason + await assertTransactionFailure( + this.provider, + { + from: bufferToHex(txParams.from), + data: bufferToHex(txParams.data), + nonce: numberToRpcQuantity(txParams.nonce), + value: numberToRpcQuantity(txParams.value), + gas: numberToRpcQuantity(txParams.gasLimit), + gasPrice: numberToRpcQuantity(txParams.gasPrice) + }, + "Transaction reverted without a reason" + ); + + const tx = await this.provider.send("eth_getTransactionByHash", [ + txHash + ]); + const block = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(1), + false + ]); + + assertTransaction(tx, txHash, txParams, 1, block.hash, 0); + }); + }); + + describe("eth_getTransactionCount", async function() { + it("Should return 0 for random accounts", async function() { + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + zeroAddress() + ]), + 0 + ); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + "0x0000000000000000000000000000000000000001" + ]), + 0 + ); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + "0x0001231287316387168230000000000000000001" + ]), + 0 + ); + }); + + it("Should return the updated count after a transaction is made", async function() { + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[0] + ]), + 0 + ); + + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + } + ]); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[0] + ]), + 1 + ); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[1] + ]), + 0 + ); + + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + } + ]); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[0] + ]), + 1 + ); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[1] + ]), + 1 + ); + }); + + it("Should not be affected by calls", async function() { + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[0] + ]), + 0 + ); + + await this.provider.send("eth_call", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[1], + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + } + ]); + + assertQuantity( + await this.provider.send("eth_getTransactionCount", [ + DEFAULT_ACCOUNTS_ADDRESSES[0] + ]), + 0 + ); + }); + }); + + describe("eth_getTransactionReceipt", async function() { + it("should return null for unknown txs", async function() { + const receipt = await this.provider.send( + "eth_getTransactionReceipt", + [ + "0x1234567876543234567876543456765434567aeaeaed67616732632762762373" + ] + ); + + assert.isNull(receipt); + }); + + it("should return the right values for successful txs", async function() { + const contractAddress = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const txHash = await this.provider.send("eth_sendTransaction", [ + { + to: contractAddress, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: `${EXAMPLE_CONTRACT.selectors.modifiesState}000000000000000000000000000000000000000000000000000000000000000a` + } + ]); + + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(2), false] + ); + + const receipt: RpcTransactionReceiptOutput = await this.provider.send( + "eth_getTransactionReceipt", + [txHash] + ); + + assert.equal(receipt.blockHash, block.hash); + assertQuantity(receipt.blockNumber, 2); + assert.isNull(receipt.contractAddress); + assert.equal(receipt.cumulativeGasUsed, receipt.gasUsed); + assert.equal(receipt.from, DEFAULT_ACCOUNTS_ADDRESSES[0]); + assertQuantity(receipt.status, 1); + assert.equal(receipt.logs.length, 1); + assert.equal(receipt.to, contractAddress); + assert.equal(receipt.transactionHash, txHash); + assertQuantity(receipt.transactionIndex, 0); + + const log = receipt.logs[0]; + + assert.isFalse(log.removed); + assertQuantity(log.logIndex, 0); + assertQuantity(log.transactionIndex, 0); + assert.equal(log.transactionHash, txHash); + assert.equal(log.blockHash, block.hash); + assertQuantity(log.blockNumber, 2); + assert.equal(log.address, contractAddress); + + // The new value of i is not indexed + assert.equal( + log.data, + "0x000000000000000000000000000000000000000000000000000000000000000a" + ); + + assert.deepEqual(log.topics, [ + EXAMPLE_CONTRACT.topics.StateModified[0], + "0x0000000000000000000000000000000000000000000000000000000000000000" + ]); + }); - describe("eth_unsubscribe", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_unsubscribe"); + it("should return the receipt for txs that were executed and failed", async function() { + const txParams: TransactionParams = { + to: toBuffer([]), + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer("0x60006000fd"), + nonce: new BN(0), + value: new BN(123), + gasLimit: new BN(250000), + gasPrice: new BN(23912) + }; + + const txHash = getSignedTxHash(txParams, 0); + + // Revert. This a deployment transaction that immediately reverts without a reason + await assertTransactionFailure( + this.provider, + { + from: bufferToHex(txParams.from), + data: bufferToHex(txParams.data), + nonce: numberToRpcQuantity(txParams.nonce), + value: numberToRpcQuantity(txParams.value), + gas: numberToRpcQuantity(txParams.gasLimit), + gasPrice: numberToRpcQuantity(txParams.gasPrice) + }, + "Transaction reverted without a reason" + ); + + const receipt = await this.provider.send( + "eth_getTransactionReceipt", + [txHash] + ); + + assert.isNotNull(receipt); + }); + }); + + describe("eth_getUncleByBlockHashAndIndex", async function() { + it("is not supported", async function() { + await assertNotSupported( + this.provider, + "eth_getUncleByBlockHashAndIndex" + ); + }); + }); + + describe("eth_getUncleByBlockNumberAndIndex", async function() { + it("is not supported", async function() { + await assertNotSupported( + this.provider, + "eth_getUncleByBlockNumberAndIndex" + ); + }); + }); + + describe("eth_getUncleCountByBlockHash", async function() { + it("is not supported", async function() { + await assertNotSupported( + this.provider, + "eth_getUncleCountByBlockHash" + ); + }); + }); + + describe("eth_getUncleCountByBlockNumber", async function() { + it("is not supported", async function() { + await assertNotSupported( + this.provider, + "eth_getUncleCountByBlockNumber" + ); + }); + }); + + describe("eth_getWork", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_getWork"); + }); + }); + + describe("eth_hashrate", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_hashrate"); + }); + }); + + describe("eth_mining", async function() { + it("should return false", async function() { + assert.deepEqual(await this.provider.send("eth_mining"), false); + }); + }); + + describe("eth_newPendingTransactionFilter", async function() { + it("is not supported", async function() { + await assertNotSupported( + this.provider, + "eth_newPendingTransactionFilter" + ); + }); + }); + + describe("eth_pendingTransactions", async function() { + it("should return an empty array, as there is no pending transactions support", async function() { + assert.deepEqual( + await this.provider.send("eth_pendingTransactions"), + [] + ); + }); + }); + + describe("eth_protocolVersion", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_protocolVersion"); + }); + }); + + describe("eth_sendRawTransaction", async function() { + it("Should throw if the data isn't a proper transaction", async function() { + await assertInvalidInputError( + this.provider, + "eth_sendRawTransaction", + ["0x123456"], + "Invalid transaction" + ); + }); + + it("Should throw if the signature is invalid", async function() { + await assertInvalidInputError( + this.provider, + "eth_sendRawTransaction", + [ + // This transaction was obtained with eth_sendTransaction, and its r value was wiped + "0xf3808501dcd6500083015f9080800082011a80a00dbd1a45b7823be518540ca77afb7178a470b8054281530a6cdfd0ad3328cf96" + ], + "Invalid transaction signature" + ); + }); + + it("Should throw if the signature is invalid but for another chain (EIP155)", async function() { + await assertInvalidInputError( + this.provider, + "eth_sendRawTransaction", + [ + "0xf86e820a0f843b9aca0083030d40941aad5e821c667e909c16a49363ca48f672b46c5d88169866e539efe0008025a07bc6a357d809c9d27f8f5a826861e7f9b4b7c9cff4f91f894b88e98212069b3da05dbadbdfa67bab1d76d2d81e33d90162d508431362331f266dd6aa0cb4b525aa" + ], + "Incompatible EIP155-based" + ); + }); + + it("Should send the raw transaction", async function() { + // This test is a copy of: Should work with just from and data + + const hash = await this.provider.send("eth_sendRawTransaction", [ + "0xf853808501dcd6500083015f9080800082011aa09c8def73818f79b6493b7a3f7ce47b557694ca195d1b54bb74e3d98990041b44a00dbd1a45b7823be518540ca77afb7178a470b8054281530a6cdfd0ad3328cf96" + ]); + + const receipt = await this.provider.send( + "eth_getTransactionReceipt", + [hash] + ); + + const receiptFromGeth = { + blockHash: + "0x01490da2af913e9a868430b7b4c5060fc29cbdb1692bb91d3c72c734acd73bc8", + blockNumber: "0x6", + contractAddress: "0x6ea84fcbef576d66896dc2c32e139b60e641170c", + cumulativeGasUsed: "0xcf0c", + from: "0xda4585f6e68ed1cdfdad44a08dbe3979ec74ad8f", + gasUsed: "0xcf0c", + logs: [], + logsBloom: + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + status: "0x1", + to: null, + transactionHash: + "0xbd24cbe9c1633b98e61d93619230341141d2cff49470ed6afa739cee057fd0aa", + transactionIndex: "0x0" + }; + + assertReceiptMatchesGethOne(receipt, receiptFromGeth, 1); + }); + }); + + describe("eth_sendTransaction", async function() { + // Because of the way we are testing this (i.e. integration testing) it's almost impossible to + // fully test this method in a reasonable amount of time. This is because it executes the core + // of Ethereum: its state transition function. + // + // We have mostly test about logic added on top of that, and will add new ones whenever + // suitable. This is approximately the same as assuming that ethereumjs-vm is correct, which + // seems reasonable, and if it weren't we should address the issues there. + + describe("Params validation", function() { + it("Should fail if the account is not managed by the provider", async function() { + await assertTransactionFailure( + this.provider, + { + from: zeroAddress(), + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + }, + "unknown account" + ); + }); + + it("Should fail if sending to the null address without data", async function() { + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0] + }, + "contract creation without any data provided" + ); + + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + }, + "contract creation without any data provided" + ); + + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: "0x", + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + }, + "contract creation without any data provided" + ); + }); + }); + + it("Should work with just from and data", async function() { + const hash = await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: "0x00" + } + ]); + + const receipt = await this.provider.send( + "eth_getTransactionReceipt", + [hash] + ); + + const receiptFromGeth = { + blockHash: + "0x01490da2af913e9a868430b7b4c5060fc29cbdb1692bb91d3c72c734acd73bc8", + blockNumber: "0x6", + contractAddress: "0x6ea84fcbef576d66896dc2c32e139b60e641170c", + cumulativeGasUsed: "0xcf0c", + from: "0xda4585f6e68ed1cdfdad44a08dbe3979ec74ad8f", + gasUsed: "0xcf0c", + logs: [], + logsBloom: + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + status: "0x1", + to: null, + transactionHash: + "0xbd24cbe9c1633b98e61d93619230341141d2cff49470ed6afa739cee057fd0aa", + transactionIndex: "0x0" + }; + + assertReceiptMatchesGethOne(receipt, receiptFromGeth, 1); + }); + + it("Should throw if the transaction fails", async function() { + // Not enough gas + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: zeroAddress(), + gas: numberToRpcQuantity(1) + }, + "Transaction requires at least 21000 gas but got 1" + ); + + // Not enough balance + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: zeroAddress(), + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(DEFAULT_ACCOUNTS_BALANCES[0]) + }, + "sender doesn't have enough funds to send tx" + ); + + // Gas is larger than block gas limit + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: zeroAddress(), + gas: numberToRpcQuantity(DEFAULT_BLOCK_GAS_LIMIT + 1) + }, + `Transaction gas limit is ${DEFAULT_BLOCK_GAS_LIMIT + + 1} and exceeds block gas limit of ${DEFAULT_BLOCK_GAS_LIMIT}` + ); + + // Invalid opcode. We try to deploy a contract with an invalid opcode in the deployment code + // The transaction gets executed anyway, so the account is updated + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: "0xAA" + }, + "Transaction failed: revert" + ); + + // Out of gas. This a deployment transaction that pushes 0x00 multiple times + // The transaction gets executed anyway, so the account is updated. + // + // Note: this test is pretty fragile, as the tx needs to have enough gas + // to pay for the calldata, but not enough to execute. This costs changed + // with istanbul, and may change again in the future. + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: + "0x6000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000", + gas: numberToRpcQuantity(53500) + }, + "out of gas" + ); + + // Invalid nonce + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + nonce: numberToRpcQuantity(1) + }, + "Invalid nonce. Expected 2 but got 1" + ); + + // Revert. This a deployment transaction that immediately reverts without a reason + // The transaction gets executed anyway, so the account is updated + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: "0x60006000fd" + }, + "Transaction reverted without a reason" + ); + + // This is a contract that reverts with A in its constructor + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: + "0x6080604052348015600f57600080fd5b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260018152602001807f410000000000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fdfe" + }, + "revert A" + ); + }); + + it("Should fail if a successful tx is sent more than once", async function() { + const hash = await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + nonce: numberToRpcQuantity(0) + } + ]); + + await assertTransactionFailure( + this.provider, + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + nonce: numberToRpcQuantity(0) + }, + `known transaction: ${bufferToHex(hash)}` + ); + }); + + it("should accept a failed transaction if it eventually becomes valid", async function() { + const txParams = { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + nonce: numberToRpcQuantity(1) + }; + + // This transaction is invalid now, because of its nonce + await assertTransactionFailure(this.provider, txParams); + + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: DEFAULT_ACCOUNTS_ADDRESSES[0], + nonce: numberToRpcQuantity(0) + } + ]); + + // The transaction is now valid + const hash = await this.provider.send("eth_sendTransaction", [ + txParams + ]); + + // It should throw now + await assertTransactionFailure( + this.provider, + txParams, + `known transaction: ${bufferToHex(hash)}` + ); + }); + }); + + describe("eth_sign", async function() { + // TODO: Test this. Note that it's implementation is tested in one of + // our provider wrappers, but re-test it here anyway. + }); + + describe("eth_signTransaction", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_signTransaction"); + }); + }); + + describe("eth_signTypedData", async function() { + // TODO: Test this. Note that it just forwards to/from eth-sign-util + }); + + describe("eth_submitHashrate", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_submitHashrate"); + }); + }); + + describe("eth_submitWork", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_submitWork"); + }); + }); + + describe("eth_subscribe", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_subscribe"); + }); + }); + + describe("eth_syncing", async function() { + it("Should return false", async function() { + assert.deepEqual(await this.provider.send("eth_syncing"), false); + }); + }); + + describe("eth_unsubscribe", async function() { + it("is not supported", async function() { + await assertNotSupported(this.provider, "eth_unsubscribe"); + }); + }); }); }); }); diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts index 9a8ade0c96..5efcb10284 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts @@ -1,5 +1,5 @@ import { assert } from "chai"; -import { BN, zeroAddress } from "ethereumjs-util"; +import { zeroAddress } from "ethereumjs-util"; import { bufferToRpcData, @@ -13,453 +13,485 @@ import { } from "../../helpers/assertions"; import { quantityToNumber } from "../../helpers/conversions"; import { setCWD } from "../../helpers/cwd"; -import { useProvider } from "../../helpers/useProvider"; +import { PROVIDERS } from "../../helpers/useProvider"; describe("Evm module", function() { - setCWD(); - useProvider(); - - describe("evm_increaseTime", async function() { - it("should increase the offset of time used for block timestamps", async function() { - const accounts = await this.provider.send("eth_accounts"); - const burnTxParams = { - from: accounts[0], - to: zeroAddress(), - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(21000), - gasPrice: numberToRpcQuantity(1) - }; - - const firstBlock = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(0), - false - ]); - - await this.provider.send("evm_increaseTime", [123]); - - await this.provider.send("eth_sendTransaction", [burnTxParams]); - - const secondBlock = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(1), - false - ]); - - await this.provider.send("evm_increaseTime", [456]); - - await this.provider.send("eth_sendTransaction", [burnTxParams]); - - const thirdBlock = await this.provider.send("eth_getBlockByNumber", [ - numberToRpcQuantity(2), - false - ]); - - const firstTimestamp = quantityToNumber(firstBlock.timestamp); - const secondTimestamp = quantityToNumber(secondBlock.timestamp); - const thirdTimestamp = quantityToNumber(thirdBlock.timestamp); - - assert.isAtLeast(secondTimestamp - firstTimestamp, 123); - assert.isAtLeast(thirdTimestamp - secondTimestamp, 456); - }); - - it("should return the total offset as a decimal string, not a QUANTITY", async function() { - let totalOffset = await this.provider.send("evm_increaseTime", [123]); - assert.isString(totalOffset); - assert.strictEqual(parseInt(totalOffset, 10), 123); - - totalOffset = await this.provider.send("evm_increaseTime", [3456789]); - assert.isString(totalOffset); - assert.strictEqual(parseInt(totalOffset, 10), 123 + 3456789); - }); - - it("should expect an actual number as its first param, not a hex string", async function() { - await assertInvalidArgumentsError(this.provider, "evm_increaseTime", [ - numberToRpcQuantity(123) - ]); - }); - }); - - describe("evm_mine", async function() { - it("should mine an empty block", async function() { - await this.provider.send("evm_mine"); - - const block: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(1), false] - ); - - assert.isEmpty(block.transactions); - - await this.provider.send("evm_mine"); - - const block2: RpcBlockOutput = await this.provider.send( - "eth_getBlockByNumber", - [numberToRpcQuantity(2), false] - ); - - assert.isEmpty(block2.transactions); - }); - }); - - describe("Snapshot functionality", function() { - describe("evm_snapshot", async function() { - it("returns the snapshot id starting at 1", async function() { - const id1: string = await this.provider.send("evm_snapshot", []); - const id2: string = await this.provider.send("evm_snapshot", []); - const id3: string = await this.provider.send("evm_snapshot", []); - - assert.equal(id1, "0x1"); - assert.equal(id2, "0x2"); - assert.equal(id3, "0x3"); - }); - - it("Doesn't repeat snapshot ids after revert is called", async function() { - const id1: string = await this.provider.send("evm_snapshot", []); - const reverted: boolean = await this.provider.send("evm_revert", [id1]); - const id2: string = await this.provider.send("evm_snapshot", []); - - assert.equal(id1, "0x1"); - assert.isTrue(reverted); - assert.equal(id2, "0x2"); - }); - }); - - describe("evm_revert", async function() { - it("Returns false for non-existing ids", async function() { - const reverted1: boolean = await this.provider.send("evm_revert", [ - "0x1" - ]); - const reverted2: boolean = await this.provider.send("evm_revert", [ - "0x2" - ]); - const reverted3: boolean = await this.provider.send("evm_revert", [ - "0x0" - ]); - - assert.isFalse(reverted1); - assert.isFalse(reverted2); - assert.isFalse(reverted3); - }); - - it("Returns false for already reverted ids", async function() { - const id1: string = await this.provider.send("evm_snapshot", []); - const reverted: boolean = await this.provider.send("evm_revert", [id1]); - const reverted2: boolean = await this.provider.send("evm_revert", [ - id1 - ]); - - assert.isTrue(reverted); - assert.isFalse(reverted2); - }); - - it("Deletes previous blocks", async function() { - const snapshotId: string = await this.provider.send("evm_snapshot", []); - const initialLatestBlock = await this.provider.send( - "eth_getBlockByNumber", - ["latest", false] - ); - - await this.provider.send("evm_mine"); - await this.provider.send("evm_mine"); - await this.provider.send("evm_mine"); - await this.provider.send("evm_mine"); - const latestBlockBeforeReverting = await this.provider.send( - "eth_getBlockByNumber", - ["latest", false] - ); - - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - const newLatestBlock = await this.provider.send( - "eth_getBlockByNumber", - ["latest", false] - ); - assert.equal(newLatestBlock.hash, initialLatestBlock.hash); - - const blockByHash = await this.provider.send("eth_getBlockByHash", [ - bufferToRpcData(latestBlockBeforeReverting.hash), - false - ]); - assert.isNull(blockByHash); - - const blockByNumber = await this.provider.send("eth_getBlockByNumber", [ - latestBlockBeforeReverting.number, - false - ]); - assert.isNull(blockByNumber); - }); - - it("Deletes previous transactions", async function() { - const [from] = await this.provider.send("eth_accounts"); - - const snapshotId: string = await this.provider.send("evm_snapshot", []); - - const txHash = await this.provider.send("eth_sendTransaction", [ - { - from, - to: "0x1111111111111111111111111111111111111111", + PROVIDERS.forEach(provider => { + describe(`Provider ${provider.name}`, function() { + setCWD(); + provider.useProvider(); + + describe("evm_increaseTime", async function() { + it("should increase the offset of time used for block timestamps", async function() { + const accounts = await this.provider.send("eth_accounts"); + const burnTxParams = { + from: accounts[0], + to: zeroAddress(), value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(100000), - gasPrice: numberToRpcQuantity(1), - nonce: numberToRpcQuantity(0) - } - ]); - - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - const txHashAfter = await this.provider.send( - "eth_getTransactionByHash", - [txHash] - ); - assert.isNull(txHashAfter); - }); - - it("Allows resending the same tx after a revert", async function() { - const [from] = await this.provider.send("eth_accounts"); - - const snapshotId: string = await this.provider.send("evm_snapshot", []); - - const txParams = { - from, - to: "0x1111111111111111111111111111111111111111", - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(100000), - gasPrice: numberToRpcQuantity(1), - nonce: numberToRpcQuantity(0) - }; - - const txHash = await this.provider.send("eth_sendTransaction", [ - txParams - ]); - - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - const txHash2 = await this.provider.send("eth_sendTransaction", [ - txParams - ]); - - assert.equal(txHash2, txHash); - }); - - it("Deletes the used snapshot and the following ones", async function() { - const snapshotId1: string = await this.provider.send( - "evm_snapshot", - [] - ); - const snapshotId2: string = await this.provider.send( - "evm_snapshot", - [] - ); - const snapshotId3: string = await this.provider.send( - "evm_snapshot", - [] - ); - - const revertedTo2: boolean = await this.provider.send("evm_revert", [ - snapshotId2 - ]); - assert.isTrue(revertedTo2); - - const revertedTo3: boolean = await this.provider.send("evm_revert", [ - snapshotId3 - ]); - // snapshot 3 didn't exist anymore - assert.isFalse(revertedTo3); - - const revertedTo1: boolean = await this.provider.send("evm_revert", [ - snapshotId1 - ]); - // snapshot 1 still existed - assert.isTrue(revertedTo1); - }); - - it("Resets the blockchain so that new blocks are added with the right numbers", async function() { - await this.provider.send("evm_mine"); - await this.provider.send("evm_mine"); - - await assertLatestBlockNumber(this.provider, 2); - - const snapshotId1: string = await this.provider.send( - "evm_snapshot", - [] - ); - - await this.provider.send("evm_mine"); + gas: numberToRpcQuantity(21000), + gasPrice: numberToRpcQuantity(1) + }; - await assertLatestBlockNumber(this.provider, 3); + const firstBlock = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(0), + false + ]); - const revertedTo1: boolean = await this.provider.send("evm_revert", [ - snapshotId1 - ]); - assert.isTrue(revertedTo1); + await this.provider.send("evm_increaseTime", [123]); - await assertLatestBlockNumber(this.provider, 2); + await this.provider.send("eth_sendTransaction", [burnTxParams]); - await this.provider.send("evm_mine"); + const secondBlock = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(1), + false + ]); - await assertLatestBlockNumber(this.provider, 3); + await this.provider.send("evm_increaseTime", [456]); - await this.provider.send("evm_mine"); + await this.provider.send("eth_sendTransaction", [burnTxParams]); - const snapshotId2: string = await this.provider.send( - "evm_snapshot", - [] - ); + const thirdBlock = await this.provider.send("eth_getBlockByNumber", [ + numberToRpcQuantity(2), + false + ]); - await this.provider.send("evm_mine"); + const firstTimestamp = quantityToNumber(firstBlock.timestamp); + const secondTimestamp = quantityToNumber(secondBlock.timestamp); + const thirdTimestamp = quantityToNumber(thirdBlock.timestamp); - const snapshotId3: string = await this.provider.send( - "evm_snapshot", - [] - ); + assert.isAtLeast(secondTimestamp - firstTimestamp, 123); + assert.isAtLeast(thirdTimestamp - secondTimestamp, 456); + }); - await this.provider.send("evm_mine"); + it("should return the total offset as a decimal string, not a QUANTITY", async function() { + let totalOffset = await this.provider.send("evm_increaseTime", [123]); + assert.isString(totalOffset); + assert.strictEqual(parseInt(totalOffset, 10), 123); - await assertLatestBlockNumber(this.provider, 6); + totalOffset = await this.provider.send("evm_increaseTime", [3456789]); + assert.isString(totalOffset); + assert.strictEqual(parseInt(totalOffset, 10), 123 + 3456789); + }); - const revertedTo2: boolean = await this.provider.send("evm_revert", [ - snapshotId2 - ]); - assert.isTrue(revertedTo2); - - await assertLatestBlockNumber(this.provider, 4); - }); - - it("Resets the date to the right time", async function() { - // First, we increase the time by 100 sec - await this.provider.send("evm_increaseTime", [100]); - const startDate = new Date(); - await this.provider.send("evm_mine"); - const snapshotId: string = await this.provider.send("evm_snapshot", []); - - const snapshotedBlock = await this.provider.send( - "eth_getBlockByNumber", - ["latest", false] - ); - - assert.equal( - snapshotedBlock.timestamp, - numberToRpcQuantity(Math.ceil(startDate.valueOf() / 1000) + 100) - ); - - // TODO: Somehow test this without a sleep - await new Promise(resolve => setTimeout(resolve, 2000)); - - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - await this.provider.send("evm_mine"); - const afterRevertBlock = await this.provider.send( - "eth_getBlockByNumber", - ["latest", false] - ); - - assert.equal( - afterRevertBlock.timestamp, - numberToRpcQuantity( - rpcQuantityToNumber(snapshotedBlock.timestamp) + 1 - ) - ); + it("should expect an actual number as its first param, not a hex string", async function() { + await assertInvalidArgumentsError(this.provider, "evm_increaseTime", [ + numberToRpcQuantity(123) + ]); + }); }); - it("Restores the previous state", async function() { - // This is a very coarse test, as we know that the entire state is - // managed by the vm, and is restored as a whole - const [from] = await this.provider.send("eth_accounts"); - - const balanceBeforeTx = await this.provider.send("eth_getBalance", [ - from - ]); - - const snapshotId: string = await this.provider.send("evm_snapshot", []); + describe("evm_mine", async function() { + it("should mine an empty block", async function() { + await this.provider.send("evm_mine"); - const txParams = { - from, - to: "0x1111111111111111111111111111111111111111", - value: numberToRpcQuantity(1), - gas: numberToRpcQuantity(100000), - gasPrice: numberToRpcQuantity(1), - nonce: numberToRpcQuantity(0) - }; + const block: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(1), false] + ); - await this.provider.send("eth_sendTransaction", [txParams]); + assert.isEmpty(block.transactions); - const balanceAfterTx = await this.provider.send("eth_getBalance", [ - from - ]); + await this.provider.send("evm_mine"); - assert.notEqual(balanceAfterTx, balanceBeforeTx); + const block2: RpcBlockOutput = await this.provider.send( + "eth_getBlockByNumber", + [numberToRpcQuantity(2), false] + ); - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - const balanceAfterRevert = await this.provider.send("eth_getBalance", [ - from - ]); - - assert.equal(balanceAfterRevert, balanceBeforeTx); + assert.isEmpty(block2.transactions); + }); }); - it("Should restore block filters", async function() { - await this.provider.send("evm_mine", []); - - const firstId = await this.provider.send("eth_newBlockFilter", []); - assert.equal(firstId, "0x1"); - - const firstFilterInitialChanges = await this.provider.send( - "eth_getFilterChanges", - [firstId] - ); - - assert.lengthOf(firstFilterInitialChanges, 1); - - await this.provider.send("evm_mine", []); - - const snapshotId: string = await this.provider.send("evm_snapshot", []); - - await this.provider.send("evm_mine", []); - - const secondId = await this.provider.send("eth_newBlockFilter", []); - assert.equal(secondId, "0x2"); - - const firstFilterSecondChanges = await this.provider.send( - "eth_getFilterChanges", - [firstId] - ); - assert.lengthOf(firstFilterSecondChanges, 2); - - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - const secondIdAfterRevert = await this.provider.send( - "eth_newBlockFilter", - [] - ); - assert.equal(secondIdAfterRevert, "0x2"); - - const firstFilterSecondChangesAfterRevert = await this.provider.send( - "eth_getFilterChanges", - [firstId] - ); - assert.lengthOf(firstFilterSecondChangesAfterRevert, 1); - assert.equal( - firstFilterSecondChangesAfterRevert[0], - firstFilterSecondChanges[0] - ); + describe("Snapshot functionality", function() { + describe("evm_snapshot", async function() { + it("returns the snapshot id starting at 1", async function() { + const id1: string = await this.provider.send("evm_snapshot", []); + const id2: string = await this.provider.send("evm_snapshot", []); + const id3: string = await this.provider.send("evm_snapshot", []); + + assert.equal(id1, "0x1"); + assert.equal(id2, "0x2"); + assert.equal(id3, "0x3"); + }); + + it("Doesn't repeat snapshot ids after revert is called", async function() { + const id1: string = await this.provider.send("evm_snapshot", []); + const reverted: boolean = await this.provider.send("evm_revert", [ + id1 + ]); + const id2: string = await this.provider.send("evm_snapshot", []); + + assert.equal(id1, "0x1"); + assert.isTrue(reverted); + assert.equal(id2, "0x2"); + }); + }); + + describe("evm_revert", async function() { + it("Returns false for non-existing ids", async function() { + const reverted1: boolean = await this.provider.send("evm_revert", [ + "0x1" + ]); + const reverted2: boolean = await this.provider.send("evm_revert", [ + "0x2" + ]); + const reverted3: boolean = await this.provider.send("evm_revert", [ + "0x0" + ]); + + assert.isFalse(reverted1); + assert.isFalse(reverted2); + assert.isFalse(reverted3); + }); + + it("Returns false for already reverted ids", async function() { + const id1: string = await this.provider.send("evm_snapshot", []); + const reverted: boolean = await this.provider.send("evm_revert", [ + id1 + ]); + const reverted2: boolean = await this.provider.send("evm_revert", [ + id1 + ]); + + assert.isTrue(reverted); + assert.isFalse(reverted2); + }); + + it("Deletes previous blocks", async function() { + const snapshotId: string = await this.provider.send( + "evm_snapshot", + [] + ); + const initialLatestBlock = await this.provider.send( + "eth_getBlockByNumber", + ["latest", false] + ); + + await this.provider.send("evm_mine"); + await this.provider.send("evm_mine"); + await this.provider.send("evm_mine"); + await this.provider.send("evm_mine"); + const latestBlockBeforeReverting = await this.provider.send( + "eth_getBlockByNumber", + ["latest", false] + ); + + const reverted: boolean = await this.provider.send("evm_revert", [ + snapshotId + ]); + assert.isTrue(reverted); + + const newLatestBlock = await this.provider.send( + "eth_getBlockByNumber", + ["latest", false] + ); + assert.equal(newLatestBlock.hash, initialLatestBlock.hash); + + const blockByHash = await this.provider.send("eth_getBlockByHash", [ + bufferToRpcData(latestBlockBeforeReverting.hash), + false + ]); + assert.isNull(blockByHash); + + const blockByNumber = await this.provider.send( + "eth_getBlockByNumber", + [latestBlockBeforeReverting.number, false] + ); + assert.isNull(blockByNumber); + }); + + it("Deletes previous transactions", async function() { + const [from] = await this.provider.send("eth_accounts"); + + const snapshotId: string = await this.provider.send( + "evm_snapshot", + [] + ); + + const txHash = await this.provider.send("eth_sendTransaction", [ + { + from, + to: "0x1111111111111111111111111111111111111111", + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(100000), + gasPrice: numberToRpcQuantity(1), + nonce: numberToRpcQuantity(0) + } + ]); + + const reverted: boolean = await this.provider.send("evm_revert", [ + snapshotId + ]); + assert.isTrue(reverted); + + const txHashAfter = await this.provider.send( + "eth_getTransactionByHash", + [txHash] + ); + assert.isNull(txHashAfter); + }); + + it("Allows resending the same tx after a revert", async function() { + const [from] = await this.provider.send("eth_accounts"); + + const snapshotId: string = await this.provider.send( + "evm_snapshot", + [] + ); + + const txParams = { + from, + to: "0x1111111111111111111111111111111111111111", + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(100000), + gasPrice: numberToRpcQuantity(1), + nonce: numberToRpcQuantity(0) + }; + + const txHash = await this.provider.send("eth_sendTransaction", [ + txParams + ]); + + const reverted: boolean = await this.provider.send("evm_revert", [ + snapshotId + ]); + assert.isTrue(reverted); + + const txHash2 = await this.provider.send("eth_sendTransaction", [ + txParams + ]); + + assert.equal(txHash2, txHash); + }); + + it("Deletes the used snapshot and the following ones", async function() { + const snapshotId1: string = await this.provider.send( + "evm_snapshot", + [] + ); + const snapshotId2: string = await this.provider.send( + "evm_snapshot", + [] + ); + const snapshotId3: string = await this.provider.send( + "evm_snapshot", + [] + ); + + const revertedTo2: boolean = await this.provider.send( + "evm_revert", + [snapshotId2] + ); + assert.isTrue(revertedTo2); + + const revertedTo3: boolean = await this.provider.send( + "evm_revert", + [snapshotId3] + ); + // snapshot 3 didn't exist anymore + assert.isFalse(revertedTo3); + + const revertedTo1: boolean = await this.provider.send( + "evm_revert", + [snapshotId1] + ); + // snapshot 1 still existed + assert.isTrue(revertedTo1); + }); + + it("Resets the blockchain so that new blocks are added with the right numbers", async function() { + await this.provider.send("evm_mine"); + await this.provider.send("evm_mine"); + + await assertLatestBlockNumber(this.provider, 2); + + const snapshotId1: string = await this.provider.send( + "evm_snapshot", + [] + ); + + await this.provider.send("evm_mine"); + + await assertLatestBlockNumber(this.provider, 3); + + const revertedTo1: boolean = await this.provider.send( + "evm_revert", + [snapshotId1] + ); + assert.isTrue(revertedTo1); + + await assertLatestBlockNumber(this.provider, 2); + + await this.provider.send("evm_mine"); + + await assertLatestBlockNumber(this.provider, 3); + + await this.provider.send("evm_mine"); + + const snapshotId2: string = await this.provider.send( + "evm_snapshot", + [] + ); + + await this.provider.send("evm_mine"); + + const snapshotId3: string = await this.provider.send( + "evm_snapshot", + [] + ); + + await this.provider.send("evm_mine"); + + await assertLatestBlockNumber(this.provider, 6); + + const revertedTo2: boolean = await this.provider.send( + "evm_revert", + [snapshotId2] + ); + assert.isTrue(revertedTo2); + + await assertLatestBlockNumber(this.provider, 4); + }); + + it("Resets the date to the right time", async function() { + // First, we increase the time by 100 sec + await this.provider.send("evm_increaseTime", [100]); + const startDate = new Date(); + await this.provider.send("evm_mine"); + const snapshotId: string = await this.provider.send( + "evm_snapshot", + [] + ); + + const snapshotedBlock = await this.provider.send( + "eth_getBlockByNumber", + ["latest", false] + ); + + assert.equal( + snapshotedBlock.timestamp, + numberToRpcQuantity(Math.ceil(startDate.valueOf() / 1000) + 100) + ); + + // TODO: Somehow test this without a sleep + await new Promise(resolve => setTimeout(resolve, 2000)); + + const reverted: boolean = await this.provider.send("evm_revert", [ + snapshotId + ]); + assert.isTrue(reverted); + + await this.provider.send("evm_mine"); + const afterRevertBlock = await this.provider.send( + "eth_getBlockByNumber", + ["latest", false] + ); + + assert.equal( + afterRevertBlock.timestamp, + numberToRpcQuantity( + rpcQuantityToNumber(snapshotedBlock.timestamp) + 1 + ) + ); + }); + + it("Restores the previous state", async function() { + // This is a very coarse test, as we know that the entire state is + // managed by the vm, and is restored as a whole + const [from] = await this.provider.send("eth_accounts"); + + const balanceBeforeTx = await this.provider.send("eth_getBalance", [ + from + ]); + + const snapshotId: string = await this.provider.send( + "evm_snapshot", + [] + ); + + const txParams = { + from, + to: "0x1111111111111111111111111111111111111111", + value: numberToRpcQuantity(1), + gas: numberToRpcQuantity(100000), + gasPrice: numberToRpcQuantity(1), + nonce: numberToRpcQuantity(0) + }; + + await this.provider.send("eth_sendTransaction", [txParams]); + + const balanceAfterTx = await this.provider.send("eth_getBalance", [ + from + ]); + + assert.notEqual(balanceAfterTx, balanceBeforeTx); + + const reverted: boolean = await this.provider.send("evm_revert", [ + snapshotId + ]); + assert.isTrue(reverted); + + const balanceAfterRevert = await this.provider.send( + "eth_getBalance", + [from] + ); + + assert.equal(balanceAfterRevert, balanceBeforeTx); + }); + + it("Should restore block filters", async function() { + await this.provider.send("evm_mine", []); + + const firstId = await this.provider.send("eth_newBlockFilter", []); + assert.equal(firstId, "0x1"); + + const firstFilterInitialChanges = await this.provider.send( + "eth_getFilterChanges", + [firstId] + ); + + assert.lengthOf(firstFilterInitialChanges, 1); + + await this.provider.send("evm_mine", []); + + const snapshotId: string = await this.provider.send( + "evm_snapshot", + [] + ); + + await this.provider.send("evm_mine", []); + + const secondId = await this.provider.send("eth_newBlockFilter", []); + assert.equal(secondId, "0x2"); + + const firstFilterSecondChanges = await this.provider.send( + "eth_getFilterChanges", + [firstId] + ); + assert.lengthOf(firstFilterSecondChanges, 2); + + const reverted: boolean = await this.provider.send("evm_revert", [ + snapshotId + ]); + assert.isTrue(reverted); + + const secondIdAfterRevert = await this.provider.send( + "eth_newBlockFilter", + [] + ); + assert.equal(secondIdAfterRevert, "0x2"); + + const firstFilterSecondChangesAfterRevert = await this.provider.send( + "eth_getFilterChanges", + [firstId] + ); + assert.lengthOf(firstFilterSecondChangesAfterRevert, 1); + assert.equal( + firstFilterSecondChangesAfterRevert[0], + firstFilterSecondChanges[0] + ); + }); + }); }); }); }); diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/net.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/net.ts index 82bec539bf..0045d1fef1 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/net.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/net.ts @@ -2,33 +2,37 @@ import { assert } from "chai"; import { numberToRpcQuantity } from "../../../../../src/internal/buidler-evm/provider/output"; import { setCWD } from "../../helpers/cwd"; -import { useProvider } from "../../helpers/useProvider"; +import { PROVIDERS } from "../../helpers/useProvider"; describe("Net module", function() { - setCWD(); - useProvider(); + PROVIDERS.forEach(provider => { + describe(`Provider ${provider.name}`, function() { + setCWD(); + provider.useProvider(); - describe("net_listening", async function() { - it("Should return true", async function() { - assert.isTrue(await this.provider.send("net_listening")); - }); - }); + describe("net_listening", async function() { + it("Should return true", async function() { + assert.isTrue(await this.provider.send("net_listening")); + }); + }); - describe("net_peerCount", async function() { - it("Should return 0", async function() { - assert.strictEqual( - await this.provider.send("net_peerCount"), - numberToRpcQuantity(0) - ); - }); - }); + describe("net_peerCount", async function() { + it("Should return 0", async function() { + assert.strictEqual( + await this.provider.send("net_peerCount"), + numberToRpcQuantity(0) + ); + }); + }); - describe("net_version", async function() { - it("Should return the network id as a decimal string, not QUANTITY", async function() { - assert.strictEqual( - await this.provider.send("net_version"), - this.common.networkId().toString() - ); + describe("net_version", async function() { + it("Should return the network id as a decimal string, not QUANTITY", async function() { + assert.strictEqual( + await this.provider.send("net_version"), + this.common.networkId().toString() + ); + }); + }); }); }); }); diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/web3.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/web3.ts index ecf50633c2..5c70863c44 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/web3.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/web3.ts @@ -3,31 +3,37 @@ import { keccak256, toBuffer } from "ethereumjs-util"; import { bufferToRpcData } from "../../../../../src/internal/buidler-evm/provider/output"; import { setCWD } from "../../helpers/cwd"; -import { useProvider } from "../../helpers/useProvider"; +import { PROVIDERS } from "../../helpers/useProvider"; describe("Web3 module", function() { - setCWD(); - useProvider(); + PROVIDERS.forEach(provider => { + describe(`Provider ${provider.name}`, function() { + setCWD(); + provider.useProvider(); - describe("web3_clientVersion", async function() { - // TODO: We skip this test for now. See the note in this call's - // implementation - it.skip("Should return the right value", async function() { - const res = await this.provider.send("web3_clientVersion"); - assert.isTrue(res.startsWith("BuidlerEVM/1.0.0-beta.13/ethereumjs-vm/4")); - }); - }); + describe("web3_clientVersion", async function() { + // TODO: We skip this test for now. See the note in this call's + // implementation + it.skip("Should return the right value", async function() { + const res = await this.provider.send("web3_clientVersion"); + assert.isTrue( + res.startsWith("BuidlerEVM/1.0.0-beta.13/ethereumjs-vm/4") + ); + }); + }); - describe("web3_sha3", async function() { - it("Should return the keccak256 of the input", async function() { - const data = "0x123a1b238123"; - const hashed = bufferToRpcData(keccak256(toBuffer(data))); + describe("web3_sha3", async function() { + it("Should return the keccak256 of the input", async function() { + const data = "0x123a1b238123"; + const hashed = bufferToRpcData(keccak256(toBuffer(data))); - const res = await this.provider.send("web3_sha3", [ - bufferToRpcData(toBuffer(data)) - ]); + const res = await this.provider.send("web3_sha3", [ + bufferToRpcData(toBuffer(data)) + ]); - assert.strictEqual(res, hashed); + assert.strictEqual(res, hashed); + }); + }); }); }); }); diff --git a/packages/buidler-core/test/internal/util/jsonrpc.ts b/packages/buidler-core/test/internal/util/jsonrpc.ts index 7522bcc2dc..a94378af34 100644 --- a/packages/buidler-core/test/internal/util/jsonrpc.ts +++ b/packages/buidler-core/test/internal/util/jsonrpc.ts @@ -31,7 +31,10 @@ describe("JSON-RPC", function() { }); it("Should validate the id field", function() { - assert.isFalse( + // Response without the id field is still a valid response, + // returned when an invalid JSON was provided as the request + // and id could not be parsed from it. + assert.isTrue( isValidJsonResponse({ jsonrpc: "2.0", result: "asd" From 770884844058fcd01ed8c165641fa2210dacb88a Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Tue, 11 Feb 2020 12:41:48 -0300 Subject: [PATCH 08/99] Fix json-rpc response and request's id types --- .../internal/buidler-evm/jsonrpc/handler.ts | 1 + .../buidler-core/src/internal/util/jsonrpc.ts | 12 +++++++---- .../test/internal/util/jsonrpc.ts | 21 ++++++++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index fb90619f5b..4a4c9a7ec4 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -103,6 +103,7 @@ export default class JsonRpcHandler { return { jsonrpc: "2.0", + id: null, error: { code: error.code, message: error.message diff --git a/packages/buidler-core/src/internal/util/jsonrpc.ts b/packages/buidler-core/src/internal/util/jsonrpc.ts index 8118cdb934..a3dcaee7a7 100644 --- a/packages/buidler-core/src/internal/util/jsonrpc.ts +++ b/packages/buidler-core/src/internal/util/jsonrpc.ts @@ -5,18 +5,18 @@ export interface JsonRpcRequest { jsonrpc: string; method: string; params: any[]; - id: number; + id: number | string; } interface SuccessfulJsonRpcResponse { jsonrpc: string; - id: number; + id: number | string; result: any; } export interface FailedJsonRpcResponse { jsonrpc: string; - id?: number; + id: number | string | null; error: { code: number; message: string; @@ -70,11 +70,15 @@ export function isValidJsonResponse(payload: any) { if ( typeof payload.id !== "number" && typeof payload.id !== "string" && - payload.id !== undefined + payload.id !== null ) { return false; } + if (payload.id === null && payload.error === undefined) { + return false; + } + if (payload.result === undefined && payload.error === undefined) { return false; } diff --git a/packages/buidler-core/test/internal/util/jsonrpc.ts b/packages/buidler-core/test/internal/util/jsonrpc.ts index a94378af34..a8e570414f 100644 --- a/packages/buidler-core/test/internal/util/jsonrpc.ts +++ b/packages/buidler-core/test/internal/util/jsonrpc.ts @@ -34,13 +34,32 @@ describe("JSON-RPC", function() { // Response without the id field is still a valid response, // returned when an invalid JSON was provided as the request // and id could not be parsed from it. - assert.isTrue( + assert.isFalse( isValidJsonResponse({ jsonrpc: "2.0", result: "asd" }) ); + assert.isTrue( + isValidJsonResponse({ + jsonrpc: "2.0", + id: null, + error: { + code: 123, + message: "asd" + } + }) + ); + + assert.isFalse( + isValidJsonResponse({ + jsonrpc: "2.0", + id: null, + result: 123 + }) + ); + assert.isFalse( isValidJsonResponse({ jsonrpc: "2.0", From fd539bfd4920de08d5e91c557056a9d5b80d1118 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Tue, 11 Feb 2020 17:02:24 +0100 Subject: [PATCH 09/99] Added check that --network parameter is not set when running `buidler jsonrpc`. --- .../buidler-core/src/builtin-tasks/jsonrpc.ts | 53 +++++++++++-------- .../src/internal/core/errors-list.ts | 9 ++++ 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts index b3653d0a22..3485fb3883 100644 --- a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts +++ b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts @@ -49,29 +49,40 @@ export default function() { 8545, types.int ) - .setAction(async ({ hostname, port }, { config }) => { - try { - const serverConfig: JsonRpcServerConfig = { - hostname, - port, - provider: _createBuidlerEVMProvider(config) - }; + .setAction( + async ({ hostname, port }, { network, buidlerArguments, config }) => { + if ( + network.name !== BUIDLEREVM_NETWORK_NAME && + buidlerArguments.network !== undefined + ) { + throw new BuidlerError( + ERRORS.BUILTIN_TASKS.JSONRPC_UNSUPPORTED_NETWORK + ); + } - const server = new JsonRpcServer(serverConfig); + try { + const serverConfig: JsonRpcServerConfig = { + hostname, + port, + provider: _createBuidlerEVMProvider(config) + }; - process.exitCode = await server.listen(); - } catch (error) { - if (BuidlerError.isBuidlerError(error)) { - throw error; - } + const server = new JsonRpcServer(serverConfig); + + process.exitCode = await server.listen(); + } catch (error) { + if (BuidlerError.isBuidlerError(error)) { + throw error; + } - throw new BuidlerError( - ERRORS.BUILTIN_TASKS.JSONRPC_SERVER_ERROR, - { - error: error.message - }, - error - ); + throw new BuidlerError( + ERRORS.BUILTIN_TASKS.JSONRPC_SERVER_ERROR, + { + error: error.message + }, + error + ); + } } - }); + ); } diff --git a/packages/buidler-core/src/internal/core/errors-list.ts b/packages/buidler-core/src/internal/core/errors-list.ts index a323491e24..5ce423e32a 100644 --- a/packages/buidler-core/src/internal/core/errors-list.ts +++ b/packages/buidler-core/src/internal/core/errors-list.ts @@ -598,6 +598,15 @@ We recommend not using this kind of dependencies.` message: "Error handling JSON-RPC request: %error%", title: "Error handling JSON-RPC request", description: `Handling an incoming JSON-RPC request resulted in an error.` + }, + JSONRPC_UNSUPPORTED_NETWORK: { + number: 606, + message: + "Unsupported network for JSON-RPC server. Only buidlerevm is currently supported.", + title: "Unsupported network for JSON-RPC server.", + description: `JSON-RPC server can only be started when running the BuidlerEVM network. + +To start the JSON-RPC server, retry the command without the --network parameter.` } }, ARTIFACTS: { From 41404fea0228acf39dfa2b5f876acefb3233f386 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Tue, 11 Feb 2020 17:05:05 +0100 Subject: [PATCH 10/99] Updated server shutdown method to include error handling. --- .../src/internal/buidler-evm/jsonrpc/server.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 31eb938d4d..00c481d05c 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -60,15 +60,18 @@ export class JsonRpcServer { }; public close = async () => { - return new Promise(resolve => { - this._server.on("close", () => { - log("JSON-RPC server closed"); + return new Promise((resolve, reject) => { + log("Closing JSON-RPC server"); + this._server.close(err => { + if (err) { + log("Failed to close JSON-RPC server"); + reject(err); + return; + } + log("JSON-RPC server closed"); resolve(); }); - - log("Closing JSON-RPC server"); - this._server.close(); }); }; } From 3b3e33a0a8bc073d0f6464baf44a17997e9befdf Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Tue, 11 Feb 2020 17:06:35 +0100 Subject: [PATCH 11/99] Added explanation for empty throw. --- packages/buidler-core/src/internal/util/jsonrpc.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/buidler-core/src/internal/util/jsonrpc.ts b/packages/buidler-core/src/internal/util/jsonrpc.ts index a3dcaee7a7..3b6275044e 100644 --- a/packages/buidler-core/src/internal/util/jsonrpc.ts +++ b/packages/buidler-core/src/internal/util/jsonrpc.ts @@ -31,6 +31,8 @@ export function parseJsonResponse(text: string): JsonRpcResponse { const json = JSON.parse(text); if (!isValidJsonResponse(json)) { + // We are sending the proper error inside the catch part of the statement. + // We just need to raise anything here. throw new Error(); } From 37b497cea11c12d7bc7b988f3e9799f687ed38a8 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Tue, 11 Feb 2020 17:19:54 +0100 Subject: [PATCH 12/99] Eth filter rpc calls. --- .../internal/buidler-evm/provider/filter.ts | 118 +++ .../internal/buidler-evm/provider/input.ts | 48 ++ .../buidler-evm/provider/modules/eth.ts | 182 ++++- .../src/internal/buidler-evm/provider/node.ts | 264 +++++-- .../internal/buidler-evm/provider/output.ts | 4 +- .../buidler-evm/provider/modules/eth.ts | 678 +++++++++++++++++- .../buidler-evm/provider/modules/evm.ts | 50 -- 7 files changed, 1212 insertions(+), 132 deletions(-) create mode 100644 packages/buidler-core/src/internal/buidler-evm/provider/filter.ts diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts new file mode 100644 index 0000000000..f7337d9c63 --- /dev/null +++ b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts @@ -0,0 +1,118 @@ +import Bloom from "@nomiclabs/ethereumjs-vm/dist/bloom"; +import { BN, bufferToHex, toBuffer } from "ethereumjs-util"; + +import { getRpcLog, RpcLogOutput } from "./output"; + +export enum Type { + LOGS_SUBSCRIPTION = 0, + PENDING_TRANSACTION_SUBSCRIPTION = 1, + BLOCK_SUBSCRIPTION = 2 +} + +export interface FilterCriteria { + fromBlock: number; + toBlock: number; + addresses: Buffer[]; + topics: Array | null>; +} + +export interface Filter { + id: number; + type: Type; + criteria?: FilterCriteria; + deadline: Date; + hashes: string[]; + logs: RpcLogOutput[]; +} + +export function bloomFilter( + bloom: Bloom, + addresses: Buffer[], + topics: Array | null> +): boolean { + if (addresses.length > 0 && !bloom.multiCheck(addresses)) { + return false; + } + + for (const sub of topics) { + if (sub == null) { + continue; + } + + let included = sub.length === 1; + for (const topic of sub) { + if (topic != null && bloom.check(topic)) { + included = true; + break; + } + } + + if (!included) { + return false; + } + } + return true; +} + +export function filterLogs( + logs: RpcLogOutput[], + criteria: FilterCriteria +): RpcLogOutput[] { + const filteredLogs: RpcLogOutput[] = []; + for (const log of logs) { + const blockNumber = new BN(toBuffer(log.blockNumber!)).toNumber(); + if (blockNumber < criteria.fromBlock) { + continue; + } + + if (criteria.toBlock !== -1 && blockNumber > criteria.toBlock) { + continue; + } + + let match: boolean = false; + const bAddress = toBuffer(log.address); + if (criteria.addresses.length !== 0) { + for (const address of criteria.addresses) { + if (Buffer.compare(address, bAddress) === 0) { + match = true; + } + } + + if (!match) { + continue; + } + } + + match = true; + for (let i = 0; i < criteria.topics.length; i++) { + if (criteria.topics.length > log.topics.length) { + match = false; + continue; + } + + const sub = criteria.topics[i]; + if (sub == null) { + continue; + } + + match = sub.length === 0; + for (const topic of sub) { + if (topic === null || log.topics[i] === bufferToHex(topic)) { + match = true; + break; + } + } + if (!match) { + break; + } + } + + if (!match) { + continue; + } + + filteredLogs.push(log); + } + + return filteredLogs; +} diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/input.ts b/packages/buidler-core/src/internal/buidler-evm/provider/input.ts index ac3aa4b749..652538b0cd 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/input.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/input.ts @@ -60,6 +60,21 @@ export const rpcAddress = new t.Type( t.identity ); +export const logAddress = t.union([ + rpcAddress, + t.array(rpcAddress), + t.undefined +]); + +export type LogAddress = t.TypeOf; + +export const logTopics = t.union([ + t.array(t.union([t.null, rpcHash, t.array(t.union([t.null, rpcHash]))])), + t.undefined +]); + +export type LogTopics = t.TypeOf; + export const optionalBlockTag = t.union([ rpcQuantity, t.keyof({ @@ -120,6 +135,29 @@ export interface RpcCallRequestInput { export type RpcCallRequest = t.TypeOf; +export const rpcFilterRequest = t.type( + { + fromBlock: optionalBlockTag, + toBlock: optionalBlockTag, + address: logAddress, + topics: logTopics, + blockHash: optional(rpcHash) + }, + "RpcFilterRequest" +); + +export type SubscribeRequest = t.TypeOf; + +export const subscribeFilter = t.union([ + t.keyof({ + heads: null, + pendingTransaction: null + }), + rpcFilterRequest +]); + +export type RpcFilterRequest = t.TypeOf; + export function validateParams(params: any[]): []; export function validateParams( @@ -200,6 +238,16 @@ export function validateParams( data: typeof rpcUnknown ): [Buffer, any]; +export function validateParams( + params: any[], + filterRequest: typeof rpcFilterRequest +): [RpcFilterRequest]; + +export function validateParams( + params: any[], + subscribeRequest: typeof subscribeFilter +): [SubscribeRequest]; + export function validateParams(params: any[], number: typeof rpcQuantity): [BN]; // tslint:disable only-buidler-error diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index e191ec48ab..a6490ae7ec 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -10,25 +10,38 @@ import { import * as t from "io-ts"; import { + InvalidArgumentsError, InvalidInputError, MethodNotFoundError, MethodNotSupportedError } from "../errors"; import { + LogAddress, + LogTopics, OptionalBlockTag, optionalBlockTag, rpcAddress, rpcCallRequest, RpcCallRequest, rpcData, + RpcFilterRequest, + rpcFilterRequest, rpcHash, rpcQuantity, rpcTransactionRequest, RpcTransactionRequest, rpcUnknown, + subscribeFilter, + SubscribeRequest, validateParams } from "../input"; -import { Block, BuidlerNode, CallParams, TransactionParams } from "../node"; +import { + Block, + BuidlerNode, + CallParams, + FilterParams, + TransactionParams +} from "../node"; import { bufferToRpcData, getRpcBlock, @@ -36,6 +49,7 @@ import { getRpcTransactionReceipt, numberToRpcQuantity, RpcBlockOutput, + RpcLogOutput, RpcTransactionOutput, RpcTransactionReceiptOutput } from "../output"; @@ -118,10 +132,10 @@ export class EthModule { ); case "eth_getFilterLogs": - throw new MethodNotSupportedError(`Method ${method} is not supported`); + return this._getFilterLogsAction(...this._getFilterLogsParams(params)); case "eth_getLogs": - throw new MethodNotSupportedError(`Method ${method} is not supported`); + return this._getLogsAction(...this._getLogsParams(params)); case "eth_getProof": throw new MethodNotSupportedError(`Method ${method} is not supported`); @@ -181,10 +195,12 @@ export class EthModule { ); case "eth_newFilter": - throw new MethodNotSupportedError(`Method ${method} is not supported`); + return this._newFilterAction(...this._newFilterParams(params)); case "eth_newPendingTransactionFilter": - throw new MethodNotSupportedError(`Method ${method} is not supported`); + return this._newPendingTransactionAction( + ...this._newPendingTransactionParams(params) + ); case "eth_pendingTransactions": return this._pendingTransactionsAction( @@ -464,26 +480,72 @@ export class EthModule { private async _getFilterChangesAction( filterId: BN - ): Promise { - const id = filterId.toNumber(); // This may throw, but it's ok + ): Promise { + const id = filterId.toNumber(); + const changes = await this._node.getFilterChanges(id); + if (changes === undefined) { + return null; + } - if (await this._node.isBlockFilter(id)) { - const blockHashes = await this._node.getBlockFilterChanges(id); - if (blockHashes === undefined) { - return null; - } + return changes; + } + + // eth_getFilterLogs + + private _getFilterLogsParams(params: any[]): [BN] { + return validateParams(params, rpcQuantity); + } - return blockHashes; + private async _getFilterLogsAction( + filterId: BN + ): Promise { + const id = filterId.toNumber(); + const changes = await this._node.getFilterLogs(id); + if (changes === undefined) { + return null; } - // This should return changes for the other filter types + return changes; + } + + // eth_getLogs - return null; + private _getLogsParams(params: any[]): [RpcFilterRequest] { + return validateParams(params, rpcFilterRequest); } - // eth_getFilterLogs + private async _rpcFilterRequestToGetLogsParams( + filter: RpcFilterRequest + ): Promise { + if (filter.blockHash !== undefined) { + if (filter.fromBlock !== undefined || filter.toBlock !== undefined) { + throw new InvalidArgumentsError( + "blockHash is mutually exclusive with fromBlock/toBlock" + ); + } + const block = await this._node.getBlockByHash(filter.blockHash); + if (block === undefined) { + throw new InvalidArgumentsError("blockHash cannot be found"); + } - // eth_getLogs + filter.fromBlock = new BN(block.header.number); + filter.toBlock = new BN(block.header.number); + } + + return { + fromBlock: this._extractBlock(filter.fromBlock), + toBlock: this._extractBlock(filter.toBlock), + topics: this._extractLogTopics(filter.topics), + addresses: this._extractLogAddresses(filter.address) + }; + } + + private async _getLogsAction( + filter: RpcFilterRequest + ): Promise { + const filterParams = await this._rpcFilterRequestToGetLogsParams(filter); + return this._node.getLogs(filterParams); + } // eth_getProof @@ -670,14 +732,33 @@ export class EthModule { } private async _newBlockFilterAction(): Promise { - const filterId = await this._node.createBlockFilter(); + const filterId = await this._node.newBlockFilter(); return numberToRpcQuantity(filterId); } // eth_newFilter + private _newFilterParams(params: any[]): [RpcFilterRequest] { + return validateParams(params, rpcFilterRequest); + } + + private async _newFilterAction(filter: RpcFilterRequest): Promise { + const filterParams = await this._rpcFilterRequestToGetLogsParams(filter); + const filterId = await this._node.newFilter(filterParams); + return numberToRpcQuantity(filterId); + } + // eth_newPendingTransactionFilter + private _newPendingTransactionParams(params: any[]): [] { + return []; + } + + private async _newPendingTransactionAction(): Promise { + const filterId = await this._node.newPendingTransactionFilter(); + return numberToRpcQuantity(filterId); + } + // eth_pendingTransactions private _pendingTransactionsParams(params: any[]): [] { @@ -768,7 +849,13 @@ export class EthModule { // eth_submitWork - // eth_subscribe + private _subscribeParams(params: any[]): [SubscribeRequest] { + return validateParams(params, subscribeFilter); + } + + // private _subscribeAction( + // subscribeRequest: SubscribeRequest + // ): Promise {} // eth_syncing @@ -782,17 +869,21 @@ export class EthModule { // eth_uninstallFilter - private _uninstallFilterParams(params: any[]): [BN] { + private _uninstallFilterParams(params: any): [BN] { return validateParams(params, rpcQuantity); } private async _uninstallFilterAction(filterId: BN): Promise { - // NOTE: This will throw if the filter id is too large for a number, but - // we don't care return this._node.uninstallFilter(filterId.toNumber()); } - // eth_unsubscribe + private _unsubscribeParams(params: any[]): [BN] { + return validateParams(params, rpcQuantity); + } + + // private async _unsubscribeAction(filterId: BN): Promise { + // return this._node.uninstallFilter(filterId.toNumber()); + // } // Utility methods @@ -855,6 +946,51 @@ export class EthModule { } } + private _extractBlock(blockTag: OptionalBlockTag): number { + switch (blockTag) { + case "earliest": + return 0; + case undefined: + case "latest": + return -1; + case "pending": + throw new InvalidArgumentsError("pending not supported"); + } + + return blockTag.toNumber(); + } + + private _extractLogTopics( + logTopics: LogTopics + ): Array | null> { + if (logTopics === undefined || logTopics.length === 0) { + return []; + } + + const topics: Array | null> = []; + for (const logTopic of logTopics) { + if (Buffer.isBuffer(logTopic)) { + topics.push([logTopic]); + } else { + topics.push(logTopic); + } + } + + return topics; + } + + private _extractLogAddresses(address: LogAddress): Buffer[] { + if (address === undefined) { + return []; + } + + if (Buffer.isBuffer(address)) { + return [address]; + } + + return address; + } + private async _getDefaultCallFrom(): Promise { const localAccounts = await this._node.getLocalAccountAddresses(); diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index a4d84ff2be..8eab06d5f2 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -1,4 +1,5 @@ import VM from "@nomiclabs/ethereumjs-vm"; +import Bloom from "@nomiclabs/ethereumjs-vm/dist/bloom"; import { EVMResult, ExecResult } from "@nomiclabs/ethereumjs-vm/dist/evm/evm"; import { ERROR } from "@nomiclabs/ethereumjs-vm/dist/exceptions"; import { @@ -16,6 +17,7 @@ import { FakeTransaction, Transaction } from "ethereumjs-tx"; import { BN, bufferToHex, + bufferToInt, ECDSASignature, ecsign, hashPersonalMessage, @@ -39,6 +41,8 @@ import { VMTracer } from "../stack-traces/vm-tracer"; import { Blockchain } from "./blockchain"; import { InternalError, InvalidInputError } from "./errors"; +import { bloomFilter, Filter, filterLogs, Type } from "./filter"; +import { getRpcLog, RpcLogOutput } from "./output"; import { getCurrentTimestamp } from "./utils"; const log = debug("buidler:core:buidler-evm:node"); @@ -77,6 +81,13 @@ export interface TransactionParams { nonce: BN; } +export interface FilterParams { + fromBlock: number; + toBlock: number; + addresses: Buffer[]; + topics: Array | null>; +} + export class TransactionExecutionError extends Error {} export interface TxBlockResult { @@ -110,8 +121,6 @@ interface Snapshot { transactionHashToBlockHash: Map; blockHashToTxBlockResults: Map; blockHashToTotalDifficulty: Map; - lastFilterId: number; - blockFiltersLastBlockSent: Map; } export class BuidlerNode { @@ -221,7 +230,7 @@ export class BuidlerNode { private _blockHashToTotalDifficulty: Map = new Map(); private _lastFilterId = 0; - private _blockFiltersLastBlockSent: Map = new Map(); + private _filters: Map = new Map(); private _nextSnapshotId = 1; // We start in 1 to mimic Ganache private readonly _snapshots: Snapshot[] = []; @@ -608,10 +617,6 @@ export class BuidlerNode { ), blockHashToTotalDifficulty: new Map( this._blockHashToTotalDifficulty.entries() - ), - lastFilterId: this._lastFilterId, - blockFiltersLastBlockSent: new Map( - this._blockFiltersLastBlockSent.entries() ) }; @@ -650,8 +655,9 @@ export class BuidlerNode { this._transactionHashToBlockHash = snapshot.transactionHashToBlockHash; this._blockHashToTxBlockResults = snapshot.blockHashToTxBlockResults; this._blockHashToTotalDifficulty = snapshot.blockHashToTotalDifficulty; - this._lastFilterId = snapshot.lastFilterId; - this._blockFiltersLastBlockSent = snapshot.blockFiltersLastBlockSent; + + // Mark logs in log filters as removed + this._removeLogs(snapshot.latestBlock); // We delete this and the following snapshots, as they can only be used // once in Ganache @@ -660,64 +666,139 @@ export class BuidlerNode { return true; } - public async createBlockFilter(): Promise { - const filterId = this._lastFilterId + 1; + public async newFilter(filterParams: FilterParams): Promise { + filterParams = await this._computeFilterParams(filterParams, true); + + const rpcID: number = this._rpcID(); + this._filters.set(rpcID, { + id: rpcID, + type: Type.LOGS_SUBSCRIPTION, + criteria: { + fromBlock: filterParams.fromBlock, + toBlock: filterParams.toBlock, + addresses: filterParams.addresses, + topics: filterParams.topics + }, + deadline: this._newDeadline(), + hashes: [], + logs: await this.getLogs(filterParams) + }); + + return rpcID; + } + public async newBlockFilter(): Promise { const block = await this.getLatestBlock(); - const currentBlockNumber = new BN(block.header.number); - // We always show the last block in the initial getChanges - const lastBlockSent = currentBlockNumber.subn(1); + const rpcID: number = this._rpcID(); + this._filters.set(rpcID, { + id: rpcID, + type: Type.BLOCK_SUBSCRIPTION, + deadline: this._newDeadline(), + hashes: [bufferToHex(block.header.hash())], + logs: [] + }); + + return rpcID; + } - this._blockFiltersLastBlockSent.set(filterId, lastBlockSent); + public async newPendingTransactionFilter(): Promise { + const rpcID: number = this._rpcID(); - this._lastFilterId += 1; + this._filters.set(rpcID, { + id: rpcID, + type: Type.PENDING_TRANSACTION_SUBSCRIPTION, + deadline: this._newDeadline(), + hashes: [], + logs: [] + }); - return filterId; + return rpcID; } public async uninstallFilter(filterId: number): Promise { - // This should be able to uninstall any kind of filter, not just - // block filters - - if (this._blockFiltersLastBlockSent.has(filterId)) { - this._blockFiltersLastBlockSent.delete(filterId); - return true; + if (!this._filters.has(filterId)) { + return false; } - return false; + this._filters.delete(filterId); + return true; } - public async isBlockFilter(filterId: number): Promise { - return this._blockFiltersLastBlockSent.has(filterId); + public async getFilterChanges( + filterId: number + ): Promise { + const filter = this._filters.get(filterId); + if (filter === undefined) { + return undefined; + } + + filter.deadline = this._newDeadline(); + switch (filter.type) { + case Type.BLOCK_SUBSCRIPTION: + case Type.PENDING_TRANSACTION_SUBSCRIPTION: + const hashes = filter.hashes; + filter.hashes = []; + return hashes; + case Type.LOGS_SUBSCRIPTION: + const logs = filter.logs; + filter.logs = []; + return logs; + } + + return undefined; } - public async getBlockFilterChanges( + public async getFilterLogs( filterId: number - ): Promise { - if (!this._blockFiltersLastBlockSent.has(filterId)) { + ): Promise { + const filter = this._filters.get(filterId); + if (filter === undefined) { return undefined; } - const lastBlockSent = this._blockFiltersLastBlockSent.get(filterId)!; + const logs = filter.logs; + filter.logs = []; + filter.deadline = this._newDeadline(); + return logs; + } - const latestBlock = await this.getLatestBlock(); - const currentBlockNumber = new BN(latestBlock.header.number); - - const blockHashes: string[] = []; - let blockNumber: BN; - for ( - blockNumber = lastBlockSent.addn(1); - blockNumber.lte(currentBlockNumber); - blockNumber = blockNumber.addn(1) - ) { - const block = await this.getBlockByNumber(blockNumber); - blockHashes.push(bufferToHex(block.header.hash())); - } + public async getLogs(filterParams: FilterParams): Promise { + filterParams = await this._computeFilterParams(filterParams, false); + + const logs: RpcLogOutput[] = []; + for (let i = filterParams.fromBlock; i <= filterParams.toBlock; i++) { + const block = await this._getBlock(new BN(i)); + const blockResults = this._blockHashToTxBlockResults.get( + bufferToHex(block.hash()) + ); + if (blockResults === undefined) { + continue; + } + + if ( + !bloomFilter( + new Bloom(block.header.bloom), + filterParams.addresses, + filterParams.topics + ) + ) { + continue; + } - this._blockFiltersLastBlockSent.set(filterId, blockNumber.subn(1)); + for (const tx of blockResults) { + logs.push( + ...filterLogs(tx.receipt.logs, { + fromBlock: filterParams.fromBlock, + toBlock: filterParams.toBlock, + addresses: filterParams.addresses, + topics: filterParams.topics + }) + ); + } + } - return blockHashes; + return logs; } private _getSnapshotIndex(id: number): number | undefined { @@ -866,6 +947,11 @@ export class BuidlerNode { private async _saveTransactionAsReceived(tx: Transaction) { this._transactionByHash.set(bufferToHex(tx.hash(true)), tx); + this._filters.forEach(filter => { + if (filter.type === Type.PENDING_TRANSACTION_SUBSCRIPTION) { + filter.hashes.push(bufferToHex(tx.hash(true))); + } + }); } private async _getLocalAccountPrivateKey(sender: Buffer): Promise { @@ -896,6 +982,17 @@ export class BuidlerNode { for (let i = 0; i < runBlockResult.results.length; i += 1) { const result = runBlockResult.results[i]; + runBlockResult.receipts[i].logs.forEach( + (rcpLog, logIndex) => + (runBlockResult.receipts[i].logs[logIndex] = getRpcLog( + rcpLog, + block.transactions[i], + block, + i, + logIndex + )) + ); + txBlockResults.push({ bloomBitvector: result.bloom.bitvector, createAddresses: result.createdAddress, @@ -908,6 +1005,34 @@ export class BuidlerNode { const td = this._computeTotalDifficulty(block); this._blockHashToTotalDifficulty.set(blockHash, td); + + const rpcLogs: RpcLogOutput[] = []; + for (const receipt of runBlockResult.receipts) { + rpcLogs.push(...receipt.logs); + } + + this._filters.forEach((filter, key) => { + if (filter.deadline < new Date()) { + this._filters.delete(key); + } + + switch (filter.type) { + case Type.BLOCK_SUBSCRIPTION: + filter.hashes.push(block.hash()); + break; + case Type.LOGS_SUBSCRIPTION: + if ( + bloomFilter( + new Bloom(block.header.bloom), + filter.criteria!.addresses, + filter.criteria!.topics + ) + ) { + filter.logs.push(...filterLogs(rpcLogs, filter.criteria!)); + } + break; + } + }); } private async _putBlock(block: Block): Promise { @@ -1172,4 +1297,53 @@ export class BuidlerNode { await this._stateManager.setStateRoot(initialStateRoot); } } + + private _removeLogs(blockNumber: number) { + this._filters.forEach(filter => { + if (filter.type !== Type.LOGS_SUBSCRIPTION) { + return; + } + + for (let i = filter.logs.length - 1; i >= 0; i--) { + if ( + new BN(toBuffer(filter.logs[i].blockNumber!)).toNumber() <= + blockNumber + ) { + break; + } + + filter.logs[i].removed = true; + } + }); + } + + private async _computeFilterParams( + filterParams: FilterParams, + isFilter: boolean + ): Promise { + if (filterParams.fromBlock === -1 || filterParams.toBlock === -1) { + const block = await this.getLatestBlock(); + if (filterParams.fromBlock === -1) { + filterParams.fromBlock = bufferToInt(block.header.number); + } + + if (!isFilter && filterParams.toBlock === -1) { + filterParams.toBlock = bufferToInt(block.header.number); + } + } + + return filterParams; + } + + private _newDeadline(): Date { + const dt = new Date(); + dt.setMinutes(dt.getMinutes() + 5); + return dt; + } + + private _rpcID(): number { + this._lastFilterId += 1; + + return this._lastFilterId; + } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/output.ts b/packages/buidler-core/src/internal/buidler-evm/provider/output.ts index 85fde2609d..badd43b393 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/output.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/output.ts @@ -187,9 +187,7 @@ export function getRpcTransactionReceipt( gasUsed: numberToRpcQuantity(new BN(receipt.gasUsed)), contractAddress: createdAddress !== undefined ? bufferToRpcData(createdAddress) : null, - logs: receipt.logs.map((log, logIndex) => - getRpcLog(log, tx, block, index, logIndex) - ), + logs: receipt.logs, logsBloom: bufferToRpcData(txBlockResults[index].bloomBitvector), status: numberToRpcQuantity(receipt.status) }; diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index c33f79d438..535d055c1b 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -730,8 +730,7 @@ describe("Eth module", function() { describe("block filters", function() { it("Supports block filters", async function() { - const filterId = await this.provider.send("eth_newBlockFilter", []); - assert.isString(filterId); + assert.isString(await this.provider.send("eth_newBlockFilter")); }); it("Supports uninstalling an existing filter", async function() { @@ -743,7 +742,7 @@ describe("Eth module", function() { assert.isTrue(uninstalled); }); - it("doesn't fail on uninstalling a non-existent filter", async function() { + it("Doesn't fail on uninstalling a non-existent filter", async function() { const uninstalled = await this.provider.send("eth_uninstallFilter", [ "0x1" ]); @@ -797,17 +796,615 @@ describe("Eth module", function() { assert.lengthOf(blockHashes, 3); }); + + it("should return reorganized block", async function() { + const filterId = await this.provider.send("eth_newBlockFilter", []); + + assert.lengthOf( + await this.provider.send("eth_getFilterChanges", [filterId]), + 1 + ); + + await this.provider.send("evm_mine", []); + const block1 = await this.provider.send("eth_getBlockByNumber", [ + await this.provider.send("eth_blockNumber"), + false + ]); + + const snapshotId: string = await this.provider.send("evm_snapshot", []); + await this.provider.send("evm_revert", [snapshotId]); + + await this.provider.send("evm_mine", []); + const block2 = await this.provider.send("eth_getBlockByNumber", [ + await this.provider.send("eth_blockNumber"), + false + ]); + + const blockHashes = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); + + assert.deepEqual(blockHashes, [ + toBuffer(block1.hash), + toBuffer(block2.hash) + ]); + }); }); describe("eth_getFilterLogs", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getFilterLogs"); + it("Supports get filter logs", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + const filterId = await this.provider.send("eth_newFilter", [{}]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + const logs = await this.provider.send("eth_getFilterLogs", [filterId]); + assert.lengthOf(logs, 1); + + const log = logs[0]; + assert.equal(log.removed, false); + assert.equal(log.logIndex, "0x0"); + assert.equal(log.transactionIndex, "0x0"); + assert.equal(log.blockNumber, "0x2"); + assert.equal(log.address, exampleContract); + assert.equal(log.data, `0x${newState}`); + }); + + it("Supports uninstalling an existing log filter", async function() { + const filterId = await this.provider.send("eth_newFilter", [{}]); + const uninstalled = await this.provider.send("eth_uninstallFilter", [ + filterId + ]); + + assert.isTrue(uninstalled); + }); + + it("Supports get filter logs with address", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + const filterId = await this.provider.send("eth_newFilter", [ + { + address: exampleContract + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getFilterLogs", [filterId]), + 1 + ); + }); + + it("Supports get filter logs with topics", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + const filterId = await this.provider.send("eth_newFilter", [ + { + topics: [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getFilterLogs", [filterId]), + 1 + ); + }); + + it("Supports get filter logs with null topic", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + const filterId = await this.provider.send("eth_newFilter", [ + { + topics: [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d", + null + ] + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getFilterLogs", [filterId]), + 1 + ); + }); + + it("Supports get filter logs with multiple topics", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + const filterId = await this.provider.send("eth_newFilter", [ + { + topics: [ + [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + ] + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getFilterLogs", [filterId]), + 1 + ); + }); + + it("Supports get filter logs with fromBlock", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + const filterId = await this.provider.send("eth_newFilter", [ + { + fromBlock: "0x0", + address: exampleContract, + topics: [ + [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d" + ], + [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000003b" + ] + ] + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getFilterLogs", [filterId]), + 2 + ); + }); + + it("Supports get filter logs with toBlock", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + const filterId = await this.provider.send("eth_newFilter", [ + { + fromBlock: "0x0", + toBlock: "0x2", + address: exampleContract, + topics: [ + [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d" + ], + [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000003b" + ] + ] + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getFilterLogs", [filterId]), + 1 + ); + }); + + it("Supports get filter logs with revert", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + const filterId = await this.provider.send("eth_newFilter", [ + { + address: exampleContract, + topics: [ + [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d" + ] + ] + } + ]); + + const snapshotId: string = await this.provider.send("evm_snapshot", []); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + await this.provider.send("evm_revert", [snapshotId]); + + const logs = await this.provider.send("eth_getFilterLogs", [filterId]); + assert.lengthOf(logs, 1); + + const log = logs[0]; + assert.equal(log.removed, true); + assert.equal(log.logIndex, "0x0"); + assert.equal(log.transactionIndex, "0x0"); + assert.equal(log.blockNumber, "0x2"); + assert.equal(log.address, exampleContract); + assert.equal(log.data, `0x${newState}`); }); }); describe("eth_getLogs", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_getLogs"); + it("Supports get logs", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000007b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + address: "0x0000000000000000000000000000000000000000" + } + ]), + 0 + ); + + const logs = await this.provider.send("eth_getLogs", [ + { + address: exampleContract + } + ]); + assert.lengthOf(logs, 1); + + const log = logs[0]; + assert.equal(log.removed, false); + assert.equal(log.logIndex, "0x0"); + assert.equal(log.transactionIndex, "0x0"); + assert.equal(log.blockNumber, "0x2"); + assert.equal(log.address, exampleContract); + assert.equal(log.data, `0x${newState}`); + }); + + it("Supports get logs with address", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + address: exampleContract + } + ]), + 1 + ); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + address: "0x0000000000000000000000000000000000000000" + } + ]), + 0 + ); + }); + + it("Supports get logs with topics", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + topics: [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d" + ] + } + ]), + 1 + ); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + topics: [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ]), + 0 + ); + }); + + it("Supports get logs with null topic", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + topics: [ + null, + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ]), + 1 + ); + }); + + it("Supports get logs with multiple topic", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + fromBlock: "0x2", + topics: [ + [ + "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d" + ], + [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000003b" + ] + ] + } + ]), + 2 + ); + }); + + it("Supports get logs with fromBlock", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + fromBlock: "0x3" + } + ]), + 1 + ); + }); + + it("Supports get logs with toBlock", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const newState = + "000000000000000000000000000000000000000000000000000000000000003b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf( + await this.provider.send("eth_getLogs", [ + { + fromBlock: "0x0", + toBlock: "0x2" + } + ]), + 1 + ); }); }); @@ -1458,11 +2055,70 @@ describe("Eth module", function() { }); describe("eth_newPendingTransactionFilter", async function() { - it("is not supported", async function() { - await assertNotSupported( - this.provider, - "eth_newPendingTransactionFilter" + it("Supports pending transaction filter", async function() { + assert.isString( + await this.provider.send("eth_newPendingTransactionFilter") + ); + }); + + it("Supports uninstalling an existing filter", async function() { + const filterId = await this.provider.send( + "eth_newPendingTransactionFilter", + [] + ); + const uninstalled = await this.provider.send("eth_uninstallFilter", [ + filterId + ]); + + assert.isTrue(uninstalled); + }); + + it("Should return new pending transactions", async function() { + const filterId = await this.provider.send( + "eth_newPendingTransactionFilter", + [] + ); + + const accounts = await this.provider.send("eth_accounts"); + const burnTxParams = { + from: accounts[0], + to: zeroAddress(), + gas: numberToRpcQuantity(21000) + }; + + this.provider.send("eth_sendTransaction", [burnTxParams]); + const txHashes = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); + + assert.isNotEmpty(txHashes); + }); + + it("Should not return new pending transactions after uninstall", async function() { + const filterId = await this.provider.send( + "eth_newPendingTransactionFilter", + [] ); + + const uninstalled = await this.provider.send("eth_uninstallFilter", [ + filterId + ]); + + assert.isTrue(uninstalled); + + const accounts = await this.provider.send("eth_accounts"); + const burnTxParams = { + from: accounts[0], + to: zeroAddress(), + gas: numberToRpcQuantity(21000) + }; + + this.provider.send("eth_sendTransaction", [burnTxParams]); + const txHashes = await this.provider.send("eth_getFilterChanges", [ + filterId + ]); + + assert.isNull(txHashes); }); }); diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts index 9a8ade0c96..2cf671bc0f 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/evm.ts @@ -411,56 +411,6 @@ describe("Evm module", function() { assert.equal(balanceAfterRevert, balanceBeforeTx); }); - - it("Should restore block filters", async function() { - await this.provider.send("evm_mine", []); - - const firstId = await this.provider.send("eth_newBlockFilter", []); - assert.equal(firstId, "0x1"); - - const firstFilterInitialChanges = await this.provider.send( - "eth_getFilterChanges", - [firstId] - ); - - assert.lengthOf(firstFilterInitialChanges, 1); - - await this.provider.send("evm_mine", []); - - const snapshotId: string = await this.provider.send("evm_snapshot", []); - - await this.provider.send("evm_mine", []); - - const secondId = await this.provider.send("eth_newBlockFilter", []); - assert.equal(secondId, "0x2"); - - const firstFilterSecondChanges = await this.provider.send( - "eth_getFilterChanges", - [firstId] - ); - assert.lengthOf(firstFilterSecondChanges, 2); - - const reverted: boolean = await this.provider.send("evm_revert", [ - snapshotId - ]); - assert.isTrue(reverted); - - const secondIdAfterRevert = await this.provider.send( - "eth_newBlockFilter", - [] - ); - assert.equal(secondIdAfterRevert, "0x2"); - - const firstFilterSecondChangesAfterRevert = await this.provider.send( - "eth_getFilterChanges", - [firstId] - ); - assert.lengthOf(firstFilterSecondChangesAfterRevert, 1); - assert.equal( - firstFilterSecondChangesAfterRevert[0], - firstFilterSecondChanges[0] - ); - }); }); }); }); From 5e2bd4399a8da195c8363b4acac3c8fa3df6fe0a Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Tue, 11 Feb 2020 18:00:01 +0100 Subject: [PATCH 13/99] Added specialized transaction error, and check for error codes. --- .../src/internal/buidler-evm/provider/node.ts | 16 +++++-- .../buidler-evm/helpers/assertions.ts | 7 ++- .../buidler-evm/provider/modules/eth.ts | 48 ++++++++++++------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index faca4d07f0..9743decf02 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -5,6 +5,7 @@ import { RunBlockResult, TxReceipt } from "@nomiclabs/ethereumjs-vm/dist/runBlock"; +import { RunTxResult } from "@nomiclabs/ethereumjs-vm/dist/runTx"; import { StateManager } from "@nomiclabs/ethereumjs-vm/dist/state"; import PStateManager from "@nomiclabs/ethereumjs-vm/dist/state/promisified"; import chalk from "chalk"; @@ -336,11 +337,16 @@ export class BuidlerNode { await this._addTransactionToBlock(block, tx); - const result = await this._vm.runBlock({ - block, - generate: true, - skipBlockValidation: true - }); + let result: RunBlockResult; + try { + result = await this._vm.runBlock({ + block, + generate: true, + skipBlockValidation: true + }); + } catch (error) { + throw new TransactionExecutionError(error.message); + } await this._printLogs(); diff --git a/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts b/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts index 9ae3e25084..7b5b48b7e3 100644 --- a/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts +++ b/packages/buidler-core/test/internal/buidler-evm/helpers/assertions.ts @@ -107,11 +107,16 @@ export async function assertNodeBalances( export async function assertTransactionFailure( provider: EthereumProvider, txData: RpcTransactionRequestInput, - message?: string + message?: string, + code?: number ) { try { await provider.send("eth_sendTransaction", [txData]); } catch (error) { + if (code !== undefined) { + assert.equal(error.code, code); + } + if (message !== undefined) { assert.include(error.message, message); } diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index a257a7c73e..c7b28dbaea 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -1197,7 +1197,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(txParams.gasLimit), gasPrice: numberToRpcQuantity(txParams.gasPrice) }, - "Transaction reverted without a reason" + "Transaction reverted without a reason", + -32003 ); const tx = await this.provider.send("eth_getTransactionByHash", [ @@ -1413,7 +1414,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(txParams.gasLimit), gasPrice: numberToRpcQuantity(txParams.gasPrice) }, - "Transaction reverted without a reason" + "Transaction reverted without a reason", + -32003 ); const receipt = await this.provider.send( @@ -1589,7 +1591,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(21000), gasPrice: numberToRpcQuantity(1) }, - "unknown account" + "unknown account", + -32000 ); }); @@ -1599,7 +1602,8 @@ describe("Eth module", function() { { from: DEFAULT_ACCOUNTS_ADDRESSES[0] }, - "contract creation without any data provided" + "contract creation without any data provided", + -32000 ); await assertTransactionFailure( @@ -1609,7 +1613,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(21000), gasPrice: numberToRpcQuantity(1) }, - "contract creation without any data provided" + "contract creation without any data provided", + -32000 ); await assertTransactionFailure( @@ -1620,7 +1625,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(21000), gasPrice: numberToRpcQuantity(1) }, - "contract creation without any data provided" + "contract creation without any data provided", + -32000 ); }); }); @@ -1668,7 +1674,8 @@ describe("Eth module", function() { to: zeroAddress(), gas: numberToRpcQuantity(1) }, - "Transaction requires at least 21000 gas but got 1" + "Transaction requires at least 21000 gas but got 1", + -32000 ); // Not enough balance @@ -1680,7 +1687,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(21000), gasPrice: numberToRpcQuantity(DEFAULT_ACCOUNTS_BALANCES[0]) }, - "sender doesn't have enough funds to send tx" + "sender doesn't have enough funds to send tx", + -32003 ); // Gas is larger than block gas limit @@ -1692,7 +1700,8 @@ describe("Eth module", function() { gas: numberToRpcQuantity(DEFAULT_BLOCK_GAS_LIMIT + 1) }, `Transaction gas limit is ${DEFAULT_BLOCK_GAS_LIMIT + - 1} and exceeds block gas limit of ${DEFAULT_BLOCK_GAS_LIMIT}` + 1} and exceeds block gas limit of ${DEFAULT_BLOCK_GAS_LIMIT}`, + -32000 ); // Invalid opcode. We try to deploy a contract with an invalid opcode in the deployment code @@ -1703,7 +1712,8 @@ describe("Eth module", function() { from: DEFAULT_ACCOUNTS_ADDRESSES[0], data: "0xAA" }, - "Transaction failed: revert" + "Transaction failed: revert", + -32003 ); // Out of gas. This a deployment transaction that pushes 0x00 multiple times @@ -1720,7 +1730,8 @@ describe("Eth module", function() { "0x6000600060006000600060006000600060006000600060006000600060006000600060006000600060006000600060006000", gas: numberToRpcQuantity(53500) }, - "out of gas" + "out of gas", + -32003 ); // Invalid nonce @@ -1731,7 +1742,8 @@ describe("Eth module", function() { to: DEFAULT_ACCOUNTS_ADDRESSES[0], nonce: numberToRpcQuantity(1) }, - "Invalid nonce. Expected 2 but got 1" + "Invalid nonce. Expected 2 but got 1", + -32000 ); // Revert. This a deployment transaction that immediately reverts without a reason @@ -1742,7 +1754,8 @@ describe("Eth module", function() { from: DEFAULT_ACCOUNTS_ADDRESSES[0], data: "0x60006000fd" }, - "Transaction reverted without a reason" + "Transaction reverted without a reason", + -32003 ); // This is a contract that reverts with A in its constructor @@ -1753,7 +1766,8 @@ describe("Eth module", function() { data: "0x6080604052348015600f57600080fd5b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260018152602001807f410000000000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fdfe" }, - "revert A" + "revert A", + -32003 ); }); @@ -1773,7 +1787,8 @@ describe("Eth module", function() { to: DEFAULT_ACCOUNTS_ADDRESSES[0], nonce: numberToRpcQuantity(0) }, - `known transaction: ${bufferToHex(hash)}` + `known transaction: ${bufferToHex(hash)}`, + -32000 ); }); @@ -1804,7 +1819,8 @@ describe("Eth module", function() { await assertTransactionFailure( this.provider, txParams, - `known transaction: ${bufferToHex(hash)}` + `known transaction: ${bufferToHex(hash)}`, + -32000 ); }); }); From 358cfa26fcd2b0412749215ba69a927ab2368c80 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Wed, 12 Feb 2020 11:35:27 +0100 Subject: [PATCH 14/99] Added websocket support for JSON-RPC server. --- packages/buidler-core/package.json | 8 ++- .../internal/buidler-evm/jsonrpc/handler.ts | 50 +++++++++++++-- .../internal/buidler-evm/jsonrpc/server.ts | 61 +++++++++++++------ 3 files changed, 92 insertions(+), 27 deletions(-) diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index c0879078c4..982a78bf4f 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -53,6 +53,7 @@ ], "devDependencies": { "@types/chai": "^4.2.0", + "@types/ci-info": "^2.0.0", "@types/debug": "^4.1.4", "@types/download": "^6.2.4", "@types/find-up": "^2.1.1", @@ -60,10 +61,10 @@ "@types/glob": "^7.1.1", "@types/lodash": "^4.14.123", "@types/node-fetch": "^2.3.7", - "@types/semver": "^6.0.2", - "@types/ci-info": "^2.0.0", "@types/qs": "^6.5.3", + "@types/semver": "^6.0.2", "@types/uuid": "^3.4.5", + "@types/ws": "^7.2.1", "chai": "^4.2.0", "time-require": "^0.1.2" }, @@ -105,7 +106,8 @@ "source-map-support": "^0.5.13", "ts-essentials": "^2.0.7", "tsort": "0.0.1", - "uuid": "^3.3.2" + "uuid": "^3.3.2", + "ws": "^7.2.1" }, "nyc": { "extension": [ diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 4a4c9a7ec4..e6f6d0a676 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -2,6 +2,7 @@ import chalk from "chalk"; import debug from "debug"; import { IncomingMessage, ServerResponse } from "http"; import getRawBody from "raw-body"; +import WebSocket from "ws"; import { EthereumProvider } from "../../../types"; import { BuidlerError } from "../../core/errors"; @@ -28,15 +29,12 @@ export default class JsonRpcHandler { this._provider = provider; } - public requestListener = async ( - req: IncomingMessage, - res: ServerResponse - ) => { + public handleHttp = async (req: IncomingMessage, res: ServerResponse) => { let rpcReq: JsonRpcRequest | undefined; let rpcResp: JsonRpcResponse | undefined; try { - rpcReq = await this._readRequest(req); + rpcReq = await this._readHttpRequest(req); rpcResp = await this._handleRequest(rpcReq); } catch (error) { @@ -58,7 +56,32 @@ export default class JsonRpcHandler { res.end(JSON.stringify(rpcResp)); }; - private _readRequest = async ( + public handleWs = async (ws: WebSocket, msg: string) => { + let rpcReq: JsonRpcRequest | undefined; + let rpcResp: JsonRpcResponse | undefined; + + try { + rpcReq = await this._readWsRequest(msg); + + rpcResp = await this._handleRequest(rpcReq); + } catch (error) { + rpcResp = await this._handleError(error); + } + + // Validate the RPC response. + if (!isValidJsonResponse(rpcResp)) { + // Malformed response coming from the provider, report to user as an internal error. + rpcResp = await this._handleError(new InternalError("Internal error")); + } + + if (rpcReq !== undefined) { + rpcResp.id = rpcReq.id; + } + + ws.send(JSON.stringify(rpcResp)); + }; + + private _readHttpRequest = async ( req: IncomingMessage ): Promise => { let json; @@ -79,6 +102,21 @@ export default class JsonRpcHandler { return json; }; + private _readWsRequest(msg: string): JsonRpcRequest { + let json: any; + try { + json = JSON.parse(msg); + } catch (error) { + throw new InvalidJsonInputError(`Parse error: ${error.message}`); + } + + if (!isValidJsonRequest(json)) { + throw new InvalidRequestError("Invalid request"); + } + + return json; + } + private _handleRequest = async ( req: JsonRpcRequest ): Promise => { diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 00c481d05c..fa82453201 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -1,5 +1,6 @@ import debug from "debug"; import http, { Server } from "http"; +import WebSocket, { Server as WSServer } from "ws"; import { EthereumProvider } from "../../../types"; import { HttpProvider } from "../../core/providers/http"; @@ -17,18 +18,27 @@ export interface JsonRpcServerConfig { export class JsonRpcServer { private _config: JsonRpcServerConfig; - private _server: Server; + private _httpServer: Server; + private _wsServer: WSServer; constructor(config: JsonRpcServerConfig) { this._config = config; const handler = new JsonRpcHandler(config.provider); - this._server = http.createServer(handler.requestListener); + this._httpServer = http.createServer(); + this._wsServer = new WSServer({ + server: this._httpServer + }); + + this._httpServer.on("request", handler.handleHttp); + this._wsServer.on("connection", ws => { + ws.on("message", msg => handler.handleWs(ws, msg as string)); + }); } public getProvider = (name = "json-rpc"): EthereumProvider => { - const { address, port } = this._server.address(); + const { address, port } = this._httpServer.address(); return new HttpProvider(`http://${address}:${port}/`, name); }; @@ -48,9 +58,9 @@ export class JsonRpcServer { public start = async () => { return new Promise(resolve => { log(`Starting JSON-RPC server on port ${this._config.port}`); - this._server.listen(this._config.port, this._config.hostname, () => { + this._httpServer.listen(this._config.port, this._config.hostname, () => { // We get the address and port directly from the server in order to handle random port allocation with `0`. - const { address, port } = this._server.address(); + const { address, port } = this._httpServer.address(); console.log(`Started JSON-RPC server at http://${address}:${port}/`); @@ -60,18 +70,33 @@ export class JsonRpcServer { }; public close = async () => { - return new Promise((resolve, reject) => { - log("Closing JSON-RPC server"); - this._server.close(err => { - if (err) { - log("Failed to close JSON-RPC server"); - reject(err); - return; - } - - log("JSON-RPC server closed"); - resolve(); - }); - }); + return Promise.all([ + new Promise((resolve, reject) => { + log("Closing JSON-RPC server"); + this._httpServer.close(err => { + if (err) { + log("Failed to close JSON-RPC server"); + reject(err); + return; + } + + log("JSON-RPC server closed"); + resolve(); + }); + }), + new Promise((resolve, reject) => { + log("Closing websocket server"); + this._wsServer.close(err => { + if (err) { + log("Failed to close websocket server"); + reject(err); + return; + } + + log("Websocket server closed"); + resolve(); + }); + }) + ]); }; } From df1b0705c85b7f866be7ee41396471751e696e8d Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Wed, 12 Feb 2020 17:03:13 +0100 Subject: [PATCH 15/99] Added eth_subscribe support for WebSocket server. --- .../internal/buidler-evm/jsonrpc/handler.ts | 233 ++++++++++-------- .../internal/buidler-evm/jsonrpc/server.ts | 6 +- .../buidler-core/src/internal/util/jsonrpc.ts | 6 + 3 files changed, 143 insertions(+), 102 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index e6f6d0a676..71a8b677cc 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -8,6 +8,7 @@ import { EthereumProvider } from "../../../types"; import { BuidlerError } from "../../core/errors"; import { ERRORS } from "../../core/errors-list"; import { + isSuccessfulJsonResponse, isValidJsonRequest, isValidJsonResponse, JsonRpcRequest, @@ -34,17 +35,17 @@ export default class JsonRpcHandler { let rpcResp: JsonRpcResponse | undefined; try { - rpcReq = await this._readHttpRequest(req); + rpcReq = await _readHttpRequest(req); rpcResp = await this._handleRequest(rpcReq); } catch (error) { - rpcResp = await this._handleError(error); + rpcResp = await _handleError(error); } // Validate the RPC response. if (!isValidJsonResponse(rpcResp)) { // Malformed response coming from the provider, report to user as an internal error. - rpcResp = await this._handleError(new InternalError("Internal error")); + rpcResp = await _handleError(new InternalError("Internal error")); } if (rpcReq !== undefined) { @@ -56,67 +57,69 @@ export default class JsonRpcHandler { res.end(JSON.stringify(rpcResp)); }; - public handleWs = async (ws: WebSocket, msg: string) => { - let rpcReq: JsonRpcRequest | undefined; - let rpcResp: JsonRpcResponse | undefined; - - try { - rpcReq = await this._readWsRequest(msg); - - rpcResp = await this._handleRequest(rpcReq); - } catch (error) { - rpcResp = await this._handleError(error); - } - - // Validate the RPC response. - if (!isValidJsonResponse(rpcResp)) { - // Malformed response coming from the provider, report to user as an internal error. - rpcResp = await this._handleError(new InternalError("Internal error")); - } - - if (rpcReq !== undefined) { - rpcResp.id = rpcReq.id; - } - - ws.send(JSON.stringify(rpcResp)); - }; - - private _readHttpRequest = async ( - req: IncomingMessage - ): Promise => { - let json; + public handleWs = async (ws: WebSocket) => { + const subscriptions: string[] = []; + let isClosed = false; + + ws.on("message", async msg => { + let rpcReq: JsonRpcRequest | undefined; + let rpcResp: JsonRpcResponse | undefined; + + try { + rpcReq = await _readWsRequest(msg as string); + + // In case of eth_subscribe, we want to provide an emit function to the BuidlerEVM Provider. + if (rpcReq.method === "eth_subscribe") { + rpcReq.params.push(function emit(event: any) { + // Don't attempt to send a message to the websocket if we already know it is closed. + if (isClosed) { + return; + } + + try { + ws.send(JSON.stringify(event)); + } catch (error) { + _handleError(error); + } + }); + } + + rpcResp = await this._handleRequest(rpcReq); + + // If eth_subscribe was successful, keep track of the subscription id, + // so we can cleanup on websocket close. + if ( + rpcReq.method === "eth_subscribe" && + isSuccessfulJsonResponse(rpcResp) + ) { + subscriptions.push(rpcResp.result.id); + } + } catch (error) { + rpcResp = await _handleError(error); + } - try { - const buf = await getRawBody(req); - const text = buf.toString(); + // Validate the RPC response. + if (!isValidJsonResponse(rpcResp)) { + // Malformed response coming from the provider, report to user as an internal error. + rpcResp = await _handleError(new InternalError("Internal error")); + } - json = JSON.parse(text); - } catch (error) { - throw new InvalidJsonInputError(`Parse error: ${error.message}`); - } + if (rpcReq !== undefined) { + rpcResp.id = rpcReq.id; + } - if (!isValidJsonRequest(json)) { - throw new InvalidRequestError("Invalid request"); - } + ws.send(JSON.stringify(rpcResp)); + }); - return json; + ws.on("close", () => { + // Clear any active subscriptions for the closed websocket connection. + isClosed = true; + subscriptions.forEach(async subscription => { + await this._provider.send("eth_unsubscribe", [subscription]); + }); + }); }; - private _readWsRequest(msg: string): JsonRpcRequest { - let json: any; - try { - json = JSON.parse(msg); - } catch (error) { - throw new InvalidJsonInputError(`Parse error: ${error.message}`); - } - - if (!isValidJsonRequest(json)) { - throw new InvalidRequestError("Invalid request"); - } - - return json; - } - private _handleRequest = async ( req: JsonRpcRequest ): Promise => { @@ -130,50 +133,84 @@ export default class JsonRpcHandler { result }; }; +} - private _handleError = async (error: any): Promise => { - this._printError(error); +const _readHttpRequest = async ( + req: IncomingMessage +): Promise => { + let json; - // In case of non-buidler error, treat it as internal and associate the appropriate error code. - if (!BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { - error = new InternalError(error.message); - } + try { + const buf = await getRawBody(req); + const text = buf.toString(); - return { - jsonrpc: "2.0", - id: null, - error: { - code: error.code, - message: error.message - } - }; - }; + json = JSON.parse(text); + } catch (error) { + throw new InvalidJsonInputError(`Parse error: ${error.message}`); + } - private _printError = (error: any) => { - if (BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { - // Report the error to console in the format of other BuidlerErrors (wrappedError.message), - // while preserving the stack from the originating error (error.stack). - const wrappedError = new BuidlerError( - ERRORS.BUILTIN_TASKS.JSONRPC_HANDLER_ERROR, - { - error: error.message - }, - error - ); - - console.error(chalk.red(`Error ${wrappedError.message}`)); - } else if (BuidlerError.isBuidlerError(error)) { - console.error(chalk.red(`Error ${error.message}`)); - } else if (error instanceof Error) { - console.error( - chalk.red(`An unexpected error occurred: ${error.message}`) - ); - } else { - console.error(chalk.red("An unexpected error occurred.")); - } + if (!isValidJsonRequest(json)) { + throw new InvalidRequestError("Invalid request"); + } + + return json; +}; + +const _readWsRequest = (msg: string): JsonRpcRequest => { + let json: any; + try { + json = JSON.parse(msg); + } catch (error) { + throw new InvalidJsonInputError(`Parse error: ${error.message}`); + } + + if (!isValidJsonRequest(json)) { + throw new InvalidRequestError("Invalid request"); + } + + return json; +}; - console.log(""); +const _handleError = async (error: any): Promise => { + _printError(error); - console.error(error.stack); + // In case of non-buidler error, treat it as internal and associate the appropriate error code. + if (!BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { + error = new InternalError(error.message); + } + + return { + jsonrpc: "2.0", + id: null, + error: { + code: error.code, + message: error.message + } }; -} +}; + +const _printError = (error: any) => { + if (BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { + // Report the error to console in the format of other BuidlerErrors (wrappedError.message), + // while preserving the stack from the originating error (error.stack). + const wrappedError = new BuidlerError( + ERRORS.BUILTIN_TASKS.JSONRPC_HANDLER_ERROR, + { + error: error.message + }, + error + ); + + console.error(chalk.red(`Error ${wrappedError.message}`)); + } else if (BuidlerError.isBuidlerError(error)) { + console.error(chalk.red(`Error ${error.message}`)); + } else if (error instanceof Error) { + console.error(chalk.red(`An unexpected error occurred: ${error.message}`)); + } else { + console.error(chalk.red("An unexpected error occurred.")); + } + + console.log(""); + + console.error(error.stack); +}; diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index fa82453201..77850c1750 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -1,6 +1,6 @@ import debug from "debug"; import http, { Server } from "http"; -import WebSocket, { Server as WSServer } from "ws"; +import { Server as WSServer } from "ws"; import { EthereumProvider } from "../../../types"; import { HttpProvider } from "../../core/providers/http"; @@ -32,9 +32,7 @@ export class JsonRpcServer { }); this._httpServer.on("request", handler.handleHttp); - this._wsServer.on("connection", ws => { - ws.on("message", msg => handler.handleWs(ws, msg as string)); - }); + this._wsServer.on("connection", handler.handleWs); } public getProvider = (name = "json-rpc"): EthereumProvider => { diff --git a/packages/buidler-core/src/internal/util/jsonrpc.ts b/packages/buidler-core/src/internal/util/jsonrpc.ts index 3b6275044e..7cad779530 100644 --- a/packages/buidler-core/src/internal/util/jsonrpc.ts +++ b/packages/buidler-core/src/internal/util/jsonrpc.ts @@ -97,3 +97,9 @@ export function isValidJsonResponse(payload: any) { return true; } + +export function isSuccessfulJsonResponse( + payload: JsonRpcResponse +): payload is SuccessfulJsonRpcResponse { + return "response" in payload; +} From 422c06fcff9a5cd374d47a86c76a99ad56248382 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Thu, 13 Feb 2020 15:22:17 +0100 Subject: [PATCH 16/99] Subscribe and unsubscribe support. --- .../internal/buidler-evm/provider/filter.ts | 1 + .../internal/buidler-evm/provider/input.ts | 33 +++++-- .../buidler-evm/provider/modules/eth.ts | 75 ++++++++++++--- .../src/internal/buidler-evm/provider/node.ts | 65 +++++++++++-- .../buidler-evm/provider/modules/eth.ts | 92 ++++++++++++++++++- 5 files changed, 230 insertions(+), 36 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts index f7337d9c63..085f523b40 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts @@ -23,6 +23,7 @@ export interface Filter { deadline: Date; hashes: string[]; logs: RpcLogOutput[]; + subscription?: (emit: any) => {}; } export function bloomFilter( diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/input.ts b/packages/buidler-core/src/internal/buidler-evm/provider/input.ts index 652538b0cd..dbf10927b3 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/input.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/input.ts @@ -146,16 +146,30 @@ export const rpcFilterRequest = t.type( "RpcFilterRequest" ); -export type SubscribeRequest = t.TypeOf; +export interface RpcSubscribe { + request: RpcFilterRequest; +} -export const subscribeFilter = t.union([ - t.keyof({ - heads: null, - pendingTransaction: null - }), - rpcFilterRequest +export type OptionalRpcFilterRequest = t.TypeOf< + typeof optionalRpcFilterRequest +>; + +export const optionalRpcFilterRequest = t.union([ + rpcFilterRequest, + t.undefined ]); +export type RpcSubscribeRequest = t.TypeOf; + +export const rpcSubscribeRequest = t.keyof( + { + newHeads: null, + newPendingTransactions: null, + logs: null + }, + "RpcSubscribe" +); + export type RpcFilterRequest = t.TypeOf; export function validateParams(params: any[]): []; @@ -245,8 +259,9 @@ export function validateParams( export function validateParams( params: any[], - subscribeRequest: typeof subscribeFilter -): [SubscribeRequest]; + subscribeRequest: typeof rpcSubscribeRequest, + optionalFilterRequest: typeof optionalRpcFilterRequest +): [RpcSubscribeRequest, OptionalRpcFilterRequest, (emit: any) => {}]; export function validateParams(params: any[], number: typeof rpcQuantity): [BN]; diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index a6490ae7ec..f97b106ec5 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -20,6 +20,8 @@ import { LogTopics, OptionalBlockTag, optionalBlockTag, + optionalRpcFilterRequest, + OptionalRpcFilterRequest, rpcAddress, rpcCallRequest, RpcCallRequest, @@ -28,11 +30,11 @@ import { rpcFilterRequest, rpcHash, rpcQuantity, + rpcSubscribeRequest, + RpcSubscribeRequest, rpcTransactionRequest, RpcTransactionRequest, rpcUnknown, - subscribeFilter, - SubscribeRequest, validateParams } from "../input"; import { @@ -236,7 +238,7 @@ export class EthModule { throw new MethodNotSupportedError(`Method ${method} is not supported`); case "eth_subscribe": - throw new MethodNotSupportedError(`Method ${method} is not supported`); + return this._subscribeAction(...this._subscribeParams(params)); case "eth_syncing": return this._syncingAction(...this._syncingParams(params)); @@ -247,7 +249,7 @@ export class EthModule { ); case "eth_unsubscribe": - throw new MethodNotSupportedError(`Method ${method} is not supported`); + return this._unsubscribeAction(...this._unsubscribeParams(params)); } throw new MethodNotFoundError(`Method ${method} not found`); @@ -849,13 +851,60 @@ export class EthModule { // eth_submitWork - private _subscribeParams(params: any[]): [SubscribeRequest] { - return validateParams(params, subscribeFilter); + private _subscribeParams( + params: any[] + ): [RpcSubscribeRequest, OptionalRpcFilterRequest, (emit: any) => {}] { + if (params.length === 0) { + throw new InvalidInputError("Notifications not supported"); + } + + if (params.length === 1) { + throw new InvalidInputError( + "Expected subscription name as first argument" + ); + } + + const callback = params.pop(); + if (!(typeof callback === "function")) { + throw new InvalidInputError("Notifications not supported"); + } + + const validatedParams: [ + RpcSubscribeRequest, + OptionalRpcFilterRequest, + (emit: any) => {} + ] = validateParams(params, rpcSubscribeRequest, optionalRpcFilterRequest); + + validatedParams.push(callback); + return validatedParams; } - // private _subscribeAction( - // subscribeRequest: SubscribeRequest - // ): Promise {} + private async _subscribeAction( + subscribeRequest: RpcSubscribeRequest, + optionalFilterRequest: OptionalRpcFilterRequest, + callback: (emit: any) => {} + ): Promise { + let filterId: number; + switch (subscribeRequest) { + case "newHeads": + filterId = await this._node.newBlockFilter(callback); + return numberToRpcQuantity(filterId); + case "newPendingTransactions": + filterId = await this._node.newPendingTransactionFilter(callback); + return numberToRpcQuantity(filterId); + case "logs": + if (optionalFilterRequest === undefined) { + throw new InvalidArgumentsError("missing params argument"); + } + + const filterParams = await this._rpcFilterRequestToGetLogsParams( + optionalFilterRequest + ); + + filterId = await this._node.newFilter(filterParams, callback); + return numberToRpcQuantity(filterId); + } + } // eth_syncing @@ -874,16 +923,16 @@ export class EthModule { } private async _uninstallFilterAction(filterId: BN): Promise { - return this._node.uninstallFilter(filterId.toNumber()); + return this._node.uninstallFilter(filterId.toNumber(), false); } private _unsubscribeParams(params: any[]): [BN] { return validateParams(params, rpcQuantity); } - // private async _unsubscribeAction(filterId: BN): Promise { - // return this._node.uninstallFilter(filterId.toNumber()); - // } + private async _unsubscribeAction(filterId: BN): Promise { + return this._node.uninstallFilter(filterId.toNumber(), true); + } // Utility methods diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 8eab06d5f2..88f76943e9 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -666,7 +666,10 @@ export class BuidlerNode { return true; } - public async newFilter(filterParams: FilterParams): Promise { + public async newFilter( + filterParams: FilterParams, + callback?: (emit: any) => {} + ): Promise { filterParams = await this._computeFilterParams(filterParams, true); const rpcID: number = this._rpcID(); @@ -681,13 +684,16 @@ export class BuidlerNode { }, deadline: this._newDeadline(), hashes: [], - logs: await this.getLogs(filterParams) + logs: await this.getLogs(filterParams), + subscription: callback }); return rpcID; } - public async newBlockFilter(): Promise { + public async newBlockFilter( + filterSubscription?: (emit: any) => {} + ): Promise { const block = await this.getLatestBlock(); const rpcID: number = this._rpcID(); @@ -696,13 +702,16 @@ export class BuidlerNode { type: Type.BLOCK_SUBSCRIPTION, deadline: this._newDeadline(), hashes: [bufferToHex(block.header.hash())], - logs: [] + logs: [], + subscription: filterSubscription }); return rpcID; } - public async newPendingTransactionFilter(): Promise { + public async newPendingTransactionFilter( + filterSubscription?: (emit: any) => {} + ): Promise { const rpcID: number = this._rpcID(); this._filters.set(rpcID, { @@ -710,17 +719,29 @@ export class BuidlerNode { type: Type.PENDING_TRANSACTION_SUBSCRIPTION, deadline: this._newDeadline(), hashes: [], - logs: [] + logs: [], + subscription: filterSubscription }); return rpcID; } - public async uninstallFilter(filterId: number): Promise { + public async uninstallFilter( + filterId: number, + subscription: boolean + ): Promise { if (!this._filters.has(filterId)) { return false; } + const filter = this._filters.get(filterId); + if ( + (filter!.subscription !== undefined && !subscription) || + (filter!.subscription === undefined && subscription) + ) { + return false; + } + this._filters.delete(filterId); return true; } @@ -949,7 +970,13 @@ export class BuidlerNode { this._transactionByHash.set(bufferToHex(tx.hash(true)), tx); this._filters.forEach(filter => { if (filter.type === Type.PENDING_TRANSACTION_SUBSCRIPTION) { - filter.hashes.push(bufferToHex(tx.hash(true))); + const hash = bufferToHex(tx.hash(true)); + if (filter.subscription !== undefined) { + filter.subscription(hash); + return; + } + + filter.hashes.push(hash); } }); } @@ -1018,7 +1045,13 @@ export class BuidlerNode { switch (filter.type) { case Type.BLOCK_SUBSCRIPTION: - filter.hashes.push(block.hash()); + const hash = block.hash(); + if (filter.subscription !== undefined) { + filter.subscription(hash); + return; + } + + filter.hashes.push(hash); break; case Type.LOGS_SUBSCRIPTION: if ( @@ -1028,7 +1061,19 @@ export class BuidlerNode { filter.criteria!.topics ) ) { - filter.logs.push(...filterLogs(rpcLogs, filter.criteria!)); + const logs = filterLogs(rpcLogs, filter.criteria!); + if (logs.length === 0) { + return; + } + + if (filter.subscription !== undefined) { + logs.forEach(rpcLog => { + filter.subscription!(rpcLog); + }); + return; + } + + filter.logs.push(...logs); } break; } diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index 535d055c1b..4092b27489 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -17,6 +17,7 @@ import { import { numberToRpcQuantity, RpcBlockOutput, + RpcLogOutput, RpcTransactionOutput, RpcTransactionReceiptOutput } from "../../../../../src/internal/buidler-evm/provider/output"; @@ -2468,8 +2469,80 @@ describe("Eth module", function() { }); describe("eth_subscribe", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_subscribe"); + it("Supports newHeads subscribe", async function() { + const heads: Buffer[] = []; + const filterId = await this.provider.send("eth_subscribe", [ + "newHeads", + (emit: any) => { + heads.push(emit); + } + ]); + + await this.provider.send("evm_mine", []); + await this.provider.send("evm_mine", []); + await this.provider.send("evm_mine", []); + + assert.isTrue(await this.provider.send("eth_unsubscribe", [filterId])); + + await this.provider.send("evm_mine", []); + + assert.lengthOf(heads, 3); + }); + + it("Supports newPendingTransactions subscribe", async function() { + const pendingTransactions: string[] = []; + const filterId = await this.provider.send("eth_subscribe", [ + "newPendingTransactions", + (emit: any) => { + pendingTransactions.push(emit); + } + ]); + + const accounts = await this.provider.send("eth_accounts"); + const burnTxParams = { + from: accounts[0], + to: zeroAddress(), + gas: numberToRpcQuantity(21000) + }; + + await this.provider.send("eth_sendTransaction", [burnTxParams]); + + assert.isTrue(await this.provider.send("eth_unsubscribe", [filterId])); + + await this.provider.send("eth_sendTransaction", [burnTxParams]); + + assert.lengthOf(pendingTransactions, 1); + }); + + it("Supports logs subscribe", async function() { + const exampleContract = await deployContract( + this.provider, + `0x${EXAMPLE_CONTRACT.bytecode.object}` + ); + + const logs: RpcLogOutput[] = []; + await this.provider.send("eth_subscribe", [ + "logs", + { + address: exampleContract + }, + (emit: any) => { + logs.push(emit); + } + ]); + + const newState = + "000000000000000000000000000000000000000000000000000000000000007b"; + + await this.provider.send("eth_sendTransaction", [ + { + to: exampleContract, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + data: EXAMPLE_CONTRACT.selectors.modifiesState + newState + } + ]); + + assert.lengthOf(logs, 1); }); }); @@ -2480,8 +2553,19 @@ describe("Eth module", function() { }); describe("eth_unsubscribe", async function() { - it("is not supported", async function() { - await assertNotSupported(this.provider, "eth_unsubscribe"); + it("Supports unsubscribe", async function() { + const filterId = await this.provider.send("eth_subscribe", [ + "newHeads", + (emit: any) => { + console.log(emit); + } + ]); + + assert.isTrue(await this.provider.send("eth_unsubscribe", [filterId])); + }); + + it("Doesn't fail when unsubscribe is called for a non-existent filter", async function() { + assert.isFalse(await this.provider.send("eth_unsubscribe", ["0x1"])); }); }); }); From 057b4e0b6e3cf84da529a02b87a420adb89059a9 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 14 Feb 2020 16:02:05 +0100 Subject: [PATCH 17/99] Updated wording. --- .../buidler-core/src/internal/buidler-evm/jsonrpc/server.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 77850c1750..56bbf6edb3 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -60,7 +60,9 @@ export class JsonRpcServer { // We get the address and port directly from the server in order to handle random port allocation with `0`. const { address, port } = this._httpServer.address(); - console.log(`Started JSON-RPC server at http://${address}:${port}/`); + console.log( + `Started HTTP and WebSocket JSON-RPC server at ${address}:${port}/` + ); resolve(); }); From e2d56f3f112ffb85bf36050ead1876c2b18b3597 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 14 Feb 2020 16:31:29 +0100 Subject: [PATCH 18/99] Changed how listeners work for eth_subscribe. --- .../internal/buidler-evm/jsonrpc/handler.ts | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 71a8b677cc..fb5c4abf6c 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -61,6 +61,29 @@ export default class JsonRpcHandler { const subscriptions: string[] = []; let isClosed = false; + const listener = (payload: { subscription: string; result: any }) => { + // Don't attempt to send a message to the websocket if we already know it is closed, + // or the current websocket connection isn't interested in the particular subscription. + if (isClosed || subscriptions.includes(payload.subscription)) { + return; + } + + try { + ws.send( + JSON.stringify({ + jsonrpc: "2.0", + method: "eth_subscribe", + params: payload + }) + ); + } catch (error) { + _handleError(error); + } + }; + + // Handle eth_subscribe notifications. + this._provider.addListener("notification", listener); + ws.on("message", async msg => { let rpcReq: JsonRpcRequest | undefined; let rpcResp: JsonRpcResponse | undefined; @@ -68,22 +91,6 @@ export default class JsonRpcHandler { try { rpcReq = await _readWsRequest(msg as string); - // In case of eth_subscribe, we want to provide an emit function to the BuidlerEVM Provider. - if (rpcReq.method === "eth_subscribe") { - rpcReq.params.push(function emit(event: any) { - // Don't attempt to send a message to the websocket if we already know it is closed. - if (isClosed) { - return; - } - - try { - ws.send(JSON.stringify(event)); - } catch (error) { - _handleError(error); - } - }); - } - rpcResp = await this._handleRequest(rpcReq); // If eth_subscribe was successful, keep track of the subscription id, @@ -112,10 +119,13 @@ export default class JsonRpcHandler { }); ws.on("close", () => { + // Remove eth_subscribe listener. + this._provider.removeListener("notification", listener); + // Clear any active subscriptions for the closed websocket connection. isClosed = true; - subscriptions.forEach(async subscription => { - await this._provider.send("eth_unsubscribe", [subscription]); + subscriptions.forEach(async subscriptionId => { + await this._provider.send("eth_unsubscribe", [subscriptionId]); }); }); }; From 20718178ce95fac6939fa12d76b4cdefda324dc8 Mon Sep 17 00:00:00 2001 From: Andrej Bencic Date: Fri, 14 Feb 2020 16:47:51 +0100 Subject: [PATCH 19/99] Updated TransactionExecutionError to include parent and keep the stack. Updated other transaction execution methods to throw the same error. --- .../internal/buidler-evm/provider/errors.ts | 13 ++++++++++-- .../src/internal/buidler-evm/provider/node.ts | 21 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts b/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts index 368c600f18..7355b0423d 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/errors.ts @@ -72,8 +72,17 @@ export class InvalidInputError extends BuidlerEVMProviderError { } export class TransactionExecutionError extends BuidlerEVMProviderError { - constructor(message: string) { - super(message, -32003); + public parent: Error; + + constructor(parent: Error | string) { + if (typeof parent === "string") { + parent = new Error(parent); + } + + super(parent.message, -32003); + + this.parent = parent; + this.stack = parent.stack; } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 9743decf02..9570058fa4 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -345,7 +345,7 @@ export class BuidlerNode { skipBlockValidation: true }); } catch (error) { - throw new TransactionExecutionError(error.message); + throw new TransactionExecutionError(error); } await this._printLogs(); @@ -403,7 +403,7 @@ export class BuidlerNode { // rollback of this block. await this._stateManager.setStateRoot(previousRoot); - throw error; + throw new TransactionExecutionError(error); } } @@ -1156,12 +1156,17 @@ export class BuidlerNode { await this._addTransactionToBlock(block, tx); - const result = await this._vm.runTx({ - block, - tx, - skipNonce: true, - skipBalance: true - }); + let result: RunTxResult; + try { + result = await this._vm.runTx({ + block, + tx, + skipNonce: true, + skipBalance: true + }); + } catch (error) { + throw new TransactionExecutionError(error); + } if (!estimateGas) { await this._printLogs(); From 7a240b6cd32d2c8403f94c6933ebfea2082a5170 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 14 Feb 2020 15:00:35 -0300 Subject: [PATCH 20/99] Fix linting errors --- .../src/internal/buidler-evm/jsonrpc/handler.ts | 14 ++++++++------ .../src/internal/buidler-evm/jsonrpc/server.ts | 4 ++-- packages/buidler-core/src/internal/util/jsonrpc.ts | 1 + 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index fb5c4abf6c..64c5d3108a 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -21,6 +21,8 @@ import { InvalidRequestError } from "../provider/errors"; +// tslint:disable only-buidler-error + const log = debug("buidler:core:buidler-evm:jsonrpc"); export default class JsonRpcHandler { @@ -39,13 +41,13 @@ export default class JsonRpcHandler { rpcResp = await this._handleRequest(rpcReq); } catch (error) { - rpcResp = await _handleError(error); + rpcResp = _handleError(error); } // Validate the RPC response. if (!isValidJsonResponse(rpcResp)) { // Malformed response coming from the provider, report to user as an internal error. - rpcResp = await _handleError(new InternalError("Internal error")); + rpcResp = _handleError(new InternalError("Internal error")); } if (rpcReq !== undefined) { @@ -89,7 +91,7 @@ export default class JsonRpcHandler { let rpcResp: JsonRpcResponse | undefined; try { - rpcReq = await _readWsRequest(msg as string); + rpcReq = _readWsRequest(msg as string); rpcResp = await this._handleRequest(rpcReq); @@ -102,13 +104,13 @@ export default class JsonRpcHandler { subscriptions.push(rpcResp.result.id); } } catch (error) { - rpcResp = await _handleError(error); + rpcResp = _handleError(error); } // Validate the RPC response. if (!isValidJsonResponse(rpcResp)) { // Malformed response coming from the provider, report to user as an internal error. - rpcResp = await _handleError(new InternalError("Internal error")); + rpcResp = _handleError(new InternalError("Internal error")); } if (rpcReq !== undefined) { @@ -181,7 +183,7 @@ const _readWsRequest = (msg: string): JsonRpcRequest => { return json; }; -const _handleError = async (error: any): Promise => { +const _handleError = (error: any): JsonRpcResponse => { _printError(error); // In case of non-buidler error, treat it as internal and associate the appropriate error code. diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 56bbf6edb3..105896c6b5 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -74,7 +74,7 @@ export class JsonRpcServer { new Promise((resolve, reject) => { log("Closing JSON-RPC server"); this._httpServer.close(err => { - if (err) { + if (err !== null && err !== undefined) { log("Failed to close JSON-RPC server"); reject(err); return; @@ -87,7 +87,7 @@ export class JsonRpcServer { new Promise((resolve, reject) => { log("Closing websocket server"); this._wsServer.close(err => { - if (err) { + if (err !== null && err !== undefined) { log("Failed to close websocket server"); reject(err); return; diff --git a/packages/buidler-core/src/internal/util/jsonrpc.ts b/packages/buidler-core/src/internal/util/jsonrpc.ts index 7cad779530..feb008a598 100644 --- a/packages/buidler-core/src/internal/util/jsonrpc.ts +++ b/packages/buidler-core/src/internal/util/jsonrpc.ts @@ -33,6 +33,7 @@ export function parseJsonResponse(text: string): JsonRpcResponse { if (!isValidJsonResponse(json)) { // We are sending the proper error inside the catch part of the statement. // We just need to raise anything here. + // tslint:disable-next-line only-buidler-error throw new Error(); } From f3a18deb6594fbc6e3683a2cce39f9479268dda8 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Fri, 14 Feb 2020 21:19:23 +0100 Subject: [PATCH 21/99] Using event emitter instead of callback for eth subscription calls. --- .../internal/buidler-evm/provider/filter.ts | 85 +++++++++++-------- .../internal/buidler-evm/provider/input.ts | 2 +- .../buidler-evm/provider/modules/eth.ts | 39 +++------ .../src/internal/buidler-evm/provider/node.ts | 61 ++++++++----- .../internal/buidler-evm/provider/provider.ts | 7 ++ .../buidler-evm/provider/modules/eth.ts | 50 ++++++----- 6 files changed, 136 insertions(+), 108 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts index 085f523b40..0a2136a29e 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts @@ -1,7 +1,9 @@ import Bloom from "@nomiclabs/ethereumjs-vm/dist/bloom"; import { BN, bufferToHex, toBuffer } from "ethereumjs-util"; -import { getRpcLog, RpcLogOutput } from "./output"; +import { RpcLogOutput } from "./output"; + +export const LATEST_BLOCK = -1; export enum Type { LOGS_SUBSCRIPTION = 0, @@ -23,7 +25,7 @@ export interface Filter { deadline: Date; hashes: string[]; logs: RpcLogOutput[]; - subscription?: (emit: any) => {}; + subscription: boolean; } export function bloomFilter( @@ -66,54 +68,63 @@ export function filterLogs( continue; } - if (criteria.toBlock !== -1 && blockNumber > criteria.toBlock) { + if (criteria.toBlock !== LATEST_BLOCK && blockNumber > criteria.toBlock) { continue; } - let match: boolean = false; - const bAddress = toBuffer(log.address); - if (criteria.addresses.length !== 0) { - for (const address of criteria.addresses) { - if (Buffer.compare(address, bAddress) === 0) { - match = true; - } - } + if ( + criteria.addresses.length !== 0 && + !includes(criteria.addresses, toBuffer(log.address)) + ) { + continue; + } - if (!match) { - continue; - } + if (!topicMatched(criteria.topics, log.topics)) { + continue; } - match = true; - for (let i = 0; i < criteria.topics.length; i++) { - if (criteria.topics.length > log.topics.length) { - match = false; - continue; - } + filteredLogs.push(log); + } - const sub = criteria.topics[i]; - if (sub == null) { - continue; - } + return filteredLogs; +} - match = sub.length === 0; - for (const topic of sub) { - if (topic === null || log.topics[i] === bufferToHex(topic)) { - match = true; - break; - } - } - if (!match) { - break; - } +export function includes(addresses: Buffer[], a: Buffer): boolean { + for (const address of addresses) { + if (Buffer.compare(address, a) === 0) { + return true; } + } - if (!match) { + return false; +} + +export function topicMatched( + topics: Array | null>, + logTopics: string[] +): boolean { + let match = true; + for (let i = 0; i < topics.length; i++) { + if (topics.length > logTopics.length) { + return false; + } + + const sub = topics[i]; + if (sub == null) { continue; } - filteredLogs.push(log); + match = sub.length === 0; + for (const topic of sub) { + if (topic === null || logTopics[i] === bufferToHex(topic)) { + match = true; + break; + } + } + if (!match) { + return false; + } } - return filteredLogs; + return true; } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/input.ts b/packages/buidler-core/src/internal/buidler-evm/provider/input.ts index dbf10927b3..cb3dae02a4 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/input.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/input.ts @@ -261,7 +261,7 @@ export function validateParams( params: any[], subscribeRequest: typeof rpcSubscribeRequest, optionalFilterRequest: typeof optionalRpcFilterRequest -): [RpcSubscribeRequest, OptionalRpcFilterRequest, (emit: any) => {}]; +): [RpcSubscribeRequest, OptionalRpcFilterRequest]; export function validateParams(params: any[], number: typeof rpcQuantity): [BN]; diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index f97b106ec5..2b1052f44d 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -734,7 +734,7 @@ export class EthModule { } private async _newBlockFilterAction(): Promise { - const filterId = await this._node.newBlockFilter(); + const filterId = await this._node.newBlockFilter(false); return numberToRpcQuantity(filterId); } @@ -746,7 +746,7 @@ export class EthModule { private async _newFilterAction(filter: RpcFilterRequest): Promise { const filterParams = await this._rpcFilterRequestToGetLogsParams(filter); - const filterId = await this._node.newFilter(filterParams); + const filterId = await this._node.newFilter(filterParams, false); return numberToRpcQuantity(filterId); } @@ -757,7 +757,7 @@ export class EthModule { } private async _newPendingTransactionAction(): Promise { - const filterId = await this._node.newPendingTransactionFilter(); + const filterId = await this._node.newPendingTransactionFilter(false); return numberToRpcQuantity(filterId); } @@ -853,44 +853,31 @@ export class EthModule { private _subscribeParams( params: any[] - ): [RpcSubscribeRequest, OptionalRpcFilterRequest, (emit: any) => {}] { + ): [RpcSubscribeRequest, OptionalRpcFilterRequest] { if (params.length === 0) { - throw new InvalidInputError("Notifications not supported"); - } - - if (params.length === 1) { throw new InvalidInputError( "Expected subscription name as first argument" ); } - const callback = params.pop(); - if (!(typeof callback === "function")) { - throw new InvalidInputError("Notifications not supported"); - } - - const validatedParams: [ - RpcSubscribeRequest, - OptionalRpcFilterRequest, - (emit: any) => {} - ] = validateParams(params, rpcSubscribeRequest, optionalRpcFilterRequest); - - validatedParams.push(callback); - return validatedParams; + return validateParams( + params, + rpcSubscribeRequest, + optionalRpcFilterRequest + ); } private async _subscribeAction( subscribeRequest: RpcSubscribeRequest, - optionalFilterRequest: OptionalRpcFilterRequest, - callback: (emit: any) => {} + optionalFilterRequest: OptionalRpcFilterRequest ): Promise { let filterId: number; switch (subscribeRequest) { case "newHeads": - filterId = await this._node.newBlockFilter(callback); + filterId = await this._node.newBlockFilter(true); return numberToRpcQuantity(filterId); case "newPendingTransactions": - filterId = await this._node.newPendingTransactionFilter(callback); + filterId = await this._node.newPendingTransactionFilter(true); return numberToRpcQuantity(filterId); case "logs": if (optionalFilterRequest === undefined) { @@ -901,7 +888,7 @@ export class EthModule { optionalFilterRequest ); - filterId = await this._node.newFilter(filterParams, callback); + filterId = await this._node.newFilter(filterParams, true); return numberToRpcQuantity(filterId); } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 88f76943e9..70649d3c0e 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -24,6 +24,7 @@ import { privateToAddress, toBuffer } from "ethereumjs-util"; +import EventEmitter from "events"; import Trie from "merkle-patricia-tree/secure"; import { promisify } from "util"; @@ -41,8 +42,13 @@ import { VMTracer } from "../stack-traces/vm-tracer"; import { Blockchain } from "./blockchain"; import { InternalError, InvalidInputError } from "./errors"; -import { bloomFilter, Filter, filterLogs, Type } from "./filter"; -import { getRpcLog, RpcLogOutput } from "./output"; +import { bloomFilter, Filter, filterLogs, LATEST_BLOCK, Type } from "./filter"; +import { + getRpcBlock, + getRpcLog, + numberToRpcQuantity, + RpcLogOutput +} from "./output"; import { getCurrentTimestamp } from "./utils"; const log = debug("buidler:core:buidler-evm:node"); @@ -123,7 +129,7 @@ interface Snapshot { blockHashToTotalDifficulty: Map; } -export class BuidlerNode { +export class BuidlerNode extends EventEmitter { public static async create( hardfork: string, networkName: string, @@ -254,6 +260,7 @@ export class BuidlerNode { private readonly _throwOnCallFailures: boolean, stackTracesOptions?: SolidityTracerOptions ) { + super(); const config = getUserConfigPath(); this._stateManager = new PStateManager(this._vm.stateManager); this._common = this._vm._common as any; // TODO: There's a version mismatch, that's why we cast @@ -668,7 +675,7 @@ export class BuidlerNode { public async newFilter( filterParams: FilterParams, - callback?: (emit: any) => {} + isSubscription: boolean ): Promise { filterParams = await this._computeFilterParams(filterParams, true); @@ -685,15 +692,13 @@ export class BuidlerNode { deadline: this._newDeadline(), hashes: [], logs: await this.getLogs(filterParams), - subscription: callback + subscription: isSubscription }); return rpcID; } - public async newBlockFilter( - filterSubscription?: (emit: any) => {} - ): Promise { + public async newBlockFilter(isSubscription: boolean): Promise { const block = await this.getLatestBlock(); const rpcID: number = this._rpcID(); @@ -703,14 +708,14 @@ export class BuidlerNode { deadline: this._newDeadline(), hashes: [bufferToHex(block.header.hash())], logs: [], - subscription: filterSubscription + subscription: isSubscription }); return rpcID; } public async newPendingTransactionFilter( - filterSubscription?: (emit: any) => {} + isSubscription: boolean ): Promise { const rpcID: number = this._rpcID(); @@ -720,7 +725,7 @@ export class BuidlerNode { deadline: this._newDeadline(), hashes: [], logs: [], - subscription: filterSubscription + subscription: isSubscription }); return rpcID; @@ -736,8 +741,8 @@ export class BuidlerNode { const filter = this._filters.get(filterId); if ( - (filter!.subscription !== undefined && !subscription) || - (filter!.subscription === undefined && subscription) + (filter!.subscription && !subscription) || + (!filter!.subscription && subscription) ) { return false; } @@ -971,8 +976,11 @@ export class BuidlerNode { this._filters.forEach(filter => { if (filter.type === Type.PENDING_TRANSACTION_SUBSCRIPTION) { const hash = bufferToHex(tx.hash(true)); - if (filter.subscription !== undefined) { - filter.subscription(hash); + if (filter.subscription) { + this.emit("ethEvent", { + result: hash, + subscription: numberToRpcQuantity(filter.id) + }); return; } @@ -1046,8 +1054,11 @@ export class BuidlerNode { switch (filter.type) { case Type.BLOCK_SUBSCRIPTION: const hash = block.hash(); - if (filter.subscription !== undefined) { - filter.subscription(hash); + if (filter.subscription) { + this.emit("ethEvent", { + result: getRpcBlock(block, td, false), + subscription: numberToRpcQuantity(filter.id) + }); return; } @@ -1066,9 +1077,12 @@ export class BuidlerNode { return; } - if (filter.subscription !== undefined) { + if (filter.subscription) { logs.forEach(rpcLog => { - filter.subscription!(rpcLog); + this.emit("ethEvent", { + result: rpcLog, + subscription: numberToRpcQuantity(filter.id) + }); }); return; } @@ -1366,13 +1380,16 @@ export class BuidlerNode { filterParams: FilterParams, isFilter: boolean ): Promise { - if (filterParams.fromBlock === -1 || filterParams.toBlock === -1) { + if ( + filterParams.fromBlock === LATEST_BLOCK || + filterParams.toBlock === LATEST_BLOCK + ) { const block = await this.getLatestBlock(); - if (filterParams.fromBlock === -1) { + if (filterParams.fromBlock === LATEST_BLOCK) { filterParams.fromBlock = bufferToInt(block.header.number); } - if (!isFilter && filterParams.toBlock === -1) { + if (!isFilter && filterParams.toBlock === LATEST_BLOCK) { filterParams.toBlock = bufferToInt(block.header.number); } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 596b46383e..d3500a5cd5 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -181,5 +181,12 @@ export class BuidlerEVMProvider extends EventEmitter this._web3Module = new Web3Module(); this._evmModule = new EvmModule(node); this._buidlerModule = new BuidlerModule(node); + + const listener = (payload: { subscription: string; result: any }) => { + this.emit("notifications", payload); + }; + + // Handle eth_subscribe events and proxy them to handler + this._node.addListener("ethEvent", listener); } } diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index 4092b27489..3cef00b78e 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -2470,13 +2470,16 @@ describe("Eth module", function() { describe("eth_subscribe", async function() { it("Supports newHeads subscribe", async function() { - const heads: Buffer[] = []; - const filterId = await this.provider.send("eth_subscribe", [ - "newHeads", - (emit: any) => { - heads.push(emit); + const heads: any[] = []; + const filterId = await this.provider.send("eth_subscribe", ["newHeads"]); + + const listener = (payload: { subscription: string; result: any }) => { + if (filterId === payload.subscription) { + heads.push(payload.result); } - ]); + }; + + this.provider.addListener("notifications", listener); await this.provider.send("evm_mine", []); await this.provider.send("evm_mine", []); @@ -2484,20 +2487,23 @@ describe("Eth module", function() { assert.isTrue(await this.provider.send("eth_unsubscribe", [filterId])); - await this.provider.send("evm_mine", []); - assert.lengthOf(heads, 3); }); it("Supports newPendingTransactions subscribe", async function() { const pendingTransactions: string[] = []; const filterId = await this.provider.send("eth_subscribe", [ - "newPendingTransactions", - (emit: any) => { - pendingTransactions.push(emit); - } + "newPendingTransactions" ]); + const listener = (payload: { subscription: string; result: any }) => { + if (filterId === payload.subscription) { + pendingTransactions.push(payload.result); + } + }; + + this.provider.addListener("notifications", listener); + const accounts = await this.provider.send("eth_accounts"); const burnTxParams = { from: accounts[0], @@ -2521,16 +2527,21 @@ describe("Eth module", function() { ); const logs: RpcLogOutput[] = []; - await this.provider.send("eth_subscribe", [ + const filterId = await this.provider.send("eth_subscribe", [ "logs", { address: exampleContract - }, - (emit: any) => { - logs.push(emit); } ]); + const listener = (payload: { subscription: string; result: any }) => { + if (filterId === payload.subscription) { + logs.push(payload.result); + } + }; + + this.provider.addListener("notifications", listener); + const newState = "000000000000000000000000000000000000000000000000000000000000007b"; @@ -2554,12 +2565,7 @@ describe("Eth module", function() { describe("eth_unsubscribe", async function() { it("Supports unsubscribe", async function() { - const filterId = await this.provider.send("eth_subscribe", [ - "newHeads", - (emit: any) => { - console.log(emit); - } - ]); + const filterId = await this.provider.send("eth_subscribe", ["newHeads"]); assert.isTrue(await this.provider.send("eth_unsubscribe", [filterId])); }); From f2086edd27a83c32d89da0f2379223ebc639cbf1 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Mon, 17 Feb 2020 19:46:34 +0100 Subject: [PATCH 22/99] PR fixes. --- .../internal/buidler-evm/provider/filter.ts | 56 ++++++++----- .../buidler-evm/provider/modules/eth.ts | 59 +++++++------ .../src/internal/buidler-evm/provider/node.ts | 84 +++++++++++-------- .../internal/buidler-evm/provider/utils.ts | 6 ++ .../buidler-evm/provider/modules/eth.ts | 12 ++- 5 files changed, 122 insertions(+), 95 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts index 0a2136a29e..12737ba817 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts @@ -3,7 +3,7 @@ import { BN, bufferToHex, toBuffer } from "ethereumjs-util"; import { RpcLogOutput } from "./output"; -export const LATEST_BLOCK = -1; +export const LATEST_BLOCK = new BN(-1); export enum Type { LOGS_SUBSCRIPTION = 0, @@ -12,14 +12,14 @@ export enum Type { } export interface FilterCriteria { - fromBlock: number; - toBlock: number; + fromBlock: BN; + toBlock: BN; addresses: Buffer[]; - topics: Array | null>; + normalizedTopics: Array | null>; } export interface Filter { - id: number; + id: string; type: Type; criteria?: FilterCriteria; deadline: Date; @@ -31,18 +31,28 @@ export interface Filter { export function bloomFilter( bloom: Bloom, addresses: Buffer[], - topics: Array | null> + normalizedTopics: Array | null> ): boolean { - if (addresses.length > 0 && !bloom.multiCheck(addresses)) { - return false; + if (addresses.length > 0) { + let included = false; + for (const address of addresses) { + if (bloom.check(address)) { + included = true; + break; + } + } + + if (!included) { + return false; + } } - for (const sub of topics) { - if (sub == null) { + for (const sub of normalizedTopics) { + if (sub == null || sub.length === 0) { continue; } - let included = sub.length === 1; + let included = false; for (const topic of sub) { if (topic != null && bloom.check(topic)) { included = true; @@ -63,12 +73,15 @@ export function filterLogs( ): RpcLogOutput[] { const filteredLogs: RpcLogOutput[] = []; for (const log of logs) { - const blockNumber = new BN(toBuffer(log.blockNumber!)).toNumber(); - if (blockNumber < criteria.fromBlock) { + const blockNumber = new BN(toBuffer(log.blockNumber!)); + if (blockNumber.lt(criteria.fromBlock)) { continue; } - if (criteria.toBlock !== LATEST_BLOCK && blockNumber > criteria.toBlock) { + if ( + !criteria.toBlock.eq(LATEST_BLOCK) && + blockNumber.gt(criteria.toBlock) + ) { continue; } @@ -79,7 +92,7 @@ export function filterLogs( continue; } - if (!topicMatched(criteria.topics, log.topics)) { + if (!topicMatched(criteria.normalizedTopics, log.topics)) { continue; } @@ -100,21 +113,20 @@ export function includes(addresses: Buffer[], a: Buffer): boolean { } export function topicMatched( - topics: Array | null>, + normalizedTopics: Array | null>, logTopics: string[] ): boolean { - let match = true; - for (let i = 0; i < topics.length; i++) { - if (topics.length > logTopics.length) { + for (let i = 0; i < normalizedTopics.length; i++) { + if (normalizedTopics.length > logTopics.length) { return false; } - const sub = topics[i]; - if (sub == null) { + const sub = normalizedTopics[i]; + if (sub == null || sub.length === 0) { continue; } - match = sub.length === 0; + let match: boolean = false; for (const topic of sub) { if (topic === null || logTopics[i] === bufferToHex(topic)) { match = true; diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index 2b1052f44d..31a8e1a2bb 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -15,6 +15,7 @@ import { MethodNotFoundError, MethodNotSupportedError } from "../errors"; +import { LATEST_BLOCK } from "../filter"; import { LogAddress, LogTopics, @@ -55,6 +56,7 @@ import { RpcTransactionOutput, RpcTransactionReceiptOutput } from "../output"; +import { BNtoHex } from "../utils"; // tslint:disable only-buidler-error @@ -483,7 +485,7 @@ export class EthModule { private async _getFilterChangesAction( filterId: BN ): Promise { - const id = filterId.toNumber(); + const id = BNtoHex(filterId); const changes = await this._node.getFilterChanges(id); if (changes === undefined) { return null; @@ -501,7 +503,7 @@ export class EthModule { private async _getFilterLogsAction( filterId: BN ): Promise { - const id = filterId.toNumber(); + const id = BNtoHex(filterId); const changes = await this._node.getFilterLogs(id); if (changes === undefined) { return null; @@ -537,7 +539,7 @@ export class EthModule { return { fromBlock: this._extractBlock(filter.fromBlock), toBlock: this._extractBlock(filter.toBlock), - topics: this._extractLogTopics(filter.topics), + normalizedTopics: this._extractNormalizedLogTopics(filter.topics), addresses: this._extractLogAddresses(filter.address) }; } @@ -734,8 +736,7 @@ export class EthModule { } private async _newBlockFilterAction(): Promise { - const filterId = await this._node.newBlockFilter(false); - return numberToRpcQuantity(filterId); + return this._node.newBlockFilter(false); } // eth_newFilter @@ -746,8 +747,7 @@ export class EthModule { private async _newFilterAction(filter: RpcFilterRequest): Promise { const filterParams = await this._rpcFilterRequestToGetLogsParams(filter); - const filterId = await this._node.newFilter(filterParams, false); - return numberToRpcQuantity(filterId); + return this._node.newFilter(filterParams, false); } // eth_newPendingTransactionFilter @@ -757,8 +757,7 @@ export class EthModule { } private async _newPendingTransactionAction(): Promise { - const filterId = await this._node.newPendingTransactionFilter(false); - return numberToRpcQuantity(filterId); + return this._node.newPendingTransactionFilter(false); } // eth_pendingTransactions @@ -871,14 +870,11 @@ export class EthModule { subscribeRequest: RpcSubscribeRequest, optionalFilterRequest: OptionalRpcFilterRequest ): Promise { - let filterId: number; switch (subscribeRequest) { case "newHeads": - filterId = await this._node.newBlockFilter(true); - return numberToRpcQuantity(filterId); + return this._node.newBlockFilter(true); case "newPendingTransactions": - filterId = await this._node.newPendingTransactionFilter(true); - return numberToRpcQuantity(filterId); + return this._node.newPendingTransactionFilter(true); case "logs": if (optionalFilterRequest === undefined) { throw new InvalidArgumentsError("missing params argument"); @@ -888,8 +884,7 @@ export class EthModule { optionalFilterRequest ); - filterId = await this._node.newFilter(filterParams, true); - return numberToRpcQuantity(filterId); + return this._node.newFilter(filterParams, true); } } @@ -910,7 +905,8 @@ export class EthModule { } private async _uninstallFilterAction(filterId: BN): Promise { - return this._node.uninstallFilter(filterId.toNumber(), false); + const id = BNtoHex(filterId); + return this._node.uninstallFilter(id, false); } private _unsubscribeParams(params: any[]): [BN] { @@ -918,7 +914,8 @@ export class EthModule { } private async _unsubscribeAction(filterId: BN): Promise { - return this._node.uninstallFilter(filterId.toNumber(), true); + const id = BNtoHex(filterId); + return this._node.uninstallFilter(id, true); } // Utility methods @@ -982,37 +979,37 @@ export class EthModule { } } - private _extractBlock(blockTag: OptionalBlockTag): number { + private _extractBlock(blockTag: OptionalBlockTag): BN { switch (blockTag) { case "earliest": - return 0; + return new BN(0); case undefined: case "latest": - return -1; + return LATEST_BLOCK; case "pending": throw new InvalidArgumentsError("pending not supported"); } - return blockTag.toNumber(); + return blockTag; } - private _extractLogTopics( - logTopics: LogTopics + private _extractNormalizedLogTopics( + topics: LogTopics ): Array | null> { - if (logTopics === undefined || logTopics.length === 0) { + if (topics === undefined || topics.length === 0) { return []; } - const topics: Array | null> = []; - for (const logTopic of logTopics) { - if (Buffer.isBuffer(logTopic)) { - topics.push([logTopic]); + const normalizedTopics: Array | null> = []; + for (const topic of topics) { + if (Buffer.isBuffer(topic)) { + normalizedTopics.push([topic]); } else { - topics.push(logTopic); + normalizedTopics.push(topic); } } - return topics; + return normalizedTopics; } private _extractLogAddresses(address: LogAddress): Buffer[] { diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 70649d3c0e..e97cc67bf8 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -17,7 +17,6 @@ import { FakeTransaction, Transaction } from "ethereumjs-tx"; import { BN, bufferToHex, - bufferToInt, ECDSASignature, ecsign, hashPersonalMessage, @@ -88,14 +87,21 @@ export interface TransactionParams { } export interface FilterParams { - fromBlock: number; - toBlock: number; + fromBlock: BN; + toBlock: BN; addresses: Buffer[]; - topics: Array | null>; + normalizedTopics: Array | null>; } export class TransactionExecutionError extends Error {} +export interface TxReceipt { + status: 0 | 1; + gasUsed: Buffer; + bitvector: Buffer; + logs: RpcLogOutput[]; +} + export interface TxBlockResult { receipt: TxReceipt; createAddresses: Buffer | undefined; @@ -236,7 +242,7 @@ export class BuidlerNode extends EventEmitter { private _blockHashToTotalDifficulty: Map = new Map(); private _lastFilterId = 0; - private _filters: Map = new Map(); + private _filters: Map = new Map(); private _nextSnapshotId = 1; // We start in 1 to mimic Ganache private readonly _snapshots: Snapshot[] = []; @@ -676,10 +682,10 @@ export class BuidlerNode extends EventEmitter { public async newFilter( filterParams: FilterParams, isSubscription: boolean - ): Promise { + ): Promise { filterParams = await this._computeFilterParams(filterParams, true); - const rpcID: number = this._rpcID(); + const rpcID: string = this._rpcID(); this._filters.set(rpcID, { id: rpcID, type: Type.LOGS_SUBSCRIPTION, @@ -687,7 +693,7 @@ export class BuidlerNode extends EventEmitter { fromBlock: filterParams.fromBlock, toBlock: filterParams.toBlock, addresses: filterParams.addresses, - topics: filterParams.topics + normalizedTopics: filterParams.normalizedTopics }, deadline: this._newDeadline(), hashes: [], @@ -698,10 +704,10 @@ export class BuidlerNode extends EventEmitter { return rpcID; } - public async newBlockFilter(isSubscription: boolean): Promise { + public async newBlockFilter(isSubscription: boolean): Promise { const block = await this.getLatestBlock(); - const rpcID: number = this._rpcID(); + const rpcID: string = this._rpcID(); this._filters.set(rpcID, { id: rpcID, type: Type.BLOCK_SUBSCRIPTION, @@ -716,8 +722,8 @@ export class BuidlerNode extends EventEmitter { public async newPendingTransactionFilter( isSubscription: boolean - ): Promise { - const rpcID: number = this._rpcID(); + ): Promise { + const rpcID: string = this._rpcID(); this._filters.set(rpcID, { id: rpcID, @@ -732,7 +738,7 @@ export class BuidlerNode extends EventEmitter { } public async uninstallFilter( - filterId: number, + filterId: string, subscription: boolean ): Promise { if (!this._filters.has(filterId)) { @@ -752,7 +758,7 @@ export class BuidlerNode extends EventEmitter { } public async getFilterChanges( - filterId: number + filterId: string ): Promise { const filter = this._filters.get(filterId); if (filter === undefined) { @@ -776,7 +782,7 @@ export class BuidlerNode extends EventEmitter { } public async getFilterLogs( - filterId: number + filterId: string ): Promise { const filter = this._filters.get(filterId); if (filter === undefined) { @@ -793,7 +799,11 @@ export class BuidlerNode extends EventEmitter { filterParams = await this._computeFilterParams(filterParams, false); const logs: RpcLogOutput[] = []; - for (let i = filterParams.fromBlock; i <= filterParams.toBlock; i++) { + for ( + let i = filterParams.fromBlock; + i <= filterParams.toBlock; + i = i.addn(1) + ) { const block = await this._getBlock(new BN(i)); const blockResults = this._blockHashToTxBlockResults.get( bufferToHex(block.hash()) @@ -806,7 +816,7 @@ export class BuidlerNode extends EventEmitter { !bloomFilter( new Bloom(block.header.bloom), filterParams.addresses, - filterParams.topics + filterParams.normalizedTopics ) ) { continue; @@ -818,7 +828,7 @@ export class BuidlerNode extends EventEmitter { fromBlock: filterParams.fromBlock, toBlock: filterParams.toBlock, addresses: filterParams.addresses, - topics: filterParams.topics + normalizedTopics: filterParams.normalizedTopics }) ); } @@ -979,7 +989,7 @@ export class BuidlerNode extends EventEmitter { if (filter.subscription) { this.emit("ethEvent", { result: hash, - subscription: numberToRpcQuantity(filter.id) + subscription: filter.id }); return; } @@ -1017,7 +1027,8 @@ export class BuidlerNode extends EventEmitter { for (let i = 0; i < runBlockResult.results.length; i += 1) { const result = runBlockResult.results[i]; - runBlockResult.receipts[i].logs.forEach( + const receipt = runBlockResult.receipts[i]; + const logs = receipt.logs.map( (rcpLog, logIndex) => (runBlockResult.receipts[i].logs[logIndex] = getRpcLog( rcpLog, @@ -1031,7 +1042,12 @@ export class BuidlerNode extends EventEmitter { txBlockResults.push({ bloomBitvector: result.bloom.bitvector, createAddresses: result.createdAddress, - receipt: runBlockResult.receipts[i] + receipt: { + status: receipt.status, + gasUsed: receipt.gasUsed, + bitvector: receipt.bitvector, + logs + } }); } @@ -1057,19 +1073,19 @@ export class BuidlerNode extends EventEmitter { if (filter.subscription) { this.emit("ethEvent", { result: getRpcBlock(block, td, false), - subscription: numberToRpcQuantity(filter.id) + subscription: filter.id }); return; } - filter.hashes.push(hash); + filter.hashes.push(bufferToHex(hash)); break; case Type.LOGS_SUBSCRIPTION: if ( bloomFilter( new Bloom(block.header.bloom), filter.criteria!.addresses, - filter.criteria!.topics + filter.criteria!.normalizedTopics ) ) { const logs = filterLogs(rpcLogs, filter.criteria!); @@ -1081,7 +1097,7 @@ export class BuidlerNode extends EventEmitter { logs.forEach(rpcLog => { this.emit("ethEvent", { result: rpcLog, - subscription: numberToRpcQuantity(filter.id) + subscription: filter.id }); }); return; @@ -1357,17 +1373,15 @@ export class BuidlerNode extends EventEmitter { } } - private _removeLogs(blockNumber: number) { + private _removeLogs(block: Block) { + const blockNumber = new BN(block.header.number); this._filters.forEach(filter => { if (filter.type !== Type.LOGS_SUBSCRIPTION) { return; } for (let i = filter.logs.length - 1; i >= 0; i--) { - if ( - new BN(toBuffer(filter.logs[i].blockNumber!)).toNumber() <= - blockNumber - ) { + if (new BN(filter.logs[i].blockNumber!).lte(blockNumber)) { break; } @@ -1386,11 +1400,11 @@ export class BuidlerNode extends EventEmitter { ) { const block = await this.getLatestBlock(); if (filterParams.fromBlock === LATEST_BLOCK) { - filterParams.fromBlock = bufferToInt(block.header.number); + filterParams.fromBlock = new BN(block.header.number); } if (!isFilter && filterParams.toBlock === LATEST_BLOCK) { - filterParams.toBlock = bufferToInt(block.header.number); + filterParams.toBlock = new BN(block.header.number); } } @@ -1399,13 +1413,13 @@ export class BuidlerNode extends EventEmitter { private _newDeadline(): Date { const dt = new Date(); - dt.setMinutes(dt.getMinutes() + 5); + dt.setMinutes(dt.getMinutes() + 5); // This will not overflow return dt; } - private _rpcID(): number { + private _rpcID(): string { this._lastFilterId += 1; - return this._lastFilterId; + return `0x${this._lastFilterId.toString(16)}`; } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts b/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts index c50a8bc968..c0f1ca1033 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts @@ -1,3 +1,9 @@ +import { BN } from "ethereumjs-util"; + export function getCurrentTimestamp(): number { return Math.ceil(new Date().getTime() / 1000); } + +export function BNtoHex(bn: BN): string { + return `0x${bn.toString(16)}`; +} diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index 3cef00b78e..d70bf9d3ac 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -806,13 +806,14 @@ describe("Eth module", function() { 1 ); + const snapshotId: string = await this.provider.send("evm_snapshot", []); + await this.provider.send("evm_mine", []); const block1 = await this.provider.send("eth_getBlockByNumber", [ await this.provider.send("eth_blockNumber"), false ]); - const snapshotId: string = await this.provider.send("evm_snapshot", []); await this.provider.send("evm_revert", [snapshotId]); await this.provider.send("evm_mine", []); @@ -825,10 +826,7 @@ describe("Eth module", function() { filterId ]); - assert.deepEqual(blockHashes, [ - toBuffer(block1.hash), - toBuffer(block2.hash) - ]); + assert.deepEqual(blockHashes, [block1.hash, block2.hash]); }); }); @@ -2087,7 +2085,7 @@ describe("Eth module", function() { gas: numberToRpcQuantity(21000) }; - this.provider.send("eth_sendTransaction", [burnTxParams]); + await this.provider.send("eth_sendTransaction", [burnTxParams]); const txHashes = await this.provider.send("eth_getFilterChanges", [ filterId ]); @@ -2114,7 +2112,7 @@ describe("Eth module", function() { gas: numberToRpcQuantity(21000) }; - this.provider.send("eth_sendTransaction", [burnTxParams]); + await this.provider.send("eth_sendTransaction", [burnTxParams]); const txHashes = await this.provider.send("eth_getFilterChanges", [ filterId ]); From 187fc7b7b807869d7891f5b72f24d7c01eb8dee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Milar?= Date: Mon, 17 Feb 2020 16:11:23 -0300 Subject: [PATCH 23/99] Add clarification for testing requirements for a Windows machine. (#447) --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70b70df106..68f166bbc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,9 @@ docker pull ethereum/vyper:0.1.0b10 ``` ### Entire project -You can run all the tests at once by running `npm test` from the root folder. Requires `ethereum/vyper` docker instance installed for package [buidler-vyper](./packages/buidler-vyper), see previous section for details. +You can run all the tests at once by running `npm test` from the root folder. + +For the case of package [buidler-vyper](./packages/buidler-vyper), an `ethereum/vyper` docker instance installed is required (see previous section for details). _Exception_ of this requirement is if running on a Windows local machine, in this case we skip it by default since Win 10 Pro version would be also required. ## Code formatting From 2f03b8dd13126ffb903af028c5a4b7a501b92640 Mon Sep 17 00:00:00 2001 From: "nebojsa.urosevic" Date: Wed, 19 Feb 2020 14:41:25 +0100 Subject: [PATCH 24/99] Remove logs removal upon snapshot reverting. --- .../src/internal/buidler-evm/provider/node.ts | 27 +----------- .../buidler-evm/provider/modules/eth.ts | 44 ------------------- 2 files changed, 1 insertion(+), 70 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index e97cc67bf8..24682e4413 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -42,12 +42,7 @@ import { VMTracer } from "../stack-traces/vm-tracer"; import { Blockchain } from "./blockchain"; import { InternalError, InvalidInputError } from "./errors"; import { bloomFilter, Filter, filterLogs, LATEST_BLOCK, Type } from "./filter"; -import { - getRpcBlock, - getRpcLog, - numberToRpcQuantity, - RpcLogOutput -} from "./output"; +import { getRpcBlock, getRpcLog, RpcLogOutput } from "./output"; import { getCurrentTimestamp } from "./utils"; const log = debug("buidler:core:buidler-evm:node"); @@ -669,9 +664,6 @@ export class BuidlerNode extends EventEmitter { this._blockHashToTxBlockResults = snapshot.blockHashToTxBlockResults; this._blockHashToTotalDifficulty = snapshot.blockHashToTotalDifficulty; - // Mark logs in log filters as removed - this._removeLogs(snapshot.latestBlock); - // We delete this and the following snapshots, as they can only be used // once in Ganache this._snapshots.splice(snapshotIndex); @@ -1373,23 +1365,6 @@ export class BuidlerNode extends EventEmitter { } } - private _removeLogs(block: Block) { - const blockNumber = new BN(block.header.number); - this._filters.forEach(filter => { - if (filter.type !== Type.LOGS_SUBSCRIPTION) { - return; - } - - for (let i = filter.logs.length - 1; i >= 0; i--) { - if (new BN(filter.logs[i].blockNumber!).lte(blockNumber)) { - break; - } - - filter.logs[i].removed = true; - } - }); - } - private async _computeFilterParams( filterParams: FilterParams, isFilter: boolean diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index d70bf9d3ac..5570379ad9 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -1096,50 +1096,6 @@ describe("Eth module", function() { 1 ); }); - - it("Supports get filter logs with revert", async function() { - const exampleContract = await deployContract( - this.provider, - `0x${EXAMPLE_CONTRACT.bytecode.object}` - ); - - const newState = - "000000000000000000000000000000000000000000000000000000000000003b"; - - const filterId = await this.provider.send("eth_newFilter", [ - { - address: exampleContract, - topics: [ - [ - "0x3359f789ea83a10b6e9605d460de1088ff290dd7b3c9a155c896d45cf495ed4d" - ] - ] - } - ]); - - const snapshotId: string = await this.provider.send("evm_snapshot", []); - - await this.provider.send("eth_sendTransaction", [ - { - to: exampleContract, - from: DEFAULT_ACCOUNTS_ADDRESSES[0], - data: EXAMPLE_CONTRACT.selectors.modifiesState + newState - } - ]); - - await this.provider.send("evm_revert", [snapshotId]); - - const logs = await this.provider.send("eth_getFilterLogs", [filterId]); - assert.lengthOf(logs, 1); - - const log = logs[0]; - assert.equal(log.removed, true); - assert.equal(log.logIndex, "0x0"); - assert.equal(log.transactionIndex, "0x0"); - assert.equal(log.blockNumber, "0x2"); - assert.equal(log.address, exampleContract); - assert.equal(log.data, `0x${newState}`); - }); }); describe("eth_getLogs", async function() { From c60cd7e9d60456e85a47abd8d4bed59178071de2 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Wed, 19 Feb 2020 15:28:25 -0300 Subject: [PATCH 25/99] WIP --- .../internal/buidler-evm/jsonrpc/handler.ts | 3 +- .../src/internal/buidler-evm/provider/node.ts | 123 +++++++++++------- .../internal/buidler-evm/provider/provider.ts | 19 ++- .../internal/core/config/config-loading.ts | 49 +------ 4 files changed, 88 insertions(+), 106 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 64c5d3108a..e3f1168a74 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -135,7 +135,7 @@ export default class JsonRpcHandler { private _handleRequest = async ( req: JsonRpcRequest ): Promise => { - console.log(req.method); + // console.log(req.method); const result = await this._provider.send(req.method, req.params); @@ -202,6 +202,7 @@ const _handleError = (error: any): JsonRpcResponse => { }; const _printError = (error: any) => { + return; if (BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { // Report the error to console in the format of other BuidlerErrors (wrappedError.message), // while preserving the stack from the originating error (error.stack). diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 9570058fa4..62d723bb03 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -32,6 +32,7 @@ import { createModelsAndDecodeBytecodes } from "../stack-traces/compiler-to-mode import { CompilerInput, CompilerOutput } from "../stack-traces/compiler-types"; import { ConsoleLogger } from "../stack-traces/consoleLogger"; import { ContractsIdentifier } from "../stack-traces/contracts-identifier"; +import { MessageTrace } from "../stack-traces/message-trace"; import { decodeRevertReason } from "../stack-traces/revert-reasons"; import { encodeSolidityStackTrace } from "../stack-traces/solidity-errors"; import { SolidityStackTrace } from "../stack-traces/solidity-stack-trace"; @@ -127,7 +128,8 @@ export class BuidlerNode { throwOnTransactionFailures: boolean, throwOnCallFailures: boolean, genesisAccounts: GenesisAccount[] = [], - stackTracesOptions?: SolidityTracerOptions + stackTracesOptions?: SolidityTracerOptions, + loggingEnabled = true ): Promise<[Common, BuidlerNode]> { const stateTrie = new Trie(); const putIntoStateTrie = promisify(stateTrie.put.bind(stateTrie)); @@ -206,7 +208,8 @@ export class BuidlerNode { genesisBlock, throwOnTransactionFailures, throwOnCallFailures, - stackTracesOptions + stackTracesOptions, + loggingEnabled ); return [common, node]; @@ -230,7 +233,7 @@ export class BuidlerNode { private readonly _snapshots: Snapshot[] = []; private readonly _stackTracesEnabled: boolean = false; - private readonly _vmTracer?: VMTracer; + private readonly _vmTracer: VMTracer; private readonly _solidityTracer?: SolidityTracer; private readonly _consoleLogger: ConsoleLogger = new ConsoleLogger(); private _failedStackTraces = 0; @@ -246,7 +249,8 @@ export class BuidlerNode { genesisBlock: Block, private readonly _throwOnTransactionFailures: boolean, private readonly _throwOnCallFailures: boolean, - stackTracesOptions?: SolidityTracerOptions + stackTracesOptions?: SolidityTracerOptions, + private readonly _loggingEnabled = false ) { const config = getUserConfigPath(); this._stateManager = new PStateManager(this._vm.stateManager); @@ -348,12 +352,6 @@ export class BuidlerNode { throw new TransactionExecutionError(error); } - await this._printLogs(); - - const error = !this._throwOnTransactionFailures - ? undefined - : await this._manageErrors(result.results[0].execResult); - if (needsTimestampIncrease) { await this.increaseTime(new BN(1)); } @@ -361,8 +359,22 @@ export class BuidlerNode { await this._saveBlockAsSuccessfullyRun(block, result); await this._saveTransactionAsSuccessfullyRun(tx, block); - if (error !== undefined) { - throw error; + const vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); + const vmTracerError = this._vmTracer.getLastError(); + this._vmTracer.clearLastError(); + + await this._printLogs(vmTrace, vmTracerError); + + if (this._throwOnTransactionFailures) { + const error = await this._manageErrors( + result.results[0].execResult, + vmTrace, + vmTracerError + ); + + if (error !== undefined) { + throw error; + } } return result; @@ -413,14 +425,24 @@ export class BuidlerNode { nonce: await this.getAccountNonce(call.from) }); - const result = await this._runTxAndRevertMutations(tx, false, false); + const result = await this._runTxAndRevertMutations(tx, false); + + const vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); + const vmTracerError = this._vmTracer.getLastError(); + this._vmTracer.clearLastError(); - const error = !this._throwOnCallFailures - ? undefined - : await this._manageErrors(result.execResult); + await this._printLogs(vmTrace, vmTracerError); + + if (this._throwOnCallFailures) { + const error = await this._manageErrors( + result.execResult, + vmTrace, + vmTracerError + ); - if (error !== undefined) { - throw error; + if (error !== undefined) { + throw error; + } } if ( @@ -431,7 +453,7 @@ export class BuidlerNode { } // If we got here we found another kind of error and we throw anyway - throw this._manageErrors(result.execResult)!; + throw await this._manageErrors(result.execResult, vmTrace, vmTracerError)!; } public async getAccountBalance(address: Buffer): Promise { @@ -462,7 +484,7 @@ export class BuidlerNode { gasLimit: await this.getBlockGasLimit() }); - const result = await this._runTxAndRevertMutations(tx, true, true); + const result = await this._runTxAndRevertMutations(tx, true); // This is only considered if the call to _runTxAndRevertMutations doesn't // manage errors @@ -749,27 +771,26 @@ export class BuidlerNode { } } - private async _printLogs() { - try { - const vmTracerError = this._vmTracer!.getLastError(); - // in case stack traces are enabled we dont want to clear last error - if (vmTracerError !== undefined && !this._stackTracesEnabled) { - this._vmTracer!.clearLastError(); - throw vmTracerError; - } - - const messageTrace = this._vmTracer!.getLastTopLevelMessageTrace(); - this._consoleLogger.printLogs(messageTrace); - } catch (error) { + private async _printLogs( + vmTrace: MessageTrace, + vmTracerError: Error | undefined + ) { + if (vmTracerError !== undefined) { log( "Could not print console log. Please report this to help us improve Buidler.\n", - error + vmTracerError ); + + return; } + + this._consoleLogger.printLogs(vmTrace); } private async _manageErrors( - vmResult: ExecResult + vmResult: ExecResult, + vmTrace: MessageTrace, + vmTracerError?: Error ): Promise { if (vmResult.exceptionError === undefined) { return undefined; @@ -779,15 +800,12 @@ export class BuidlerNode { if (this._stackTracesEnabled) { try { - const vmTracerError = this._vmTracer!.getLastError(); if (vmTracerError !== undefined) { - this._vmTracer!.clearLastError(); throw vmTracerError; } - const messageTrace = this._vmTracer!.getLastTopLevelMessageTrace(); const decodedTrace = this._solidityTracer!.tryToDecodeMessageTrace( - messageTrace + vmTrace ); stackTrace = this._solidityTracer!.getStackTrace(decodedTrace); @@ -1050,7 +1068,7 @@ export class BuidlerNode { }); } - const result = await this._runTxAndRevertMutations(tx, false, true); + const result = await this._runTxAndRevertMutations(tx, false); if (result.execResult.exceptionError === undefined) { return initialEstimation; @@ -1118,7 +1136,7 @@ export class BuidlerNode { gasLimit: newEstimation }); - const result = await this._runTxAndRevertMutations(tx, false, true); + const result = await this._runTxAndRevertMutations(tx, false); if (result.execResult.exceptionError === undefined) { return this._binarySearchEstimation( @@ -1137,10 +1155,17 @@ export class BuidlerNode { ); } + /** + * This function runs a transaction and reverts all the modifications that it + * makes. + * + * If throwOnError is true, errors are managed locally and thrown on + * failure. If it's false, the tx's RunTxResult is returned, and the vmTracer + * inspected/resetted. + */ private async _runTxAndRevertMutations( tx: Transaction, - manageErrors = true, - estimateGas = false + throwOnError = true ): Promise { const initialStateRoot = await this._stateManager.getStateRoot(); @@ -1168,12 +1193,16 @@ export class BuidlerNode { throw new TransactionExecutionError(error); } - if (!estimateGas) { - await this._printLogs(); - } + if (throwOnError) { + const vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); + const vmTracerError = this._vmTracer.getLastError(); + this._vmTracer.clearLastError(); - if (manageErrors) { - const error = await this._manageErrors(result.execResult); + const error = await this._manageErrors( + result.execResult, + vmTrace, + vmTracerError + ); if (error !== undefined) { throw error; diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 596b46383e..180698cf67 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -45,7 +45,8 @@ export class BuidlerEVMProvider extends EventEmitter private readonly _throwOnCallFailures: boolean, private readonly _genesisAccounts: GenesisAccount[] = [], private readonly _solcVersion?: string, - private readonly _paths?: ProjectPaths + private readonly _paths?: ProjectPaths, + private readonly _loggingEnabled = false ) { super(); const config = getUserConfigPath(); @@ -55,7 +56,7 @@ export class BuidlerEVMProvider extends EventEmitter const release = await this._mutex.acquire(); try { - return await this._send(method, params); + return await this._sendDebug(method, params); } finally { release(); } @@ -63,15 +64,12 @@ export class BuidlerEVMProvider extends EventEmitter private async _sendDebug(method: string, params: any[] = []): Promise { try { - console.log(chalk.green(`RPC CALL ${method}`), params); - - const res = await this._send(method, params); + console.log(chalk.green(`JSON-RPC call: ${method}`)); - console.log(chalk.green(`Response of ${method}`), res); - - return res; + return await this._send(method, params); } catch (err) { - console.error(chalk.red(`Error running ${method}`), err); + console.error(chalk.red(err.message)); + console.error(err); throw err; } @@ -170,7 +168,8 @@ export class BuidlerEVMProvider extends EventEmitter this._throwOnTransactionFailures, this._throwOnCallFailures, this._genesisAccounts, - stackTracesOptions + stackTracesOptions, + this._loggingEnabled ); this._common = common; diff --git a/packages/buidler-core/src/internal/core/config/config-loading.ts b/packages/buidler-core/src/internal/core/config/config-loading.ts index a503f40614..bdb0fc7fe8 100644 --- a/packages/buidler-core/src/internal/core/config/config-loading.ts +++ b/packages/buidler-core/src/internal/core/config/config-loading.ts @@ -1,12 +1,6 @@ -import chalk from "chalk"; import path from "path"; -import { - BuidlerArguments, - BuidlerConfig, - ResolvedBuidlerConfig -} from "../../../types"; -import { BUIDLEREVM_NETWORK_NAME } from "../../constants"; +import { BuidlerArguments, ResolvedBuidlerConfig } from "../../../types"; import { BuidlerContext } from "../../context"; import { loadPluginFile } from "../plugins"; import { getUserConfigPath } from "../project-structure"; @@ -48,7 +42,6 @@ export function loadConfigAndTasks( const defaultConfig = importCsjOrEsModule("./default-config"); const userConfig = importCsjOrEsModule(configPath); - validateConfig(userConfig); // To avoid bad practices we remove the previously exported stuff @@ -61,45 +54,5 @@ export function loadConfigAndTasks( BuidlerContext.getBuidlerContext().configExtenders ); - showBuidlerEVMActivationWarningIfNecessary( - userConfig, - resolved, - defaultConfig, - buidlerArguments - ); - return resolved; } - -function showBuidlerEVMActivationWarningIfNecessary( - userConfig: BuidlerConfig, - resolved: ResolvedBuidlerConfig, - defaultConfig: BuidlerConfig, - buidlerArguments?: Partial -) { - if (defaultConfig.defaultNetwork !== BUIDLEREVM_NETWORK_NAME) { - return; - } - - if ( - buidlerArguments === undefined || - buidlerArguments.network !== undefined - ) { - return; - } - - if (userConfig.defaultNetwork !== undefined) { - return; - } - - if (resolved.defaultNetwork !== BUIDLEREVM_NETWORK_NAME) { - return; - } - - console.warn( - chalk.yellow( - `Using the built-in experimental development network "${BUIDLEREVM_NETWORK_NAME}". This enables combined JavaScript and Solidity stack traces. -To silence this warning set defaultNetwork in your config to "${BUIDLEREVM_NETWORK_NAME}", or to the network of your choosing (also works through --network).` - ) - ); -} From 108f679e0bcc5134139ff91bf6707be8c0547a51 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Wed, 19 Feb 2020 19:28:00 -0300 Subject: [PATCH 26/99] Remove logging flag from BEVM's Node --- .../src/internal/buidler-evm/provider/node.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 49e0298ab1..bf18b9c74d 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -144,8 +144,7 @@ export class BuidlerNode extends EventEmitter { throwOnTransactionFailures: boolean, throwOnCallFailures: boolean, genesisAccounts: GenesisAccount[] = [], - stackTracesOptions?: SolidityTracerOptions, - loggingEnabled = true + stackTracesOptions?: SolidityTracerOptions ): Promise<[Common, BuidlerNode]> { const stateTrie = new Trie(); const putIntoStateTrie = promisify(stateTrie.put.bind(stateTrie)); @@ -224,8 +223,7 @@ export class BuidlerNode extends EventEmitter { genesisBlock, throwOnTransactionFailures, throwOnCallFailures, - stackTracesOptions, - loggingEnabled + stackTracesOptions ); return [common, node]; @@ -265,8 +263,7 @@ export class BuidlerNode extends EventEmitter { genesisBlock: Block, private readonly _throwOnTransactionFailures: boolean, private readonly _throwOnCallFailures: boolean, - stackTracesOptions?: SolidityTracerOptions, - private readonly _loggingEnabled = false + stackTracesOptions?: SolidityTracerOptions ) { super(); const config = getUserConfigPath(); From 77fa15890072746062c5fe36da0e8092959f423f Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Wed, 19 Feb 2020 20:52:30 -0300 Subject: [PATCH 27/99] Don't leak filter ids representation outside of Node --- .../internal/buidler-evm/provider/filter.ts | 2 +- .../buidler-evm/provider/modules/eth.ts | 32 +++---- .../src/internal/buidler-evm/provider/node.ts | 84 ++++++++++--------- .../internal/buidler-evm/provider/provider.ts | 11 ++- .../internal/buidler-evm/provider/utils.ts | 6 -- 5 files changed, 70 insertions(+), 65 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts index 12737ba817..6302d2db92 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/filter.ts @@ -19,7 +19,7 @@ export interface FilterCriteria { } export interface Filter { - id: string; + id: BN; type: Type; criteria?: FilterCriteria; deadline: Date; diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index 31a8e1a2bb..740fb659da 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -56,7 +56,6 @@ import { RpcTransactionOutput, RpcTransactionReceiptOutput } from "../output"; -import { BNtoHex } from "../utils"; // tslint:disable only-buidler-error @@ -485,8 +484,7 @@ export class EthModule { private async _getFilterChangesAction( filterId: BN ): Promise { - const id = BNtoHex(filterId); - const changes = await this._node.getFilterChanges(id); + const changes = await this._node.getFilterChanges(filterId); if (changes === undefined) { return null; } @@ -503,8 +501,7 @@ export class EthModule { private async _getFilterLogsAction( filterId: BN ): Promise { - const id = BNtoHex(filterId); - const changes = await this._node.getFilterLogs(id); + const changes = await this._node.getFilterLogs(filterId); if (changes === undefined) { return null; } @@ -736,7 +733,8 @@ export class EthModule { } private async _newBlockFilterAction(): Promise { - return this._node.newBlockFilter(false); + const filterId = await this._node.newBlockFilter(false); + return numberToRpcQuantity(filterId); } // eth_newFilter @@ -747,7 +745,8 @@ export class EthModule { private async _newFilterAction(filter: RpcFilterRequest): Promise { const filterParams = await this._rpcFilterRequestToGetLogsParams(filter); - return this._node.newFilter(filterParams, false); + const filterId = await this._node.newFilter(filterParams, false); + return numberToRpcQuantity(filterId); } // eth_newPendingTransactionFilter @@ -757,7 +756,8 @@ export class EthModule { } private async _newPendingTransactionAction(): Promise { - return this._node.newPendingTransactionFilter(false); + const filterId = await this._node.newPendingTransactionFilter(false); + return numberToRpcQuantity(filterId); } // eth_pendingTransactions @@ -872,9 +872,11 @@ export class EthModule { ): Promise { switch (subscribeRequest) { case "newHeads": - return this._node.newBlockFilter(true); + return numberToRpcQuantity(await this._node.newBlockFilter(true)); case "newPendingTransactions": - return this._node.newPendingTransactionFilter(true); + return numberToRpcQuantity( + await this._node.newPendingTransactionFilter(true) + ); case "logs": if (optionalFilterRequest === undefined) { throw new InvalidArgumentsError("missing params argument"); @@ -884,7 +886,9 @@ export class EthModule { optionalFilterRequest ); - return this._node.newFilter(filterParams, true); + return numberToRpcQuantity( + await this._node.newFilter(filterParams, true) + ); } } @@ -905,8 +909,7 @@ export class EthModule { } private async _uninstallFilterAction(filterId: BN): Promise { - const id = BNtoHex(filterId); - return this._node.uninstallFilter(id, false); + return this._node.uninstallFilter(filterId, false); } private _unsubscribeParams(params: any[]): [BN] { @@ -914,8 +917,7 @@ export class EthModule { } private async _unsubscribeAction(filterId: BN): Promise { - const id = BNtoHex(filterId); - return this._node.uninstallFilter(id, true); + return this._node.uninstallFilter(filterId, true); } // Utility methods diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index bf18b9c74d..11e70c4704 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -240,7 +240,7 @@ export class BuidlerNode extends EventEmitter { private _blockHashToTxBlockResults: Map = new Map(); private _blockHashToTotalDifficulty: Map = new Map(); - private _lastFilterId = 0; + private _lastFilterId = new BN(0); private _filters: Map = new Map(); private _nextSnapshotId = 1; // We start in 1 to mimic Ganache @@ -701,12 +701,12 @@ export class BuidlerNode extends EventEmitter { public async newFilter( filterParams: FilterParams, isSubscription: boolean - ): Promise { + ): Promise { filterParams = await this._computeFilterParams(filterParams, true); - const rpcID: string = this._rpcID(); - this._filters.set(rpcID, { - id: rpcID, + const filterId = this._getNextFilterId(); + this._filters.set(this._filterIdToFiltersKey(filterId), { + id: filterId, type: Type.LOGS_SUBSCRIPTION, criteria: { fromBlock: filterParams.fromBlock, @@ -720,15 +720,15 @@ export class BuidlerNode extends EventEmitter { subscription: isSubscription }); - return rpcID; + return filterId; } - public async newBlockFilter(isSubscription: boolean): Promise { + public async newBlockFilter(isSubscription: boolean): Promise { const block = await this.getLatestBlock(); - const rpcID: string = this._rpcID(); - this._filters.set(rpcID, { - id: rpcID, + const filterId = this._getNextFilterId(); + this._filters.set(this._filterIdToFiltersKey(filterId), { + id: filterId, type: Type.BLOCK_SUBSCRIPTION, deadline: this._newDeadline(), hashes: [bufferToHex(block.header.hash())], @@ -736,16 +736,16 @@ export class BuidlerNode extends EventEmitter { subscription: isSubscription }); - return rpcID; + return filterId; } public async newPendingTransactionFilter( isSubscription: boolean - ): Promise { - const rpcID: string = this._rpcID(); + ): Promise { + const filterId = this._getNextFilterId(); - this._filters.set(rpcID, { - id: rpcID, + this._filters.set(this._filterIdToFiltersKey(filterId), { + id: filterId, type: Type.PENDING_TRANSACTION_SUBSCRIPTION, deadline: this._newDeadline(), hashes: [], @@ -753,18 +753,20 @@ export class BuidlerNode extends EventEmitter { subscription: isSubscription }); - return rpcID; + return filterId; } public async uninstallFilter( - filterId: string, + filterId: BN, subscription: boolean ): Promise { - if (!this._filters.has(filterId)) { + const key = this._filterIdToFiltersKey(filterId); + const filter = this._filters.get(key); + + if (filter === undefined) { return false; } - const filter = this._filters.get(filterId); if ( (filter!.subscription && !subscription) || (!filter!.subscription && subscription) @@ -772,14 +774,15 @@ export class BuidlerNode extends EventEmitter { return false; } - this._filters.delete(filterId); + this._filters.delete(key); return true; } public async getFilterChanges( - filterId: string + filterId: BN ): Promise { - const filter = this._filters.get(filterId); + const key = this._filterIdToFiltersKey(filterId); + const filter = this._filters.get(key); if (filter === undefined) { return undefined; } @@ -801,9 +804,10 @@ export class BuidlerNode extends EventEmitter { } public async getFilterLogs( - filterId: string + filterId: BN ): Promise { - const filter = this._filters.get(filterId); + const key = this._filterIdToFiltersKey(filterId); + const filter = this._filters.get(key); if (filter === undefined) { return undefined; } @@ -1002,10 +1006,7 @@ export class BuidlerNode extends EventEmitter { if (filter.type === Type.PENDING_TRANSACTION_SUBSCRIPTION) { const hash = bufferToHex(tx.hash(true)); if (filter.subscription) { - this.emit("ethEvent", { - result: hash, - subscription: filter.id - }); + this._emitEthEvent(filter.id, hash); return; } @@ -1086,10 +1087,7 @@ export class BuidlerNode extends EventEmitter { case Type.BLOCK_SUBSCRIPTION: const hash = block.hash(); if (filter.subscription) { - this.emit("ethEvent", { - result: getRpcBlock(block, td, false), - subscription: filter.id - }); + this._emitEthEvent(filter.id, getRpcBlock(block, td, false)); return; } @@ -1110,10 +1108,7 @@ export class BuidlerNode extends EventEmitter { if (filter.subscription) { logs.forEach(rpcLog => { - this.emit("ethEvent", { - result: rpcLog, - subscription: filter.id - }); + this._emitEthEvent(filter.id, rpcLog); }); return; } @@ -1431,9 +1426,20 @@ export class BuidlerNode extends EventEmitter { return dt; } - private _rpcID(): string { - this._lastFilterId += 1; + private _getNextFilterId(): BN { + this._lastFilterId = this._lastFilterId.addn(1); - return `0x${this._lastFilterId.toString(16)}`; + return this._lastFilterId; + } + + private _filterIdToFiltersKey(filterId: BN): string { + return filterId.toString(); + } + + private _emitEthEvent(filterId: BN, result: any) { + this.emit("ethEvent", { + result, + filterId + }); } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 4399d6b297..2a93acd2e2 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -1,6 +1,7 @@ import chalk from "chalk"; import debug from "debug"; import Common from "ethereumjs-common"; +import { BN } from "ethereumjs-util"; import { EventEmitter } from "events"; import fsExtra from "fs-extra"; import path from "path"; @@ -175,8 +176,7 @@ export class BuidlerEVMProvider extends EventEmitter this._throwOnTransactionFailures, this._throwOnCallFailures, this._genesisAccounts, - stackTracesOptions, - this._loggingEnabled + stackTracesOptions ); this._common = common; @@ -188,8 +188,11 @@ export class BuidlerEVMProvider extends EventEmitter this._evmModule = new EvmModule(node); this._buidlerModule = new BuidlerModule(node); - const listener = (payload: { subscription: string; result: any }) => { - this.emit("notifications", payload); + const listener = (payload: { filterId: BN; result: any }) => { + this.emit("notifications", { + subscription: `0x${payload.filterId.toString(16)}`, + result: payload.result + }); }; // Handle eth_subscribe events and proxy them to handler diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts b/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts index c0f1ca1033..c50a8bc968 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/utils.ts @@ -1,9 +1,3 @@ -import { BN } from "ethereumjs-util"; - export function getCurrentTimestamp(): number { return Math.ceil(new Date().getTime() / 1000); } - -export function BNtoHex(bn: BN): string { - return `0x${bn.toString(16)}`; -} From 30f708b6875c624099bff3774fa15e5b5b85f817 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Wed, 19 Feb 2020 20:52:56 -0300 Subject: [PATCH 28/99] Unrelated lint fix --- .../buidler.config.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/buidler-waffle/test/buidler-project-custom-accounts/buidler.config.js b/packages/buidler-waffle/test/buidler-project-custom-accounts/buidler.config.js index 638c8d750f..978ccee1e2 100644 --- a/packages/buidler-waffle/test/buidler-project-custom-accounts/buidler.config.js +++ b/packages/buidler-waffle/test/buidler-project-custom-accounts/buidler.config.js @@ -5,8 +5,16 @@ module.exports = { networks: { buidlerevm: { accounts: [ - {privateKey: "0x07711bb9fb8508a36bf0307c579a8974d1a3230badb8757b6e22d203190ea800", balance: "123"}, - {privateKey: "0x07711bb9fb8508a36bf0307c579a8974d1a3230badb8757b6e22d203190ea803", balance: "123"} + { + privateKey: + "0x07711bb9fb8508a36bf0307c579a8974d1a3230badb8757b6e22d203190ea800", + balance: "123" + }, + { + privateKey: + "0x07711bb9fb8508a36bf0307c579a8974d1a3230badb8757b6e22d203190ea803", + balance: "123" + } ] } } From 6353b879c105b82114e27ee69f3378e6287087b3 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Wed, 19 Feb 2020 22:35:33 -0300 Subject: [PATCH 29/99] Decouple vm trace decoding from solidity tracer --- .../src/internal/buidler-evm/provider/node.ts | 87 +++++++++++-------- .../internal/buidler-evm/provider/provider.ts | 23 ++--- .../stack-traces/solidityTracer.ts | 20 ----- .../stack-traces/vm-trace-decoder.ts | 26 ++++++ .../internal/buidler-evm/stack-traces/test.ts | 6 +- 5 files changed, 93 insertions(+), 69 deletions(-) create mode 100644 packages/buidler-core/src/internal/buidler-evm/stack-traces/vm-trace-decoder.ts diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 11e70c4704..ac9f654085 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -39,6 +39,7 @@ import { decodeRevertReason } from "../stack-traces/revert-reasons"; import { encodeSolidityStackTrace } from "../stack-traces/solidity-errors"; import { SolidityStackTrace } from "../stack-traces/solidity-stack-trace"; import { SolidityTracer } from "../stack-traces/solidityTracer"; +import { VmTraceDecoder } from "../stack-traces/vm-trace-decoder"; import { VMTracer } from "../stack-traces/vm-tracer"; import { Blockchain } from "./blockchain"; @@ -144,7 +145,9 @@ export class BuidlerNode extends EventEmitter { throwOnTransactionFailures: boolean, throwOnCallFailures: boolean, genesisAccounts: GenesisAccount[] = [], - stackTracesOptions?: SolidityTracerOptions + solidityVersion?: string, + compilerInput?: CompilerInput, + compilerOutput?: CompilerOutput ): Promise<[Common, BuidlerNode]> { const stateTrie = new Trie(); const putIntoStateTrie = promisify(stateTrie.put.bind(stateTrie)); @@ -223,7 +226,9 @@ export class BuidlerNode extends EventEmitter { genesisBlock, throwOnTransactionFailures, throwOnCallFailures, - stackTracesOptions + solidityVersion, + compilerInput, + compilerOutput ); return [common, node]; @@ -248,6 +253,7 @@ export class BuidlerNode extends EventEmitter { private readonly _stackTracesEnabled: boolean = false; private readonly _vmTracer: VMTracer; + private readonly _vmTraceDecoder?: VmTraceDecoder; private readonly _solidityTracer?: SolidityTracer; private readonly _consoleLogger: ConsoleLogger = new ConsoleLogger(); private _failedStackTraces = 0; @@ -263,7 +269,9 @@ export class BuidlerNode extends EventEmitter { genesisBlock: Block, private readonly _throwOnTransactionFailures: boolean, private readonly _throwOnCallFailures: boolean, - stackTracesOptions?: SolidityTracerOptions + solidityVersion?: string, + compilerInput?: CompilerInput, + compilerOutput?: CompilerOutput ) { super(); const config = getUserConfigPath(); @@ -287,37 +295,40 @@ export class BuidlerNode extends EventEmitter { this._vmTracer = new VMTracer(this._vm, true); this._vmTracer.enableTracing(); - if (stackTracesOptions !== undefined) { - this._stackTracesEnabled = true; - - try { - const bytecodes = createModelsAndDecodeBytecodes( - stackTracesOptions.solidityVersion, - stackTracesOptions.compilerInput, - stackTracesOptions.compilerOutput - ); + if ( + solidityVersion === undefined || + compilerInput === undefined || + compilerOutput === undefined + ) { + return; + } - const contractsIdentifier = new ContractsIdentifier(); + try { + const bytecodes = createModelsAndDecodeBytecodes( + solidityVersion, + compilerInput, + compilerOutput + ); - for (const bytecode of bytecodes) { - contractsIdentifier.addBytecode(bytecode); - } + const contractsIdentifier = new ContractsIdentifier(); - this._solidityTracer = new SolidityTracer(contractsIdentifier); - } catch (error) { - console.warn( - chalk.yellow( - "Stack traces engine could not be initialized. Run Buidler with --verbose to learn more." - ) - ); + for (const bytecode of bytecodes) { + contractsIdentifier.addBytecode(bytecode); + } - this._stackTracesEnabled = false; + this._vmTraceDecoder = new VmTraceDecoder(contractsIdentifier); + this._solidityTracer = new SolidityTracer(); + } catch (error) { + console.warn( + chalk.yellow( + "The Buidler EVM tracing engine could not be initialized. Run Buidler with --verbose to learn more." + ) + ); - log( - "Solidity stack traces disabled: SolidityTracer failed to be initialized. Please report this to help us improve Buidler.\n", - error - ); - } + log( + "Buidler EVM tracing disabled: ContractsIdentifier failed to be initialized. Please report this to help us improve Buidler.\n", + error + ); } } @@ -373,10 +384,14 @@ export class BuidlerNode extends EventEmitter { await this._saveBlockAsSuccessfullyRun(block, result); await this._saveTransactionAsSuccessfullyRun(tx, block); - const vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); + let vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); const vmTracerError = this._vmTracer.getLastError(); this._vmTracer.clearLastError(); + if (this._vmTraceDecoder !== undefined) { + vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); + } + await this._printLogs(vmTrace, vmTracerError); if (this._throwOnTransactionFailures) { @@ -441,10 +456,14 @@ export class BuidlerNode extends EventEmitter { const result = await this._runTxAndRevertMutations(tx, false); - const vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); + let vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); const vmTracerError = this._vmTracer.getLastError(); this._vmTracer.clearLastError(); + if (this._vmTraceDecoder !== undefined) { + vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); + } + await this._printLogs(vmTrace, vmTracerError); if (this._throwOnCallFailures) { @@ -914,11 +933,7 @@ export class BuidlerNode extends EventEmitter { throw vmTracerError; } - const decodedTrace = this._solidityTracer!.tryToDecodeMessageTrace( - vmTrace - ); - - stackTrace = this._solidityTracer!.getStackTrace(decodedTrace); + stackTrace = this._solidityTracer!.getStackTrace(vmTrace); } catch (error) { this._failedStackTraces += 1; log( diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 2a93acd2e2..baaec15ebe 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -10,6 +10,7 @@ import semver from "semver"; import { EthereumProvider, ProjectPaths } from "../../../types"; import { SOLC_INPUT_FILENAME, SOLC_OUTPUT_FILENAME } from "../../constants"; import { getUserConfigPath } from "../../core/project-structure"; +import { CompilerInput, CompilerOutput } from "../stack-traces/compiler-types"; import { FIRST_SOLC_VERSION_SUPPORTED } from "../stack-traces/solidityTracer"; import { Mutex } from "../vendor/await-semaphore"; @@ -114,7 +115,8 @@ export class BuidlerEVMProvider extends EventEmitter return; } - let stackTracesOptions: SolidityTracerOptions | undefined; + let compilerInput: CompilerInput | undefined; + let compilerOutput: CompilerOutput | undefined; if (this._solcVersion !== undefined && this._paths !== undefined) { if (semver.lt(this._solcVersion, FIRST_SOLC_VERSION_SUPPORTED)) { @@ -142,15 +144,12 @@ export class BuidlerEVMProvider extends EventEmitter SOLC_OUTPUT_FILENAME ); - stackTracesOptions = { - solidityVersion: this._solcVersion, - compilerInput: await fsExtra.readJSON(solcInputPath, { - encoding: "utf8" - }), - compilerOutput: await fsExtra.readJSON(solcOutputPath, { - encoding: "utf8" - }) - }; + compilerInput = await fsExtra.readJSON(solcInputPath, { + encoding: "utf8" + }); + compilerOutput = await fsExtra.readJSON(solcOutputPath, { + encoding: "utf8" + }); } catch (error) { console.warn( chalk.yellow( @@ -176,7 +175,9 @@ export class BuidlerEVMProvider extends EventEmitter this._throwOnTransactionFailures, this._throwOnCallFailures, this._genesisAccounts, - stackTracesOptions + this._solcVersion, + compilerInput, + compilerOutput ); this._common = common; diff --git a/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidityTracer.ts b/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidityTracer.ts index 7c33bccda8..3633328721 100644 --- a/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidityTracer.ts +++ b/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidityTracer.ts @@ -47,26 +47,6 @@ export const FIRST_SOLC_VERSION_SUPPORTED = "0.5.1"; const FIRST_SOLC_VERSION_CREATE_PARAMS_VALIDATION = "0.5.9"; export class SolidityTracer { - constructor(private readonly _contractsIdentifier: ContractsIdentifier) { - const config = getUserConfigPath(); - } - - public tryToDecodeMessageTrace(messageTrace: MessageTrace): MessageTrace { - if (isPrecompileTrace(messageTrace)) { - return messageTrace; - } - - return { - ...messageTrace, - bytecode: this._contractsIdentifier.getBytecodeFromMessageTrace( - messageTrace - ), - steps: messageTrace.steps.map(s => - isEvmStep(s) ? s : this.tryToDecodeMessageTrace(s) - ) - }; - } - public getStackTrace( maybeDecodedMessageTrace: MessageTrace ): SolidityStackTrace { diff --git a/packages/buidler-core/src/internal/buidler-evm/stack-traces/vm-trace-decoder.ts b/packages/buidler-core/src/internal/buidler-evm/stack-traces/vm-trace-decoder.ts new file mode 100644 index 0000000000..24a68c21bd --- /dev/null +++ b/packages/buidler-core/src/internal/buidler-evm/stack-traces/vm-trace-decoder.ts @@ -0,0 +1,26 @@ +import { getUserConfigPath } from "../../core/project-structure"; + +import { ContractsIdentifier } from "./contracts-identifier"; +import { isEvmStep, isPrecompileTrace, MessageTrace } from "./message-trace"; + +export class VmTraceDecoder { + constructor(private readonly _contractsIdentifier: ContractsIdentifier) { + const config = getUserConfigPath(); + } + + public tryToDecodeMessageTrace(messageTrace: MessageTrace): MessageTrace { + if (isPrecompileTrace(messageTrace)) { + return messageTrace; + } + + return { + ...messageTrace, + bytecode: this._contractsIdentifier.getBytecodeFromMessageTrace( + messageTrace + ), + steps: messageTrace.steps.map(s => + isEvmStep(s) ? s : this.tryToDecodeMessageTrace(s) + ) + }; + } +} diff --git a/packages/buidler-core/test/internal/buidler-evm/stack-traces/test.ts b/packages/buidler-core/test/internal/buidler-evm/stack-traces/test.ts index 231e204f94..5a3e64b83c 100644 --- a/packages/buidler-core/test/internal/buidler-evm/stack-traces/test.ts +++ b/packages/buidler-core/test/internal/buidler-evm/stack-traces/test.ts @@ -32,6 +32,7 @@ import { StackTraceEntryType } from "../../../../src/internal/buidler-evm/stack-traces/solidity-stack-trace"; import { SolidityTracer } from "../../../../src/internal/buidler-evm/stack-traces/solidityTracer"; +import { VmTraceDecoder } from "../../../../src/internal/buidler-evm/stack-traces/vm-trace-decoder"; import { setCWD } from "../helpers/cwd"; import { compile, getSolidityVersion } from "./compilation"; @@ -360,7 +361,8 @@ async function runTest( contractsIdentifier.addBytecode(bytecode); } - const tracer = new SolidityTracer(contractsIdentifier); + const vmTraceDecoder = new VmTraceDecoder(contractsIdentifier); + const tracer = new SolidityTracer(); const logger = new ConsoleLogger(); const vm = await instantiateVm(); @@ -411,7 +413,7 @@ async function runTest( compareConsoleLogs(logger.getExecutionLogs(trace), tx.consoleLogs); - const decodedTrace = tracer.tryToDecodeMessageTrace(trace); + const decodedTrace = vmTraceDecoder.tryToDecodeMessageTrace(trace); try { if (tx.stackTrace === undefined) { From e7202d983c0d94e9b6ed5eb2adc13b6cc6072040 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 12:31:44 -0300 Subject: [PATCH 30/99] Implement logging --- .../buidler-core/src/builtin-tasks/jsonrpc.ts | 50 +++- .../internal/buidler-evm/jsonrpc/server.ts | 34 +-- .../buidler-evm/provider/modules/eth.ts | 256 +++++++++++++++++- .../buidler-evm/provider/modules/logger.ts | 15 + .../src/internal/buidler-evm/provider/node.ts | 217 ++++++++------- .../internal/buidler-evm/provider/provider.ts | 110 +++++++- .../buidler-evm/stack-traces/consoleLogger.ts | 22 +- .../stack-traces/solidity-errors.ts | 15 +- .../internal/core/providers/construction.ts | 3 +- packages/buidler-core/src/types.ts | 1 + .../buidler-evm/helpers/useProvider.ts | 7 +- 11 files changed, 566 insertions(+), 164 deletions(-) create mode 100644 packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts index 3485fb3883..2267dc1a5e 100644 --- a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts +++ b/packages/buidler-core/src/builtin-tasks/jsonrpc.ts @@ -1,4 +1,6 @@ +import chalk from "chalk"; import debug from "debug"; +import { BN, bufferToHex, privateToAddress, toBuffer } from "ethereumjs-util"; import { JsonRpcServer, @@ -10,7 +12,11 @@ import { BuidlerError } from "../internal/core/errors"; import { ERRORS } from "../internal/core/errors-list"; import { createProvider } from "../internal/core/providers/construction"; import { lazyObject } from "../internal/util/lazy"; -import { EthereumProvider, ResolvedBuidlerConfig } from "../types"; +import { + BuidlerNetworkConfig, + EthereumProvider, + ResolvedBuidlerConfig +} from "../types"; import { TASK_JSONRPC } from "./task-names"; @@ -22,19 +28,40 @@ function _createBuidlerEVMProvider( log("Creating BuidlerEVM Provider"); const networkName = BUIDLEREVM_NETWORK_NAME; - const networkConfig = config.networks[networkName]; + const networkConfig = config.networks[networkName] as BuidlerNetworkConfig; return lazyObject(() => { log(`Creating buidlerevm provider for JSON-RPC sever`); return createProvider( networkName, - networkConfig, + { loggingEnabled: true, ...networkConfig }, config.solc.version, config.paths ); }); } +function logBuidlerEvmAccounts(networkConfig: BuidlerNetworkConfig) { + if (networkConfig.accounts === undefined) { + return; + } + + console.log("Accounts"); + console.log("========"); + + for (const [index, account] of networkConfig.accounts.entries()) { + const address = bufferToHex(privateToAddress(toBuffer(account.privateKey))); + const privateKey = bufferToHex(toBuffer(account.privateKey)); + const balance = new BN(account.balance) + .div(new BN(10).pow(new BN(18))) + .toString(10); + + console.log(`Account #${index}: ${address} (${balance} ETH) +Private Key: ${privateKey} +`); + } +} + export default function() { task(TASK_JSONRPC, "Starts a buidler JSON-RPC server") .addOptionalParam( @@ -69,7 +96,22 @@ export default function() { const server = new JsonRpcServer(serverConfig); - process.exitCode = await server.listen(); + const { port: actualPort, address } = await server.listen(); + + console.log( + chalk.green( + `Started HTTP and WebSocket JSON-RPC server at ${address}:${actualPort}/` + ) + ); + + console.log(); + + const networkConfig = config.networks[ + BUIDLEREVM_NETWORK_NAME + ] as BuidlerNetworkConfig; + logBuidlerEvmAccounts(networkConfig); + + await server.waitUntilClosed(); } catch (error) { if (BuidlerError.isBuidlerError(error)) { throw error; diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 4929c99992..20c75cece9 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -41,34 +41,30 @@ export class JsonRpcServer { return new HttpProvider(`http://${address}:${port}/`, name); }; - public listen = (): Promise => { - return new Promise(async resolve => { - process.once("SIGINT", async () => { - await this.close(); - - resolve(0); - }); - - await this.start(); + public listen = (): Promise<{ address: string; port: number }> => { + process.once("SIGINT", async () => { + await this.close(); }); - }; - public start = async (logOnStarted = true) => { return new Promise(resolve => { log(`Starting JSON-RPC server on port ${this._config.port}`); this._httpServer.listen(this._config.port, this._config.hostname, () => { // We get the address and port directly from the server in order to handle random port allocation with `0`. - const { address, port } = this._httpServer.address(); + resolve(this._httpServer.address()); + }); + }); + }; - if (logOnStarted) { - console.log( - `Started HTTP and WebSocket JSON-RPC server at ${address}:${port}/` - ); - } + public waitUntilClosed = async () => { + const httpServerClosed = new Promise(resolve => { + this._httpServer.once("close", resolve); + }); - resolve(); - }); + const wsServerClosed = new Promise(resolve => { + this._wsServer.once("close", resolve); }); + + return Promise.all([httpServerClosed, wsServerClosed]); }; public close = async () => { diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index 740fb659da..9a5a88cf12 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -1,3 +1,4 @@ +import { RunBlockResult } from "@nomiclabs/ethereumjs-vm/dist/runBlock"; import Common from "ethereumjs-common"; import { Transaction } from "ethereumjs-tx"; import { @@ -8,7 +9,19 @@ import { zeroAddress } from "ethereumjs-util"; import * as t from "io-ts"; +import util from "util"; +import { + isCreateTrace, + isPrecompileTrace, + MessageTrace +} from "../../stack-traces/message-trace"; +import { ContractFunctionType } from "../../stack-traces/model"; +import { + FALLBACK_FUNCTION_NAME, + UNRECOGNIZED_CONTRACT_NAME, + UNRECOGNIZED_FUNCTION_NAME +} from "../../stack-traces/solidity-stack-trace"; import { InvalidArgumentsError, InvalidInputError, @@ -57,12 +70,16 @@ import { RpcTransactionReceiptOutput } from "../output"; -// tslint:disable only-buidler-error +import { ModulesLogger } from "./logger"; +// tslint:disable only-buidler-error export class EthModule { constructor( private readonly _common: Common, - private readonly _node: BuidlerNode + private readonly _node: BuidlerNode, + private readonly _throwOnTransactionFailures: boolean, + private readonly _throwOnCallFailures: boolean, + private readonly _logger?: ModulesLogger ) {} public async processRequest( @@ -291,7 +308,27 @@ export class EthModule { this._validateBlockTag(blockTag); const callParams = await this._rpcCallRequestToNodeCallParams(rpcCall); - const returnData = await this._node.runCall(callParams); + const { + result: returnData, + trace, + error, + consoleLogMessages + } = await this._node.runCall(callParams); + + this._logCallTrace(callParams, trace); + + this._logConsoleLogMessages(consoleLogMessages); + + if (error !== undefined) { + if (this._throwOnCallFailures) { + throw error; + } + + // TODO: This is a little duplicated with the provider, it should be + // refactored away + // TODO: This will log the error, but the RPC method won't be red + this._logError(error); + } return bufferToRpcData(returnData); } @@ -340,7 +377,24 @@ export class EthModule { transactionRequest ); - return numberToRpcQuantity(await this._node.estimateGas(txParams)); + const { + estimation, + error, + trace, + consoleLogMessages + } = await this._node.estimateGas(txParams); + + if (error !== undefined) { + this._logContractAndFunctionName(trace); + this._logFrom(txParams.from); + this._logValue(new BN(txParams.value)); + + this._logConsoleLogMessages(consoleLogMessages); + + throw error; + } + + return numberToRpcQuantity(estimation); } // eth_gasPrice @@ -795,9 +849,7 @@ export class EthModule { throw error; } - await this._node.runTransactionInNewBlock(tx); - - return bufferToRpcData(tx.hash(true)); + return this._sendTransactionAndReturnHash(tx); } // eth_sendTransaction @@ -814,9 +866,8 @@ export class EthModule { ); const tx = await this._node.getSignedTransaction(txParams); - await this._node.runTransactionInNewBlock(tx); - return bufferToRpcData(tx.hash(true)); + return this._sendTransactionAndReturnHash(tx); } // eth_sign @@ -1035,4 +1086,191 @@ export class EthModule { return toBuffer(localAccounts[0]); } + + private _logTransactionTrace( + tx: Transaction, + trace: MessageTrace, + block: Block, + blockResult: RunBlockResult + ) { + if (this._logger === undefined) { + return; + } + + this._logContractAndFunctionName(trace); + this._logger.log(`Transaction: ${bufferToHex(tx.hash(true))}`); + this._logFrom(tx.getSenderAddress()); + this._logValue(new BN(tx.value)); + this._logger.log( + `Gas used: ${new BN(blockResult.receipts[0].gasUsed).toString( + 10 + )} of ${new BN(tx.gasLimit).toString(10)}` + ); + this._logger.log( + `Block: #${new BN(block.header.number).toString( + 10 + )} - Hash: ${bufferToHex(block.hash())}` + ); + } + + private _logConsoleLogMessages(messages: string[]) { + // This is a especial case, as we always want to print the console.log + // messages. The difference is how. + // If we have a logger, we should use that, so that logs are printed in + // order. If we don't, we just print the messages here. + if (this._logger === undefined) { + for (const msg of messages) { + console.log(msg); + } + return; + } + + if (messages.length === 0) { + return; + } + + this._logger.log(""); + + this._logger.log("console.log:"); + for (const msg of messages) { + this._logger.log(` ${msg}`); + } + } + + private _logCallTrace(callParams: CallParams, trace: MessageTrace) { + if (this._logger === undefined) { + return; + } + + this._logContractAndFunctionName(trace); + this._logFrom(callParams.from); + if (callParams.value.gtn(0)) { + this._logValue(callParams.value); + } + } + + private _logContractAndFunctionName(trace: MessageTrace) { + if (this._logger === undefined) { + return; + } + + if (isPrecompileTrace(trace)) { + this._logger.log( + `Precompile call: ` + ); + return; + } + + if (isCreateTrace(trace)) { + if (trace.bytecode === undefined) { + this._logger.log(`Contract deployment: ${UNRECOGNIZED_CONTRACT_NAME}`); + } else { + this._logger.log( + `Contract deployment: ${trace.bytecode.contract.name}` + ); + } + + if (trace.deployedContract !== undefined) { + this._logger.log( + `Contract address: ${bufferToHex(trace.deployedContract)}` + ); + } + + return; + } + + if (trace.bytecode === undefined) { + this._logger.log(`Contract call: ${UNRECOGNIZED_CONTRACT_NAME}`); + return; + } + + const func = trace.bytecode.contract.getFunctionFromSelector( + trace.calldata.slice(0, 4) + ); + + const functionName: string = + func === undefined + ? UNRECOGNIZED_FUNCTION_NAME + : func.type === ContractFunctionType.FALLBACK + ? FALLBACK_FUNCTION_NAME + : func.name; + + this._logger.log( + `Contract call: ${trace.bytecode.contract.name}#${functionName}` + ); + } + + private _logValue(value: BN) { + if (this._logger === undefined) { + return; + } + // eth = 1e18 + // gwei = 1e9 + // 0.0001 eth = 1e14 = 1e5gwei + // 0.0001 gwei = 1e5 + + if (value.eqn(0)) { + this._logger.log(`Value: 0 ETH`); + return; + } + + if (value.lt(new BN(10).pow(new BN(5)))) { + this._logger.log(`Value: ${value} wei`); + return; + } + + if (value.lt(new BN(10).pow(new BN(14)))) { + this._logger.log(`Value: ${value.sub(new BN(10).pow(new BN(9)))} gwei`); + return; + } + + this._logger.log(`Value: ${value.sub(new BN(10).pow(new BN(18)))} ETH`); + } + + private _logError(error: Error) { + if (this._logger === undefined) { + return; + } + + // TODO: We log an empty line here because this is only used when throwing + // errors is disabled. The empty line is normally printed by the provider + // when an exception is thrown. As we don't throw, we do it here. + this._logger.log(""); + this._logger.log(util.inspect(error)); + } + + private _logFrom(from: Buffer) { + if (this._logger === undefined) { + return; + } + + this._logger.log(`From: ${bufferToHex(from)}`); + } + + private async _sendTransactionAndReturnHash(tx: Transaction) { + const { + trace, + block, + blockResult, + consoleLogMessages, + error + } = await this._node.runTransactionInNewBlock(tx); + + this._logTransactionTrace(tx, trace, block, blockResult); + + this._logConsoleLogMessages(consoleLogMessages); + + if (error !== undefined) { + if (this._throwOnTransactionFailures) { + throw error; + } + + // TODO: This is a little duplicated with the provider, it should be + // refactored away + // TODO: This will log the error, but the RPC method won't be red + this._logError(error); + } + + return bufferToRpcData(tx.hash(true)); + } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts new file mode 100644 index 0000000000..76482d667c --- /dev/null +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts @@ -0,0 +1,15 @@ +export class ModulesLogger { + private _logs: string[] = []; + + public log(message: string) { + this._logs.push(message); + } + + public clearLogs() { + this._logs = []; + } + + public getLogs(): string[] { + return [...this._logs]; + } +} diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index ac9f654085..1aae3529b5 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -36,7 +36,10 @@ import { ConsoleLogger } from "../stack-traces/consoleLogger"; import { ContractsIdentifier } from "../stack-traces/contracts-identifier"; import { MessageTrace } from "../stack-traces/message-trace"; import { decodeRevertReason } from "../stack-traces/revert-reasons"; -import { encodeSolidityStackTrace } from "../stack-traces/solidity-errors"; +import { + encodeSolidityStackTrace, + SolidityError +} from "../stack-traces/solidity-errors"; import { SolidityStackTrace } from "../stack-traces/solidity-stack-trace"; import { SolidityTracer } from "../stack-traces/solidityTracer"; import { VmTraceDecoder } from "../stack-traces/vm-trace-decoder"; @@ -142,8 +145,6 @@ export class BuidlerNode extends EventEmitter { chainId: number, networkId: number, blockGasLimit: number, - throwOnTransactionFailures: boolean, - throwOnCallFailures: boolean, genesisAccounts: GenesisAccount[] = [], solidityVersion?: string, compilerInput?: CompilerInput, @@ -224,8 +225,6 @@ export class BuidlerNode extends EventEmitter { genesisAccounts.map(acc => toBuffer(acc.privateKey)), new BN(blockGasLimit), genesisBlock, - throwOnTransactionFailures, - throwOnCallFailures, solidityVersion, compilerInput, compilerOutput @@ -251,7 +250,6 @@ export class BuidlerNode extends EventEmitter { private _nextSnapshotId = 1; // We start in 1 to mimic Ganache private readonly _snapshots: Snapshot[] = []; - private readonly _stackTracesEnabled: boolean = false; private readonly _vmTracer: VMTracer; private readonly _vmTraceDecoder?: VmTraceDecoder; private readonly _solidityTracer?: SolidityTracer; @@ -267,8 +265,6 @@ export class BuidlerNode extends EventEmitter { localAccounts: Buffer[], private readonly _blockGasLimit: BN, genesisBlock: Block, - private readonly _throwOnTransactionFailures: boolean, - private readonly _throwOnCallFailures: boolean, solidityVersion?: string, compilerInput?: CompilerInput, compilerOutput?: CompilerOutput @@ -351,7 +347,13 @@ export class BuidlerNode extends EventEmitter { public async runTransactionInNewBlock( tx: Transaction - ): Promise { + ): Promise<{ + trace: MessageTrace; + block: Block; + blockResult: RunBlockResult; + error?: Error; + consoleLogMessages: string[]; + }> { await this._validateTransaction(tx); await this._saveTransactionAsReceived(tx); @@ -366,16 +368,11 @@ export class BuidlerNode extends EventEmitter { await this._addTransactionToBlock(block, tx); - let result: RunBlockResult; - try { - result = await this._vm.runBlock({ - block, - generate: true, - skipBlockValidation: true - }); - } catch (error) { - throw new TransactionExecutionError(error); - } + const result = await this._vm.runBlock({ + block, + generate: true, + skipBlockValidation: true + }); if (needsTimestampIncrease) { await this.increaseTime(new BN(1)); @@ -392,21 +389,24 @@ export class BuidlerNode extends EventEmitter { vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); } - await this._printLogs(vmTrace, vmTracerError); - - if (this._throwOnTransactionFailures) { - const error = await this._manageErrors( - result.results[0].execResult, - vmTrace, - vmTracerError - ); + const consoleLogMessages = await this._getConsoleLogMessages( + vmTrace, + vmTracerError + ); - if (error !== undefined) { - throw error; - } - } + const error = await this._manageErrors( + result.results[0].execResult, + vmTrace, + vmTracerError + ); - return result; + return { + trace: vmTrace, + block, + blockResult: result, + error, + consoleLogMessages + }; } public async mineEmptyBlock() { @@ -448,13 +448,20 @@ export class BuidlerNode extends EventEmitter { } } - public async runCall(call: CallParams): Promise { + public async runCall( + call: CallParams + ): Promise<{ + result: Buffer; + trace: MessageTrace; + error?: Error; + consoleLogMessages: string[]; + }> { const tx = await this._getFakeTransaction({ ...call, nonce: await this.getAccountNonce(call.from) }); - const result = await this._runTxAndRevertMutations(tx, false); + const result = await this._runTxAndRevertMutations(tx); let vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); const vmTracerError = this._vmTracer.getLastError(); @@ -464,29 +471,23 @@ export class BuidlerNode extends EventEmitter { vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); } - await this._printLogs(vmTrace, vmTracerError); - - if (this._throwOnCallFailures) { - const error = await this._manageErrors( - result.execResult, - vmTrace, - vmTracerError - ); - - if (error !== undefined) { - throw error; - } - } + const consoleLogMessages = await this._getConsoleLogMessages( + vmTrace, + vmTracerError + ); - if ( - result.execResult.exceptionError === undefined || - result.execResult.exceptionError.error === ERROR.REVERT - ) { - return result.execResult.returnValue; - } + const error = await this._manageErrors( + result.execResult, + vmTrace, + vmTracerError + ); - // If we got here we found another kind of error and we throw anyway - throw await this._manageErrors(result.execResult, vmTrace, vmTracerError)!; + return { + result: result.execResult.returnValue, + trace: vmTrace, + error, + consoleLogMessages + }; } public async getAccountBalance(address: Buffer): Promise { @@ -511,23 +512,59 @@ export class BuidlerNode extends EventEmitter { return this._blockGasLimit; } - public async estimateGas(txParams: TransactionParams): Promise { + public async estimateGas( + txParams: TransactionParams + ): Promise<{ + estimation: BN; + trace: MessageTrace; + error?: Error; + consoleLogMessages: string[]; + }> { const tx = await this._getFakeTransaction({ ...txParams, gasLimit: await this.getBlockGasLimit() }); - const result = await this._runTxAndRevertMutations(tx, true); + const result = await this._runTxAndRevertMutations(tx); + + let vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); + const vmTracerError = this._vmTracer.getLastError(); + this._vmTracer.clearLastError(); + + if (this._vmTraceDecoder !== undefined) { + vmTrace = this._vmTraceDecoder.tryToDecodeMessageTrace(vmTrace); + } + + const consoleLogMessages = await this._getConsoleLogMessages( + vmTrace, + vmTracerError + ); // This is only considered if the call to _runTxAndRevertMutations doesn't // manage errors if (result.execResult.exceptionError !== undefined) { - return this.getBlockGasLimit(); + return { + estimation: await this.getBlockGasLimit(), + trace: vmTrace, + error: await this._manageErrors( + result.execResult, + vmTrace, + vmTracerError + ), + consoleLogMessages + }; } const initialEstimation = result.gasUsed; - return this._correctInitialEstimation(txParams, initialEstimation); + return { + estimation: await this._correctInitialEstimation( + txParams, + initialEstimation + ), + trace: vmTrace, + consoleLogMessages + }; } public async getGasPrice(): Promise { @@ -787,8 +824,8 @@ export class BuidlerNode extends EventEmitter { } if ( - (filter!.subscription && !subscription) || - (!filter!.subscription && subscription) + (filter.subscription && !subscription) || + (!filter.subscription && subscription) ) { return false; } @@ -900,40 +937,40 @@ export class BuidlerNode extends EventEmitter { } } - private async _printLogs( + private async _getConsoleLogMessages( vmTrace: MessageTrace, vmTracerError: Error | undefined - ) { + ): Promise { if (vmTracerError !== undefined) { log( "Could not print console log. Please report this to help us improve Buidler.\n", vmTracerError ); - return; + return []; } - this._consoleLogger.printLogs(vmTrace); + return this._consoleLogger.getLogMessages(vmTrace); } private async _manageErrors( vmResult: ExecResult, vmTrace: MessageTrace, vmTracerError?: Error - ): Promise { + ): Promise { if (vmResult.exceptionError === undefined) { return undefined; } let stackTrace: SolidityStackTrace | undefined; - if (this._stackTracesEnabled) { + if (this._solidityTracer !== undefined) { try { if (vmTracerError !== undefined) { throw vmTracerError; } - stackTrace = this._solidityTracer!.getStackTrace(vmTrace); + stackTrace = this._solidityTracer.getStackTrace(vmTrace); } catch (error) { this._failedStackTraces += 1; log( @@ -1267,7 +1304,7 @@ export class BuidlerNode extends EventEmitter { }); } - const result = await this._runTxAndRevertMutations(tx, false); + const result = await this._runTxAndRevertMutations(tx); if (result.execResult.exceptionError === undefined) { return initialEstimation; @@ -1335,7 +1372,7 @@ export class BuidlerNode extends EventEmitter { gasLimit: newEstimation }); - const result = await this._runTxAndRevertMutations(tx, false); + const result = await this._runTxAndRevertMutations(tx); if (result.execResult.exceptionError === undefined) { return this._binarySearchEstimation( @@ -1362,10 +1399,7 @@ export class BuidlerNode extends EventEmitter { * failure. If it's false, the tx's RunTxResult is returned, and the vmTracer * inspected/resetted. */ - private async _runTxAndRevertMutations( - tx: Transaction, - throwOnError = true - ): Promise { + private async _runTxAndRevertMutations(tx: Transaction): Promise { const initialStateRoot = await this._stateManager.getStateRoot(); try { @@ -1380,35 +1414,12 @@ export class BuidlerNode extends EventEmitter { await this._addTransactionToBlock(block, tx); - let result: RunTxResult; - try { - result = await this._vm.runTx({ - block, - tx, - skipNonce: true, - skipBalance: true - }); - } catch (error) { - throw new TransactionExecutionError(error); - } - - if (throwOnError) { - const vmTrace = this._vmTracer.getLastTopLevelMessageTrace(); - const vmTracerError = this._vmTracer.getLastError(); - this._vmTracer.clearLastError(); - - const error = await this._manageErrors( - result.execResult, - vmTrace, - vmTracerError - ); - - if (error !== undefined) { - throw error; - } - } - - return result; + return await this._vm.runTx({ + block, + tx, + skipNonce: true, + skipBalance: true + }); } finally { await this._stateManager.setStateRoot(initialStateRoot); } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index baaec15ebe..08c02212cc 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -1,4 +1,4 @@ -import chalk from "chalk"; +import chalk, { Chalk } from "chalk"; import debug from "debug"; import Common from "ethereumjs-common"; import { BN } from "ethereumjs-util"; @@ -6,24 +6,34 @@ import { EventEmitter } from "events"; import fsExtra from "fs-extra"; import path from "path"; import semver from "semver"; +import util from "util"; import { EthereumProvider, ProjectPaths } from "../../../types"; import { SOLC_INPUT_FILENAME, SOLC_OUTPUT_FILENAME } from "../../constants"; import { getUserConfigPath } from "../../core/project-structure"; import { CompilerInput, CompilerOutput } from "../stack-traces/compiler-types"; +import { SolidityError } from "../stack-traces/solidity-errors"; import { FIRST_SOLC_VERSION_SUPPORTED } from "../stack-traces/solidityTracer"; import { Mutex } from "../vendor/await-semaphore"; -import { MethodNotFoundError } from "./errors"; +import { + BuidlerEVMProviderError, + MethodNotFoundError, + MethodNotSupportedError +} from "./errors"; import { BuidlerModule } from "./modules/buidler"; import { EthModule } from "./modules/eth"; import { EvmModule } from "./modules/evm"; +import { ModulesLogger } from "./modules/logger"; import { NetModule } from "./modules/net"; import { Web3Module } from "./modules/web3"; import { BuidlerNode, GenesisAccount, SolidityTracerOptions } from "./node"; const log = debug("buidler:core:buidler-evm:provider"); +// Set of methods that are never logged +const PRIVATE_RPC_METHODS = new Set(["buidler_getStackTraceFailuresCount"]); + // tslint:disable only-buidler-error export class BuidlerEVMProvider extends EventEmitter @@ -36,6 +46,7 @@ export class BuidlerEVMProvider extends EventEmitter private _evmModule?: EvmModule; private _buidlerModule?: BuidlerModule; private readonly _mutex = new Mutex(); + private readonly _logger = new ModulesLogger(); constructor( private readonly _hardfork: string, @@ -58,7 +69,7 @@ export class BuidlerEVMProvider extends EventEmitter const release = await this._mutex.acquire(); try { - if (this._loggingEnabled) { + if (this._loggingEnabled && !PRIVATE_RPC_METHODS.has(method)) { return await this._sendWithLogging(method, params); } @@ -73,14 +84,55 @@ export class BuidlerEVMProvider extends EventEmitter params: any[] = [] ): Promise { try { - console.log(chalk.green(`JSON-RPC call: ${method}`)); + const result = await this._send(method, params); - return await this._send(method, params); + // TODO: If an eth_call, eth_sendTransaction, or eth_sendRawTransaction + // fails without throwing, this will be displayed in green. It's unclear + // if this is correct. See Eth module's TODOs for more info. + // We log after running the method, because we want to use different + // colors depending on whether it failed or not + this._log(method, false, chalk.green); + + const loggedSomething = this._logModuleMessages(); + if (loggedSomething) { + this._log(""); + } + + return result; } catch (err) { - console.error(chalk.red(err.message)); - console.error(err); + if ( + err instanceof MethodNotFoundError || + err instanceof MethodNotSupportedError + ) { + this._log(`${method} - Method not supported`, false, chalk.red); + + throw err; + } + + this._log(method, false, chalk.red); + + const loggedSomething = this._logModuleMessages(); + if (loggedSomething) { + this._log(""); + } + + if (err instanceof SolidityError) { + this._logError(err); + } else if (err instanceof BuidlerEVMProviderError) { + this._log(err.message, true); + } else { + this._logError(err, true); + this._log( + "If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug", + true + ); + } + + this._log(""); throw err; + } finally { + this._logger.clearLogs(); } } @@ -172,8 +224,6 @@ export class BuidlerEVMProvider extends EventEmitter this._chainId, this._networkId, this._blockGasLimit, - this._throwOnTransactionFailures, - this._throwOnCallFailures, this._genesisAccounts, this._solcVersion, compilerInput, @@ -183,7 +233,14 @@ export class BuidlerEVMProvider extends EventEmitter this._common = common; this._node = node; - this._ethModule = new EthModule(common, node); + this._ethModule = new EthModule( + common, + node, + this._throwOnTransactionFailures, + this._throwOnCallFailures, + this._loggingEnabled ? this._logger : undefined + ); + this._netModule = new NetModule(common); this._web3Module = new Web3Module(); this._evmModule = new EvmModule(node); @@ -199,4 +256,37 @@ export class BuidlerEVMProvider extends EventEmitter // Handle eth_subscribe events and proxy them to handler this._node.addListener("ethEvent", listener); } + + private _logModuleMessages(): boolean { + const logs = this._logger.getLogs(); + if (logs.length === 0) { + return false; + } + + for (const msg of logs) { + this._log(msg, true); + } + + return true; + } + + private _logError(err: Error, logInRed = false) { + this._log(util.inspect(err), true, logInRed ? chalk.red : undefined); + } + + private _log(msg: string, indent = false, color?: Chalk) { + if (indent) { + msg = msg + .split("\n") + .map(line => ` ${line}`) + .join("\n"); + } + + if (color !== undefined) { + console.log(color(msg)); + return; + } + + console.log(msg); + } } diff --git a/packages/buidler-core/src/internal/buidler-evm/stack-traces/consoleLogger.ts b/packages/buidler-core/src/internal/buidler-evm/stack-traces/consoleLogger.ts index 90957d1850..42fe986fad 100644 --- a/packages/buidler-core/src/internal/buidler-evm/stack-traces/consoleLogger.ts +++ b/packages/buidler-core/src/internal/buidler-evm/stack-traces/consoleLogger.ts @@ -1,4 +1,5 @@ import { bufferToHex, bufferToInt, fromSigned } from "ethereumjs-util"; +import util from "util"; import { AddressTy, @@ -69,20 +70,23 @@ export class ConsoleLogger { this._consoleLogs = ConsoleLogs; } - public printLogs(maybeDecodedMessageTrace: MessageTrace) { - if (isPrecompileTrace(maybeDecodedMessageTrace)) { - return; - } + public getLogMessages(maybeDecodedMessageTrace: MessageTrace): string[] { + return this.getExecutionLogs(maybeDecodedMessageTrace).map(log => { + if (log === undefined) { + return ""; + } - const logs = this.getExecutionLogs(maybeDecodedMessageTrace); - for (const log of logs) { - console.log(...log); - } + return util.format(log[0], ...log.slice(1)); + }); } public getExecutionLogs( - maybeDecodedMessageTrace: EvmMessageTrace + maybeDecodedMessageTrace: MessageTrace ): ConsoleLogs[] { + if (isPrecompileTrace(maybeDecodedMessageTrace)) { + return []; + } + const logs: ConsoleLogs[] = []; this._collectExecutionLogs(maybeDecodedMessageTrace, logs); return logs; diff --git a/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts b/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts index b5e5908cc2..09b9df1a52 100644 --- a/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts +++ b/packages/buidler-core/src/internal/buidler-evm/stack-traces/solidity-errors.ts @@ -239,7 +239,10 @@ function getMessageFromLastStackTraceEntry( } } -export class SolidityError extends TransactionExecutionError { +// Note: This error class MUST NOT extend BuidlerEVMProviderError, as libraries +// use the code property to detect if they are dealing with a JSON-RPC error, +// and take control of errors. +export class SolidityError extends Error { public readonly stackTrace: SolidityStackTrace; constructor(message: string, stackTrace: SolidityStackTrace) { @@ -247,12 +250,14 @@ export class SolidityError extends TransactionExecutionError { this.stackTrace = stackTrace; } - public [inspect.custom]() { - return this.stack; + public [inspect.custom](): string { + return this.inspect(); } - public inspect() { - return this.stack; + public inspect(): string { + return this.stack !== undefined + ? this.stack + : "Internal error when encoding SolidityError"; } } diff --git a/packages/buidler-core/src/internal/core/providers/construction.ts b/packages/buidler-core/src/internal/core/providers/construction.ts index 0929d36b0c..fae9ea36aa 100644 --- a/packages/buidler-core/src/internal/core/providers/construction.ts +++ b/packages/buidler-core/src/internal/core/providers/construction.ts @@ -43,7 +43,8 @@ export function createProvider( buidlerNetConfig.throwOnCallFailures!, buidlerNetConfig.accounts, solcVersion, - paths + paths, + buidlerNetConfig.loggingEnabled ); } else { const httpNetConfig = networkConfig as HttpNetworkConfig; diff --git a/packages/buidler-core/src/types.ts b/packages/buidler-core/src/types.ts index fe51caa4df..9a174e8097 100644 --- a/packages/buidler-core/src/types.ts +++ b/packages/buidler-core/src/types.ts @@ -26,6 +26,7 @@ export interface BuidlerNetworkConfig extends CommonNetworkConfig { hardfork?: string; throwOnTransactionFailures?: boolean; throwOnCallFailures?: boolean; + loggingEnabled?: boolean; } export interface HDAccountsConfig { diff --git a/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts b/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts index a5e6c1f28a..376e50861f 100644 --- a/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts +++ b/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts @@ -10,6 +10,7 @@ declare module "mocha" { interface Context { provider: EthereumProvider; common: Common; + server?: JsonRpcServer; } } @@ -73,8 +74,6 @@ export function useProvider( chainId, networkId, blockGasLimit, - true, - true, accounts ); @@ -97,7 +96,7 @@ export function useProvider( provider: this.provider }); - await this.server.start(false); + await this.server.listen(); this.provider = this.server.getProvider(); } @@ -107,7 +106,7 @@ export function useProvider( delete this.common; delete this.provider; - if (useJsonRpc) { + if (this.server !== undefined) { await this.server.close(); delete this.server; From d6c685b754c0e862038b95efa46d7a35583bee6d Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 12:38:18 -0300 Subject: [PATCH 31/99] Rename jsonrpc task to node --- .../buidler-core/src/builtin-tasks/{jsonrpc.ts => node.ts} | 6 +++--- packages/buidler-core/src/builtin-tasks/task-names.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename packages/buidler-core/src/builtin-tasks/{jsonrpc.ts => node.ts} (95%) diff --git a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts b/packages/buidler-core/src/builtin-tasks/node.ts similarity index 95% rename from packages/buidler-core/src/builtin-tasks/jsonrpc.ts rename to packages/buidler-core/src/builtin-tasks/node.ts index 2267dc1a5e..47f0ee15f1 100644 --- a/packages/buidler-core/src/builtin-tasks/jsonrpc.ts +++ b/packages/buidler-core/src/builtin-tasks/node.ts @@ -18,9 +18,9 @@ import { ResolvedBuidlerConfig } from "../types"; -import { TASK_JSONRPC } from "./task-names"; +import { TASK_NODE } from "./task-names"; -const log = debug("buidler:core:tasks:jsonrpc"); +const log = debug("buidler:core:tasks:node"); function _createBuidlerEVMProvider( config: ResolvedBuidlerConfig @@ -63,7 +63,7 @@ Private Key: ${privateKey} } export default function() { - task(TASK_JSONRPC, "Starts a buidler JSON-RPC server") + task(TASK_NODE, "Starts a buidler JSON-RPC server") .addOptionalParam( "hostname", "The host to which to bind to for new connections", diff --git a/packages/buidler-core/src/builtin-tasks/task-names.ts b/packages/buidler-core/src/builtin-tasks/task-names.ts index 2153c96ee4..d539014842 100644 --- a/packages/buidler-core/src/builtin-tasks/task-names.ts +++ b/packages/buidler-core/src/builtin-tasks/task-names.ts @@ -23,7 +23,7 @@ export const TASK_HELP = "help"; export const TASK_RUN = "run"; -export const TASK_JSONRPC = "jsonrpc"; +export const TASK_NODE = "node"; export const TASK_TEST = "test"; From 54c0bbb86c7721f04e4a10e77b5f9a715b49c69c Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 12:38:45 -0300 Subject: [PATCH 32/99] Improve decription of task node --- packages/buidler-core/src/builtin-tasks/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/src/builtin-tasks/node.ts b/packages/buidler-core/src/builtin-tasks/node.ts index 47f0ee15f1..61f688f5a2 100644 --- a/packages/buidler-core/src/builtin-tasks/node.ts +++ b/packages/buidler-core/src/builtin-tasks/node.ts @@ -63,7 +63,7 @@ Private Key: ${privateKey} } export default function() { - task(TASK_NODE, "Starts a buidler JSON-RPC server") + task(TASK_NODE, "Starts a Buidler EVM as a JSON-RPC server") .addOptionalParam( "hostname", "The host to which to bind to for new connections", From 194c14e30a3732ad084428a7e0f1c46e5588e005 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 13:13:38 -0300 Subject: [PATCH 33/99] Fix listener leak in json rpc server --- .../src/internal/buidler-evm/jsonrpc/server.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 20c75cece9..613fe34522 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -42,9 +42,7 @@ export class JsonRpcServer { }; public listen = (): Promise<{ address: string; port: number }> => { - process.once("SIGINT", async () => { - await this.close(); - }); + process.once("SIGINT", this._onSigint); return new Promise(resolve => { log(`Starting JSON-RPC server on port ${this._config.port}`); @@ -68,6 +66,8 @@ export class JsonRpcServer { }; public close = async () => { + process.removeListener("SIGINT", this._onSigint); + return Promise.all([ new Promise((resolve, reject) => { log("Closing JSON-RPC server"); @@ -97,4 +97,8 @@ export class JsonRpcServer { }) ]); }; + + private _onSigint = async () => { + await this.close(); + }; } From 8f128d2a31632663ffa1ccdc1d53f0967784ac2d Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 13:14:08 -0300 Subject: [PATCH 34/99] Remove accidentally committed .only from tests --- .../test/internal/buidler-evm/provider/modules/eth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index bdfedb9864..4992b78ba9 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -120,7 +120,7 @@ function getSignedTxHash( return bufferToHex(txToSign.hash(true)); } -describe.only("Eth module", function() { +describe("Eth module", function() { PROVIDERS.forEach(provider => { describe(`Provider ${provider.name}`, function() { setCWD(); From 10985824a6c1fa0a7efc0f2c4769ba7e8b0efc1c Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 13:14:27 -0300 Subject: [PATCH 35/99] Fix bug in builin tasks loading --- packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts b/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts index dbf4c28154..7cd74f4d64 100644 --- a/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts +++ b/packages/buidler-core/src/internal/core/tasks/builtin-tasks.ts @@ -28,7 +28,7 @@ export default function() { ); loadPluginFile( - path.join(__dirname, "..", "..", "..", "builtin-tasks", "jsonrpc") + path.join(__dirname, "..", "..", "..", "builtin-tasks", "node") ); loadPluginFile( From 2cc164840dca98db0f842a3c6b99cfd6c5114662 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 13:14:55 -0300 Subject: [PATCH 36/99] Update Buidler EVM config validation --- .../internal/core/config/config-validation.ts | 29 ++++++++++++++++++- .../internal/core/config/config-validation.ts | 24 +++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/core/config/config-validation.ts b/packages/buidler-core/src/internal/core/config/config-validation.ts index 4c83f85dbe..44fd4de3b1 100644 --- a/packages/buidler-core/src/internal/core/config/config-validation.ts +++ b/packages/buidler-core/src/internal/core/config/config-validation.ts @@ -88,7 +88,8 @@ const BuidlerNetworkConfig = t.type({ accounts: optional(t.array(BuidlerNetworkAccount)), blockGasLimit: optional(t.number), throwOnTransactionFailures: optional(t.boolean), - throwOnCallFailures: optional(t.boolean) + throwOnCallFailures: optional(t.boolean), + loggingEnabled: optional(t.boolean) }); const HDAccountsConfig = t.type({ @@ -239,6 +240,32 @@ export function getValidationErrors(config: any): string[] { ); } + if ( + buidlerNetwork.chainId !== undefined && + typeof buidlerNetwork.chainId !== "number" + ) { + errors.push( + getErrorMessage( + `BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.chainId`, + buidlerNetwork.chainId, + "number | undefined" + ) + ); + } + + if ( + buidlerNetwork.loggingEnabled !== undefined && + typeof buidlerNetwork.loggingEnabled !== "boolean" + ) { + errors.push( + getErrorMessage( + `BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.chainId`, + buidlerNetwork.loggingEnabled, + "boolean | undefined" + ) + ); + } + if (buidlerNetwork.accounts !== undefined) { if (Array.isArray(buidlerNetwork.accounts)) { for (const account of buidlerNetwork.accounts) { diff --git a/packages/buidler-core/test/internal/core/config/config-validation.ts b/packages/buidler-core/test/internal/core/config/config-validation.ts index a76a0a539f..7eb4ff5454 100644 --- a/packages/buidler-core/test/internal/core/config/config-validation.ts +++ b/packages/buidler-core/test/internal/core/config/config-validation.ts @@ -447,6 +447,30 @@ describe("Config validation", function() { }), ERRORS.GENERAL.INVALID_CONFIG ); + + expectBuidlerError( + () => + validateConfig({ + networks: { + [BUIDLEREVM_NETWORK_NAME]: { + loggingEnabled: 123 + } + } + }), + ERRORS.GENERAL.INVALID_CONFIG + ); + + expectBuidlerError( + () => + validateConfig({ + networks: { + [BUIDLEREVM_NETWORK_NAME]: { + loggingEnabled: "a" + } + } + }), + ERRORS.GENERAL.INVALID_CONFIG + ); }); }); From 97b2ec31c34ab0306b9289b5fb1a68a8a1a73e7c Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 15:15:59 -0300 Subject: [PATCH 37/99] Add solidity stack traces integration test --- .../artifacts/Contract.json | 15 ++ .../buidler.config.js | 1 + .../cache/solc-input.json | 31 ++++ .../cache/solc-output.json | 148 ++++++++++++++++++ .../contracts/Contract.sol | 7 + .../internal/buidler-evm/buidler.config.js | 1 + .../buidler-evm/provider/integration.ts | 35 +++++ 7 files changed, 238 insertions(+) create mode 100644 packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/artifacts/Contract.json create mode 100644 packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/buidler.config.js create mode 100644 packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-input.json create mode 100644 packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-output.json create mode 100644 packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/contracts/Contract.sol create mode 100644 packages/buidler-core/test/internal/buidler-evm/provider/integration.ts diff --git a/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/artifacts/Contract.json b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/artifacts/Contract.json new file mode 100644 index 0000000000..155698a5bb --- /dev/null +++ b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/artifacts/Contract.json @@ -0,0 +1,15 @@ +{ + "contractName": "Contract", + "abi": [ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } + ], + "bytecode": "0x6080604052348015600f57600080fd5b50600080fdfe", + "deployedBytecode": "0x6080604052600080fdfea265627a7a72315820716e5bc701a8bf3471fe0966d131dba10a4ed44239a5c5551b70273ae3d99eb264736f6c634300050f0032", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/buidler.config.js b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/buidler.config.js new file mode 100644 index 0000000000..f053ebf797 --- /dev/null +++ b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/buidler.config.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-input.json b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-input.json new file mode 100644 index 0000000000..1e8766911d --- /dev/null +++ b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-input.json @@ -0,0 +1,31 @@ +{ + "language": "Solidity", + "sources": { + "contracts/Contract.sol": { + "content": "pragma solidity ^0.5.0;\n\ncontract Contract {\n constructor() public {\n revert();\n }\n}\n" + } + }, + "settings": { + "metadata": { + "useLiteralContent": true + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers" + ], + "": [ + "id", + "ast" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-output.json b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-output.json new file mode 100644 index 0000000000..59e8678319 --- /dev/null +++ b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/cache/solc-output.json @@ -0,0 +1,148 @@ +{ + "contracts": { + "contracts/Contract.sol": { + "Contract": { + "abi": [ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "6080604052348015600f57600080fd5b50600080fdfe", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x0 DUP1 REVERT INVALID ", + "sourceMap": "25:64:0:-;;;47:40;8:9:-1;5:2;;;30:1;27;20:12;5:2;47:40:0;74:8;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "6080604052600080fdfea265627a7a72315820716e5bc701a8bf3471fe0966d131dba10a4ed44239a5c5551b70273ae3d99eb264736f6c634300050f0032", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 PUSH18 0x6E5BC701A8BF3471FE0966D131DBA10A4ED4 TIMESTAMP CODECOPY 0xA5 0xC5 SSTORE SHL PUSH17 0x273AE3D99EB264736F6C634300050F0032 ", + "sourceMap": "25:64:0:-;;;;;" + }, + "methodIdentifiers": {} + } + } + } + }, + "sources": { + "contracts/Contract.sol": { + "ast": { + "absolutePath": "contracts/Contract.sol", + "exportedSymbols": { + "Contract": [ + 9 + ] + }, + "id": 10, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + "^", + "0.5", + ".0" + ], + "nodeType": "PragmaDirective", + "src": "0:23:0" + }, + { + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 9, + "linearizedBaseContracts": [ + 9 + ], + "name": "Contract", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": { + "id": 7, + "nodeType": "Block", + "src": "68:19:0", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 4, + "name": "revert", + "nodeType": "Identifier", + "overloadedDeclarations": [ + 29, + 30 + ], + "referencedDeclaration": 29, + "src": "74:6:0", + "typeDescriptions": { + "typeIdentifier": "t_function_revert_pure$__$returns$__$", + "typeString": "function () pure" + } + }, + "id": 5, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "74:8:0", + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 6, + "nodeType": "ExpressionStatement", + "src": "74:8:0" + } + ] + }, + "documentation": null, + "id": 8, + "implemented": true, + "kind": "constructor", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 2, + "nodeType": "ParameterList", + "parameters": [], + "src": "58:2:0" + }, + "returnParameters": { + "id": 3, + "nodeType": "ParameterList", + "parameters": [], + "src": "68:0:0" + }, + "scope": 9, + "src": "47:40:0", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + } + ], + "scope": 10, + "src": "25:64:0" + } + ], + "src": "0:90:0" + }, + "id": 0 + } + } +} \ No newline at end of file diff --git a/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/contracts/Contract.sol b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/contracts/Contract.sol new file mode 100644 index 0000000000..d99afa2ec1 --- /dev/null +++ b/packages/buidler-core/test/fixture-projects/solidity-stack-traces-integration/contracts/Contract.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.5.0; + +contract Contract { + constructor() public { + revert(); + } +} diff --git a/packages/buidler-core/test/internal/buidler-evm/buidler.config.js b/packages/buidler-core/test/internal/buidler-evm/buidler.config.js index e69de29bb2..5face36662 100644 --- a/packages/buidler-core/test/internal/buidler-evm/buidler.config.js +++ b/packages/buidler-core/test/internal/buidler-evm/buidler.config.js @@ -0,0 +1 @@ +// This is here to prevent Buidler EVM from crashing diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/integration.ts b/packages/buidler-core/test/internal/buidler-evm/provider/integration.ts new file mode 100644 index 0000000000..be09d2692f --- /dev/null +++ b/packages/buidler-core/test/internal/buidler-evm/provider/integration.ts @@ -0,0 +1,35 @@ +import { assert } from "chai"; +import fsExtra from "fs-extra"; + +import { useEnvironment } from "../../../helpers/environment"; +import { useFixtureProject } from "../../../helpers/project"; + +describe("Provider integration tests", function() { + describe("Solidity stack traces", function() { + useFixtureProject("solidity-stack-traces-integration"); + useEnvironment(); + + it("Should compile", async function() { + const artifact = await fsExtra.readJSON("artifacts/Contract.json"); + + try { + await this.env.network.provider.send("eth_sendTransaction", [ + { + data: artifact.bytecode + } + ]); + } catch (error) { + assert.include(error.stack, "Contract.sol:"); + + // These exceptions should not have a code property, or Ethereum libs + // treat them as JSON-RPC responses, capturing them and loosing their + // stack trace. + assert.isUndefined(error.code); + + return; + } + + assert.fail("Exception expected but not thrown"); + }); + }); +}); From 5ce81c423fade7b642ca37f163813f93f16f27b7 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 16:16:20 -0300 Subject: [PATCH 38/99] Fix a pweb3 test --- packages/buidler-web3-legacy/test/pweb3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-web3-legacy/test/pweb3.ts b/packages/buidler-web3-legacy/test/pweb3.ts index d40e4ef759..3d58f6e101 100644 --- a/packages/buidler-web3-legacy/test/pweb3.ts +++ b/packages/buidler-web3-legacy/test/pweb3.ts @@ -67,7 +67,7 @@ describe("pweb3", () => { const TestContract = pweb3.eth.contract(ABI); const test = await TestContract.new({ - data: CONTRACT_BYTECODE, + data: `0x${CONTRACT_BYTECODE}`, from: accounts[0], gas: 456789 }); From 00a776bd788fac81a9b44f1e851b262a7e636207 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 16:16:36 -0300 Subject: [PATCH 39/99] Fix config validation wording --- .../buidler-core/src/internal/core/config/config-validation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/core/config/config-validation.ts b/packages/buidler-core/src/internal/core/config/config-validation.ts index 44fd4de3b1..8ef04b6f98 100644 --- a/packages/buidler-core/src/internal/core/config/config-validation.ts +++ b/packages/buidler-core/src/internal/core/config/config-validation.ts @@ -259,7 +259,7 @@ export function getValidationErrors(config: any): string[] { ) { errors.push( getErrorMessage( - `BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.chainId`, + `BuidlerConfig.networks.${BUIDLEREVM_NETWORK_NAME}.loggingEnabled`, buidlerNetwork.loggingEnabled, "boolean | undefined" ) From 5484c7eb669a659274c65a4e5a167ad842756a08 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 16:19:06 -0300 Subject: [PATCH 40/99] Add support for batched JSON-RPC requests --- .../internal/buidler-evm/jsonrpc/handler.ts | 118 +++++++++--------- 1 file changed, 56 insertions(+), 62 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index e3f1168a74..940e6f1823 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -1,12 +1,9 @@ -import chalk from "chalk"; import debug from "debug"; import { IncomingMessage, ServerResponse } from "http"; import getRawBody from "raw-body"; import WebSocket from "ws"; import { EthereumProvider } from "../../../types"; -import { BuidlerError } from "../../core/errors"; -import { ERRORS } from "../../core/errors-list"; import { isSuccessfulJsonResponse, isValidJsonRequest, @@ -33,30 +30,28 @@ export default class JsonRpcHandler { } public handleHttp = async (req: IncomingMessage, res: ServerResponse) => { - let rpcReq: JsonRpcRequest | undefined; - let rpcResp: JsonRpcResponse | undefined; - + let jsonHttpRequest: any; try { - rpcReq = await _readHttpRequest(req); - - rpcResp = await this._handleRequest(rpcReq); + jsonHttpRequest = await _readJsonHttpRequest(req); } catch (error) { - rpcResp = _handleError(error); + this._sendResponse(res, _handleError(error)); + return; } - // Validate the RPC response. - if (!isValidJsonResponse(rpcResp)) { - // Malformed response coming from the provider, report to user as an internal error. - rpcResp = _handleError(new InternalError("Internal error")); - } + if (Array.isArray(jsonHttpRequest)) { + const responses = await Promise.all( + jsonHttpRequest.map((singleReq: any) => + this._handleSingleRequest(singleReq) + ) + ); - if (rpcReq !== undefined) { - rpcResp.id = rpcReq.id; + this._sendResponse(res, responses); + return; } - res.statusCode = 200; - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(rpcResp)); + const rpcResp = await this._handleSingleRequest(jsonHttpRequest); + + this._sendResponse(res, rpcResp); }; public handleWs = async (ws: WebSocket) => { @@ -93,6 +88,10 @@ export default class JsonRpcHandler { try { rpcReq = _readWsRequest(msg as string); + if (!isValidJsonRequest(rpcReq)) { + throw new InvalidRequestError("Invalid request"); + } + rpcResp = await this._handleRequest(rpcReq); // If eth_subscribe was successful, keep track of the subscription id, @@ -132,11 +131,45 @@ export default class JsonRpcHandler { }); }; + private _sendResponse( + res: ServerResponse, + rpcResp: JsonRpcResponse | JsonRpcResponse[] + ) { + res.statusCode = 200; + res.setHeader("Content-Type", "application/json"); + res.end(JSON.stringify(rpcResp)); + } + + private async _handleSingleRequest(req: any): Promise { + if (!isValidJsonRequest(req)) { + return _handleError(new InvalidRequestError("Invalid request")); + } + + const rpcReq: JsonRpcRequest = req; + let rpcResp: JsonRpcResponse | undefined; + + try { + rpcResp = await this._handleRequest(rpcReq); + } catch (error) { + rpcResp = _handleError(error); + } + + // Validate the RPC response. + if (!isValidJsonResponse(rpcResp)) { + // Malformed response coming from the provider, report to user as an internal error. + rpcResp = _handleError(new InternalError("Internal error")); + } + + if (rpcReq !== undefined) { + rpcResp.id = rpcReq.id !== undefined ? rpcReq.id : null; + } + + return rpcResp; + } + private _handleRequest = async ( req: JsonRpcRequest ): Promise => { - // console.log(req.method); - const result = await this._provider.send(req.method, req.params); return { @@ -147,9 +180,7 @@ export default class JsonRpcHandler { }; } -const _readHttpRequest = async ( - req: IncomingMessage -): Promise => { +const _readJsonHttpRequest = async (req: IncomingMessage): Promise => { let json; try { @@ -161,10 +192,6 @@ const _readHttpRequest = async ( throw new InvalidJsonInputError(`Parse error: ${error.message}`); } - if (!isValidJsonRequest(json)) { - throw new InvalidRequestError("Invalid request"); - } - return json; }; @@ -176,16 +203,10 @@ const _readWsRequest = (msg: string): JsonRpcRequest => { throw new InvalidJsonInputError(`Parse error: ${error.message}`); } - if (!isValidJsonRequest(json)) { - throw new InvalidRequestError("Invalid request"); - } - return json; }; const _handleError = (error: any): JsonRpcResponse => { - _printError(error); - // In case of non-buidler error, treat it as internal and associate the appropriate error code. if (!BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { error = new InternalError(error.message); @@ -200,30 +221,3 @@ const _handleError = (error: any): JsonRpcResponse => { } }; }; - -const _printError = (error: any) => { - return; - if (BuidlerEVMProviderError.isBuidlerEVMProviderError(error)) { - // Report the error to console in the format of other BuidlerErrors (wrappedError.message), - // while preserving the stack from the originating error (error.stack). - const wrappedError = new BuidlerError( - ERRORS.BUILTIN_TASKS.JSONRPC_HANDLER_ERROR, - { - error: error.message - }, - error - ); - - console.error(chalk.red(`Error ${wrappedError.message}`)); - } else if (BuidlerError.isBuidlerError(error)) { - console.error(chalk.red(`Error ${error.message}`)); - } else if (error instanceof Error) { - console.error(chalk.red(`An unexpected error occurred: ${error.message}`)); - } else { - console.error(chalk.red("An unexpected error occurred.")); - } - - console.log(""); - - console.error(error.stack); -}; From 437a9bd28665d90d979e9e8e21b875138df00e4d Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 16:48:12 -0300 Subject: [PATCH 41/99] Document new Buidler EVM config value --- docs/config/README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/config/README.md b/docs/config/README.md index e68b8053a8..af4b2d8b79 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -24,7 +24,7 @@ module.exports = { The `networks` config field is an optional object where network names map to their configuration. -There are two kinds of networks in Buidler: [JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) based networks, +There are two kinds of networks in Buidler: [JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) based networks, and the built-in Buidler EVM network. You can customize which network is used by default when running Buidler by setting the config's `defaultNetwork` field. If you omit this config, its default value is `"buidlerevm"`. @@ -55,21 +55,23 @@ You can set the following fields on the `buidlerevm` config: - `blockGasLimit`: The block gas limit to use in Buidler EVM's blockchain. Default value: `9500000` - `hardfork`: This setting changes how Buidler EVM works, to mimic Ethereum's mainnet at a given hardfork. It must be one of `"byzantium"`, `"constantinople"`, `"petersburg"`, and `"istanbul"`. Default value: `"istanbul"` - -- `throwOnTransactionFailures`: A boolean that controls if Buidler EVM throws on transaction failures. -If this value is `true`, Buidler EVM will throw [combined JavaScript and Soldity stack traces](../buidler-evm/README.md#solidity-stack-traces) -on transaction failures. If it is `false`, it will return the failing transaction hash. In both cases -the transactions are added into the blockchain. Default value: `true` - -- `throwOnCallFailures`: A boolean that controls if Buidler EVM throws on call failures. -If this value is `true`, Buidler EVM will throw [combined JavaScript and Soldity stack traces](../buidler-evm/README.md#solidity-stack-traces) -when a call fails. If it is `false`, it will return the call's `return data`, which can contain -a revert reason. Default value: `true` + +- `throwOnTransactionFailures`: A boolean that controls if Buidler EVM throws on transaction failures. + If this value is `true`, Buidler EVM will throw [combined JavaScript and Soldity stack traces](../buidler-evm/README.md#solidity-stack-traces) + on transaction failures. If it is `false`, it will return the failing transaction hash. In both cases + the transactions are added into the blockchain. Default value: `true` +- `throwOnCallFailures`: A boolean that controls if Buidler EVM throws on call failures. + If this value is `true`, Buidler EVM will throw [combined JavaScript and Soldity stack traces](../buidler-evm/README.md#solidity-stack-traces) + when a call fails. If it is `false`, it will return the call's `return data`, which can contain + a revert reason. Default value: `true` + +- `loggingEnabled`: A boolean that controls if Buidler EVM logs every request or not. Default value: `false` for the + in-process Buidler EVM provider, `true` for the Buidler EVM backed JSON-RPC server (i.e. the `node` task). ### JSON-RPC based networks -These are networks that connect to an external node. Nodes can be running in your computer, like Ganache, or remotely, -like Infura. +These are networks that connect to an external node. Nodes can be running in your computer, like Ganache, or remotely, +like Infura. This kind of networks are configured with objects with the following fields: @@ -87,7 +89,6 @@ This kind of networks are configured with objects with the following fields: - `accounts`: This field controls which accounts Buidler uses. It can use the node's accounts (by setting it to `"remote"`), a list of local accounts (by setting it to an array of hex-encoded private keys), or use an [HD Wallet](#hd-wallet-config). Default value: `"remote"`. - ### HD Wallet config To use an HD Wallet with Buidler you should set your network's `accounts` field to an object with the following fields: @@ -100,7 +101,6 @@ To use an HD Wallet with Buidler you should set your network's `accounts` field - `count`: The number of accounts to derive. Default value: `10`. - ### Default networks object ```js From 1ffd32a4d5d4c856de189e4218f91cf4f91ccc03 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 16:48:29 -0300 Subject: [PATCH 42/99] Update Buidler EVM supported methods --- docs/buidler-evm/README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/buidler-evm/README.md b/docs/buidler-evm/README.md index ae22546f10..611dd30a20 100644 --- a/docs/buidler-evm/README.md +++ b/docs/buidler-evm/README.md @@ -146,7 +146,9 @@ To customise it, take a look at [the configuration section](/config/#buidler-evm - `eth_getBlockTransactionCountByHash` - `eth_getBlockTransactionCountByNumber` - `eth_getCode` - - `eth_getFilterChanges – Only for block filters` + - `eth_getFilterChanges` + - `eth_getFilterLogs` + - `eth_getLogs` - `eth_getStorageAt` - `eth_getTransactionByBlockHashAndIndex` - `eth_getTransactionByBlockNumberAndIndex` @@ -155,13 +157,17 @@ To customise it, take a look at [the configuration section](/config/#buidler-evm - `eth_getTransactionReceipt` - `eth_mining` - `eth_newBlockFilter` + - `eth_newFilter` + - `eth_newPendingTransactionFilter` - `eth_pendingTransactions` - `eth_sendRawTransaction` - `eth_sendTransaction` + - `eth_signTypedData` - `eth_sign` + - `eth_subscribe` - `eth_syncing` - `eth_uninstallFilter` - - `eth_signTypedData` + - `eth_unsubscribe` - `net_listening` - `net_peerCount` - `net_version` @@ -174,10 +180,13 @@ To customise it, take a look at [the configuration section](/config/#buidler-evm - `evm_mine` – same as Ganache, except it doesn’t accept a timestamp. - `evm_revert` – same as Ganache. - `evm_snapshot` – same as Ganache. - + ### Unsupported methods - - `eth_getFilterLogs` - - `eth_getLogs` + + - `eth_compileLLL` + - `eth_compileSerpent` + - `eth_compileSolidity` + - `eth_getCompilers` - `eth_getProof` - `eth_getUncleByBlockHashAndIndex` - `eth_getUncleByBlockNumberAndIndex` @@ -185,18 +194,10 @@ To customise it, take a look at [the configuration section](/config/#buidler-evm - `eth_getUncleCountByBlockNumber` - `eth_getWork` - `eth_hashrate` - - `eth_newFilter` - - `eth_newPendingTransactionFilter` - `eth_protocolVersion` - `eth_signTransaction` - `eth_submitHashrate` - `eth_submitWork` - - `eth_subscribe` - - `eth_unsubscribe` - - `eth_compileLLL` - - `eth_compileSerpent` - - `eth_compileSolidity` - - `eth_getCompilers` ## Limitations From 310da46436e4ae7ac9a5768d941693176f81f002 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 17:20:37 -0300 Subject: [PATCH 43/99] Add logging and Buidler EVM Node sections to Buidler EVM's doc --- docs/buidler-evm/README.md | 292 ++++++++++++++++++++++--------------- 1 file changed, 176 insertions(+), 116 deletions(-) diff --git a/docs/buidler-evm/README.md b/docs/buidler-evm/README.md index 611dd30a20..7c87dd4fbe 100644 --- a/docs/buidler-evm/README.md +++ b/docs/buidler-evm/README.md @@ -1,23 +1,39 @@ # Buidler EVM -Buidler comes built in with Buidler EVM, a local Ethereum network designed for development. It allows you to deploy your contracts, run your tests and debug your code. +Buidler comes built-in with Buidler EVM, a local Ethereum network designed for development. It allows you to deploy your contracts, run your tests and debug your code. ## How does it work? + - It mines a block with each transaction that it receives, in order and with no delay. - It's backed by the `ethereumjs-vm` EVM implementation, the same one used by ganache, Remix and Ethereum Studio. - It supports the following hardforks: - - byzantium - - constantinople - - petersburg - - istanbul + - byzantium + - constantinople + - petersburg + - istanbul ## How can I use it? + - Buidler will always spin up an instance on startup when `defaultNetwork` is empty or set to `buidlerevm`. It's the default behavior. -- It can be used to run tests, in the console, scripts and tasks +- It can be used to run tests, in the console, scripts, and tasks - Plugins (web3.js, ethers.js, Truffle, etc) connect directly to the provider - There's no need to make any changes to your tests or scripts. - It's simply another network and it can be used with `--network` +## Connecting to Buidler EVM from wallets and other software + +Buidler EVM can be run as a server or testing node. To do this, you just need to run + +```shell +npx buidler node +``` + +It will start Buidler EVM, and expose it as a JSON-RPC and WebSocket server. + +Then, just connect your wallet or application to `http://localhost:8545`. + +If you want to connect Buidler to this node, you only need to run it using `--network localhost`. + ## Solidity stack traces Buidler EVM has first-class Solidity support. It always knows which @@ -72,132 +88,166 @@ error message in the following cases: - Incorrectly calling a precompiled contract ## `console.log` + Buidler EVM allows you to print logging messages and contract variables calling `console.log()` from your Solidity code. You can see an example in the Sample Project. Follow the steps in [Quick Start](/getting-started/#quick-start) to try it out. - - You can use it in calls and transactions. It works with `view` functions, but not in `pure` ones. - - It always works, regardless of the call or transaction failing or being successful. - - To use it you need to import `@nomiclabs/buidler/console.sol`. - - It works with Solidity 0.5.x and 0.6.x. - - You can call `console.log` with up to 4 parameters in any order of following types: - - `uint` - - `string` - - `bool` - - `address` - - There's also the single parameter API for the types above, and additionally `bytes`, `bytes1`.. up to `bytes32`: - - `console.logInt(int i)` - - `console.logUint(uint i)` - - `console.logString(string memory s)` - - `console.logBool(bool b)` - - `console.logAddress(address a)` - - `console.logBytes(bytes memory b)` - - `console.logByte(byte b)` - - `console.logBytes1(bytes1 b)` - - `console.logBytes2(bytes2 b)` - - ... - - `console.logBytes32(bytes32 b)` - - `console.log` implements the same formatting options that can be found in Node.js' [`console.log`](https://nodejs.org/dist/latest-v12.x/docs/api/console.html#console_console_log_data_args), which in turn uses [`util.format`](https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args). - - Example: `console.log("Changing owner from %s to %s", currentOwner, newOwner)` - - It works with any library: web3.js, ethers.js, truffle-contract, waffle, etc. - - `console.log` is implemented in standard Solidity and then detected in Buidler EVM. This makes its compilation work with any other tools (like Remix or Truffle). - - `console.log` calls can run in other networks, like mainnet, kovan, ropsten, etc. They do nothing in those networks, but spend a minimal amount of gas. + +- You can use it in calls and transactions. It works with `view` functions, but not in `pure` ones. +- It always works, regardless of the call or transaction failing or being successful. +- To use it you need to import `@nomiclabs/buidler/console.sol`. +- It works with Solidity 0.5.x and 0.6.x. +- You can call `console.log` with up to 4 parameters in any order of following types: + - `uint` + - `string` + - `bool` + - `address` +- There's also the single parameter API for the types above, and additionally `bytes`, `bytes1`.. up to `bytes32`: + - `console.logInt(int i)` + - `console.logUint(uint i)` + - `console.logString(string memory s)` + - `console.logBool(bool b)` + - `console.logAddress(address a)` + - `console.logBytes(bytes memory b)` + - `console.logByte(byte b)` + - `console.logBytes1(bytes1 b)` + - `console.logBytes2(bytes2 b)` + - ... + - `console.logBytes32(bytes32 b)` +- `console.log` implements the same formatting options that can be found in Node.js' [`console.log`](https://nodejs.org/dist/latest-v12.x/docs/api/console.html#console_console_log_data_args), which in turn uses [`util.format`](https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args). + - Example: `console.log("Changing owner from %s to %s", currentOwner, newOwner)` +- It works with any library: web3.js, ethers.js, truffle-contract, waffle, etc. +- `console.log` is implemented in standard Solidity and then detected in Buidler EVM. This makes its compilation work with any other tools (like Remix or Truffle). +- `console.log` calls can run in other networks, like mainnet, kovan, ropsten, etc. They do nothing in those networks, but spend a minimal amount of gas. + +## Logging + +Buidler EVM uses its tracing infrastructure to offer rich logging that will help +you develop and debug smart contracts. + +For example, a successful transaction and a failed call would look like this: + +```sh +eth_sendTransaction + Contract deployment: Greeter + Contract address: 0x8858eeb3dfffa017d4bce9801d340d36cf895ccf + Transaction: 0x7ea2754e53f09508d42bd3074046f90595bedd61fcdf75a4764453454733add0 + From: 0xc783df8a850f42e7f7e57013759c285caa701eb6 + Value: 0 ETH + Gas used: 568851 of 2844255 + Block: #2 - Hash: 0x4847b316b12170c576999183da927c2f2056aa7d8f49f6e87430e6654a56dab0 + + console.log: + Deploying a Greeter with greeting: Hello, world! + +eth_call + Contract call: Greeter#greet + From: 0xc783df8a850f42e7f7e57013759c285caa701eb6 + + Error: VM Exception while processing transaction: revert Not feeling like it + at Greeter.greet (contracts/Greeter.sol:14) + at process._tickCallback (internal/process/next_tick.js:68:7) +``` + +This logging is enabled by default when using Buidler EVM's node (i.e. `npx buidler node`), but disabled when using +the in-process Buidler EVM provider. See [Buidler EVM's config](../config/README.md#buidler-evm-network) enabled it in both. ## Buidler EVM initial state Buidler EVM is initialized by default in this state: - - A brand new blockchain, just with the genesis block. - - 20 accounts with 10.000 ETH each - - `0xc783df8a850f42e7f7e57013759c285caa701eb6` - - `0xead9c93b79ae7c1591b1fb5323bd777e86e150d4` - - `0xe5904695748fe4a84b40b3fc79de2277660bd1d3` - - `0x92561f28ec438ee9831d00d1d59fbdc981b762b2` - - `0x2ffd013aaa7b5a7da93336c2251075202b33fb2b` - - `0x9fc9c2dfba3b6cf204c37a5f690619772b926e39` - - `0xfbc51a9582d031f2ceaad3959256596c5d3a5468` - - `0x84fae3d3cba24a97817b2a18c2421d462dbbce9f` - - `0xfa3bdc8709226da0da13a4d904c8b66f16c3c8ba` - - `0x6c365935ca8710200c7595f0a72eb6023a7706cd` - - `0xd7de703d9bbc4602242d0f3149e5ffcd30eb3adf` - - `0x532792b73c0c6e7565912e7039c59986f7e1dd1f` - - `0xea960515f8b4c237730f028cbacf0a28e7f45de0` - - `0x3d91185a02774c70287f6c74dd26d13dfb58ff16` - - `0x5585738127d12542a8fd6c71c19d2e4cecdab08a` - - `0x0e0b5a3f244686cf9e7811754379b9114d42f78b` - - `0x704cf59b16fd50efd575342b46ce9c5e07076a4a` - - `0x0a057a7172d0466aef80976d7e8c80647dfd35e3` - - `0x68dfc526037e9030c8f813d014919cc89e7d4d74` - - `0x26c43a1d431a4e5ee86cd55ed7ef9edf3641e901` - +- A brand new blockchain, just with the genesis block. +- 20 accounts with 10.000 ETH each + - `0xc783df8a850f42e7f7e57013759c285caa701eb6` + - `0xead9c93b79ae7c1591b1fb5323bd777e86e150d4` + - `0xe5904695748fe4a84b40b3fc79de2277660bd1d3` + - `0x92561f28ec438ee9831d00d1d59fbdc981b762b2` + - `0x2ffd013aaa7b5a7da93336c2251075202b33fb2b` + - `0x9fc9c2dfba3b6cf204c37a5f690619772b926e39` + - `0xfbc51a9582d031f2ceaad3959256596c5d3a5468` + - `0x84fae3d3cba24a97817b2a18c2421d462dbbce9f` + - `0xfa3bdc8709226da0da13a4d904c8b66f16c3c8ba` + - `0x6c365935ca8710200c7595f0a72eb6023a7706cd` + - `0xd7de703d9bbc4602242d0f3149e5ffcd30eb3adf` + - `0x532792b73c0c6e7565912e7039c59986f7e1dd1f` + - `0xea960515f8b4c237730f028cbacf0a28e7f45de0` + - `0x3d91185a02774c70287f6c74dd26d13dfb58ff16` + - `0x5585738127d12542a8fd6c71c19d2e4cecdab08a` + - `0x0e0b5a3f244686cf9e7811754379b9114d42f78b` + - `0x704cf59b16fd50efd575342b46ce9c5e07076a4a` + - `0x0a057a7172d0466aef80976d7e8c80647dfd35e3` + - `0x68dfc526037e9030c8f813d014919cc89e7d4d74` + - `0x26c43a1d431a4e5ee86cd55ed7ef9edf3641e901` + To customise it, take a look at [the configuration section](/config/#buidler-evm-network). ## JSON-RPC methods support ### Supported methods - - `eth_accounts` - - `eth_blockNumber` - - `eth_call` - - `eth_chainId` - - `eth_coinbase` - - `eth_estimateGas` - - `eth_gasPrice` - - `eth_getBalance` - - `eth_getBlockByHash` - - `eth_getBlockByNumber` - - `eth_getBlockTransactionCountByHash` - - `eth_getBlockTransactionCountByNumber` - - `eth_getCode` - - `eth_getFilterChanges` - - `eth_getFilterLogs` - - `eth_getLogs` - - `eth_getStorageAt` - - `eth_getTransactionByBlockHashAndIndex` - - `eth_getTransactionByBlockNumberAndIndex` - - `eth_getTransactionByHash` - - `eth_getTransactionCount` - - `eth_getTransactionReceipt` - - `eth_mining` - - `eth_newBlockFilter` - - `eth_newFilter` - - `eth_newPendingTransactionFilter` - - `eth_pendingTransactions` - - `eth_sendRawTransaction` - - `eth_sendTransaction` - - `eth_signTypedData` - - `eth_sign` - - `eth_subscribe` - - `eth_syncing` - - `eth_uninstallFilter` - - `eth_unsubscribe` - - `net_listening` - - `net_peerCount` - - `net_version` - - `web3_clientVersion` - - `web3_sha3` - +- `eth_accounts` +- `eth_blockNumber` +- `eth_call` +- `eth_chainId` +- `eth_coinbase` +- `eth_estimateGas` +- `eth_gasPrice` +- `eth_getBalance` +- `eth_getBlockByHash` +- `eth_getBlockByNumber` +- `eth_getBlockTransactionCountByHash` +- `eth_getBlockTransactionCountByNumber` +- `eth_getCode` +- `eth_getFilterChanges` +- `eth_getFilterLogs` +- `eth_getLogs` +- `eth_getStorageAt` +- `eth_getTransactionByBlockHashAndIndex` +- `eth_getTransactionByBlockNumberAndIndex` +- `eth_getTransactionByHash` +- `eth_getTransactionCount` +- `eth_getTransactionReceipt` +- `eth_mining` +- `eth_newBlockFilter` +- `eth_newFilter` +- `eth_newPendingTransactionFilter` +- `eth_pendingTransactions` +- `eth_sendRawTransaction` +- `eth_sendTransaction` +- `eth_signTypedData` +- `eth_sign` +- `eth_subscribe` +- `eth_syncing` +- `eth_uninstallFilter` +- `eth_unsubscribe` +- `net_listening` +- `net_peerCount` +- `net_version` +- `web3_clientVersion` +- `web3_sha3` + #### Special testing/debugging methods - - `evm_increaseTime` – same as Ganache. - - `evm_mine` – same as Ganache, except it doesn’t accept a timestamp. - - `evm_revert` – same as Ganache. - - `evm_snapshot` – same as Ganache. - +- `evm_increaseTime` – same as Ganache. +- `evm_mine` – same as Ganache, except it doesn’t accept a timestamp. +- `evm_revert` – same as Ganache. +- `evm_snapshot` – same as Ganache. + ### Unsupported methods - - `eth_compileLLL` - - `eth_compileSerpent` - - `eth_compileSolidity` - - `eth_getCompilers` - - `eth_getProof` - - `eth_getUncleByBlockHashAndIndex` - - `eth_getUncleByBlockNumberAndIndex` - - `eth_getUncleCountByBlockHash` - - `eth_getUncleCountByBlockNumber` - - `eth_getWork` - - `eth_hashrate` - - `eth_protocolVersion` - - `eth_signTransaction` - - `eth_submitHashrate` - - `eth_submitWork` +- `eth_compileLLL` +- `eth_compileSerpent` +- `eth_compileSolidity` +- `eth_getCompilers` +- `eth_getProof` +- `eth_getUncleByBlockHashAndIndex` +- `eth_getUncleByBlockNumberAndIndex` +- `eth_getUncleCountByBlockHash` +- `eth_getUncleCountByBlockNumber` +- `eth_getWork` +- `eth_hashrate` +- `eth_protocolVersion` +- `eth_signTransaction` +- `eth_submitHashrate` +- `eth_submitWork` ## Limitations @@ -215,3 +265,13 @@ but this may lead to your stack traces' line numbers being a little off. We recommend compiling without optimizations when testing and debugging your contracts. + +### Contracts reloading after recompilation + +If you start Buidler EVM's node and change your contract afterwards, you won't +get Solidity stack traces for those, and the logging functionality will be more limited. + +As a temporal workaround, you need to restart Buidler EVM's node, after recompiling +your contracts. + +This limitation will be removed in a future update. From 3ef99fff642b863c3c6b47f08adc2da0514ef2f4 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 17:31:36 -0300 Subject: [PATCH 44/99] Minor markdown fix --- docs/buidler-evm/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/buidler-evm/README.md b/docs/buidler-evm/README.md index 7c87dd4fbe..3d1a201589 100644 --- a/docs/buidler-evm/README.md +++ b/docs/buidler-evm/README.md @@ -24,7 +24,7 @@ Buidler comes built-in with Buidler EVM, a local Ethereum network designed for d Buidler EVM can be run as a server or testing node. To do this, you just need to run -```shell +```sh npx buidler node ``` From 2ff3e9be99143e77d76801523a7762ccc497a306 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 18:36:49 -0300 Subject: [PATCH 45/99] Improve Buidler EVM's invalid nonce error message (#451) --- .../buidler-core/src/internal/buidler-evm/provider/node.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index a4d84ff2be..9b72d90138 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -984,7 +984,9 @@ export class BuidlerNode { const actualNonce = new BN(tx.nonce); if (!expectedNonce.eq(actualNonce)) { throw new InvalidInputError( - `Invalid nonce. Expected ${expectedNonce} but got ${actualNonce}` + `Invalid nonce. Expected ${expectedNonce} but got ${actualNonce}. + +This usually means that you are sending multiple transactions form the same account in parallel. If you are using JavaScript, you probably forgot an await.` ); } From f4b9f5025dbf2b42ed940b47b3d02deb4203776b Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Fri, 21 Feb 2020 19:24:09 -0300 Subject: [PATCH 46/99] Deduplicate GitHub Actions (#452) * Only run GH workflow on pushes to master and PRs * Fix yml * Trigger Github Action --- .github/workflows/CI.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 0417c5b819..04c81d43f2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,6 +1,12 @@ name: CI -on: [pull_request, push] +on: + push: + branches: + - master + pull_request: + branches: + - "*" jobs: lint: From 9ce6958f344c826e39938a1adff337946c98e31f Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:51:54 -0300 Subject: [PATCH 47/99] Add missing protocol to node task's log --- packages/buidler-core/src/builtin-tasks/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/src/builtin-tasks/node.ts b/packages/buidler-core/src/builtin-tasks/node.ts index 61f688f5a2..80c9912f13 100644 --- a/packages/buidler-core/src/builtin-tasks/node.ts +++ b/packages/buidler-core/src/builtin-tasks/node.ts @@ -100,7 +100,7 @@ export default function() { console.log( chalk.green( - `Started HTTP and WebSocket JSON-RPC server at ${address}:${actualPort}/` + `Started HTTP and WebSocket JSON-RPC server at http://${address}:${actualPort}/` ) ); From 4a99e355af4083135b41fed46e48b552cd69fe40 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:53:41 -0300 Subject: [PATCH 48/99] Fix incorrect imports --- .../test/internal/buidler-evm/helpers/useProvider.ts | 4 ++-- .../test/internal/buidler-evm/provider/modules/eth.ts | 2 +- packages/buidler-core/test/internal/util/jsonrpc.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts b/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts index 376e50861f..02a3ce0ce9 100644 --- a/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts +++ b/packages/buidler-core/test/internal/buidler-evm/helpers/useProvider.ts @@ -1,10 +1,10 @@ import Common from "ethereumjs-common"; import { BN } from "ethereumjs-util"; -import { JsonRpcServer } from "../../../../internal/buidler-evm/jsonrpc/server"; +import { JsonRpcServer } from "../../../../src/internal/buidler-evm/jsonrpc/server"; import { BuidlerNode } from "../../../../src/internal/buidler-evm/provider/node"; import { BuidlerEVMProvider } from "../../../../src/internal/buidler-evm/provider/provider"; -import { EthereumProvider } from "../../../../types"; +import { EthereumProvider } from "../../../../src/types"; declare module "mocha" { interface Context { diff --git a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts index 4992b78ba9..c09a5fea08 100644 --- a/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/test/internal/buidler-evm/provider/modules/eth.ts @@ -21,7 +21,7 @@ import { RpcTransactionOutput, RpcTransactionReceiptOutput } from "../../../../../src/internal/buidler-evm/provider/output"; -import { EthereumProvider } from "../../../../../types"; +import { EthereumProvider } from "../../../../../src/types"; import { assertInvalidInputError, assertNodeBalances, diff --git a/packages/buidler-core/test/internal/util/jsonrpc.ts b/packages/buidler-core/test/internal/util/jsonrpc.ts index a8e570414f..f1e8bb2abf 100644 --- a/packages/buidler-core/test/internal/util/jsonrpc.ts +++ b/packages/buidler-core/test/internal/util/jsonrpc.ts @@ -1,6 +1,6 @@ import { assert } from "chai"; -import { isValidJsonResponse } from "../../../internal/util/jsonrpc"; +import { isValidJsonResponse } from "../../../src/internal/util/jsonrpc"; describe("JSON-RPC", function() { describe("JSON-RPC response validation", function() { From 079b0d7ff20854131187560a38f3c646e74a15be Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:55:12 -0300 Subject: [PATCH 49/99] Add getLatestBlockNumber method to Node --- .../src/internal/buidler-evm/provider/modules/eth.ts | 3 +-- .../buidler-core/src/internal/buidler-evm/provider/node.ts | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index 9a5a88cf12..c58a239bde 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -290,8 +290,7 @@ export class EthModule { } private async _blockNumberAction(): Promise { - const block = await this._node.getLatestBlock(); - const blockNumber = new BN(block.header.number); + const blockNumber = await this._node.getLatestBlockNumber(); return numberToRpcQuantity(blockNumber); } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 1aae3529b5..b748bb4722 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -504,6 +504,10 @@ export class BuidlerNode extends EventEmitter { return this._getLatestBlock(); } + public async getLatestBlockNumber(): Promise { + return new BN((await this._getLatestBlock()).header.number); + } + public async getLocalAccountAddresses(): Promise { return [...this._accountPrivateKeys.keys()]; } From f71f714fff4d6ba103b418e4ddc7323c3c3fe621 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:55:58 -0300 Subject: [PATCH 50/99] Accept latest blockTag as number --- .../buidler-evm/provider/modules/eth.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index c58a239bde..c059c14329 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -304,7 +304,7 @@ export class EthModule { rpcCall: RpcCallRequest, blockTag: OptionalBlockTag ): Promise { - this._validateBlockTag(blockTag); + await this._validateBlockTag(blockTag); const callParams = await this._rpcCallRequestToNodeCallParams(rpcCall); const { @@ -370,7 +370,7 @@ export class EthModule { transactionRequest: RpcTransactionRequest, blockTag: OptionalBlockTag ): Promise { - this._validateBlockTag(blockTag); + await this._validateBlockTag(blockTag); const txParams = await this._rpcTransactionRequestToNodeTransactionParams( transactionRequest @@ -416,7 +416,7 @@ export class EthModule { address: Buffer, blockTag: OptionalBlockTag ): Promise { - this._validateBlockTag(blockTag); + await this._validateBlockTag(blockTag); return numberToRpcQuantity(await this._node.getAccountBalance(address)); } @@ -521,7 +521,7 @@ export class EthModule { address: Buffer, blockTag: OptionalBlockTag ): Promise { - this._validateBlockTag(blockTag); + await this._validateBlockTag(blockTag); return bufferToRpcData(await this._node.getCode(address)); } @@ -614,7 +614,7 @@ export class EthModule { slot: BN, blockTag: OptionalBlockTag ): Promise { - this._validateBlockTag(blockTag); + await this._validateBlockTag(blockTag); const data = await this._node.getStorageAt(address, slot); @@ -718,7 +718,7 @@ export class EthModule { address: Buffer, blockTag: OptionalBlockTag ): Promise { - this._validateBlockTag(blockTag); + await this._validateBlockTag(blockTag); return numberToRpcQuantity(await this._node.getAccountNonce(address)); } @@ -1017,7 +1017,13 @@ export class EthModule { }; } - private _validateBlockTag(blockTag: OptionalBlockTag) { + private async _validateBlockTag(blockTag: OptionalBlockTag) { + const latestBlock = await this._node.getLatestBlockNumber(); + + if (BN.isBN(blockTag) && latestBlock.eq(blockTag)) { + return; + } + // We only support latest and pending. As this provider doesn't have pending transactions, its // actually just latest. if ( From 4dc85f51a1450b9343301522287542e82e40d4f3 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:56:39 -0300 Subject: [PATCH 51/99] Buidler EVM logging improvements --- .../buidler-evm/provider/modules/eth.ts | 124 ++++++++++++------ .../buidler-evm/provider/modules/logger.ts | 29 +++- .../internal/buidler-evm/provider/provider.ts | 4 +- 3 files changed, 110 insertions(+), 47 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index c059c14329..94437452ed 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -314,7 +314,7 @@ export class EthModule { consoleLogMessages } = await this._node.runCall(callParams); - this._logCallTrace(callParams, trace); + await this._logCallTrace(callParams, trace); this._logConsoleLogMessages(consoleLogMessages); @@ -384,9 +384,7 @@ export class EthModule { } = await this._node.estimateGas(txParams); if (error !== undefined) { - this._logContractAndFunctionName(trace); - this._logFrom(txParams.from); - this._logValue(new BN(txParams.value)); + await this._logEstimateGasTrace(txParams, trace); this._logConsoleLogMessages(consoleLogMessages); @@ -1092,7 +1090,17 @@ export class EthModule { return toBuffer(localAccounts[0]); } - private _logTransactionTrace( + private async _logEstimateGasTrace( + txParams: TransactionParams, + trace: MessageTrace + ) { + await this._logContractAndFunctionName(trace, true); + this._logFrom(txParams.from); + this._logTo(trace, txParams.to); + this._logValue(new BN(txParams.value)); + } + + private async _logTransactionTrace( tx: Transaction, trace: MessageTrace, block: Block, @@ -1102,19 +1110,20 @@ export class EthModule { return; } - this._logContractAndFunctionName(trace); - this._logger.log(`Transaction: ${bufferToHex(tx.hash(true))}`); + await this._logContractAndFunctionName(trace, false); + this._logger.logWithTitle("Transaction", bufferToHex(tx.hash(true))); this._logFrom(tx.getSenderAddress()); + this._logTo(trace, tx.to); this._logValue(new BN(tx.value)); - this._logger.log( - `Gas used: ${new BN(blockResult.receipts[0].gasUsed).toString( - 10 - )} of ${new BN(tx.gasLimit).toString(10)}` + this._logger.logWithTitle( + "Gas used", + `${new BN(blockResult.receipts[0].gasUsed).toString(10)} of ${new BN( + tx.gasLimit + ).toString(10)}` ); - this._logger.log( - `Block: #${new BN(block.header.number).toString( - 10 - )} - Hash: ${bufferToHex(block.hash())}` + this._logger.logWithTitle( + `Block #${new BN(block.header.number).toString(10)}`, + bufferToHex(block.hash()) ); } @@ -1142,50 +1151,69 @@ export class EthModule { } } - private _logCallTrace(callParams: CallParams, trace: MessageTrace) { + private async _logCallTrace(callParams: CallParams, trace: MessageTrace) { if (this._logger === undefined) { return; } - this._logContractAndFunctionName(trace); + await this._logContractAndFunctionName(trace, true); this._logFrom(callParams.from); + this._logTo(trace, callParams.to); if (callParams.value.gtn(0)) { this._logValue(callParams.value); } } - private _logContractAndFunctionName(trace: MessageTrace) { + private async _logContractAndFunctionName( + trace: MessageTrace, + shouldBeContract: boolean + ) { if (this._logger === undefined) { return; } if (isPrecompileTrace(trace)) { - this._logger.log( - `Precompile call: ` + this._logger.logWithTitle( + "Precompile call", + `` ); return; } if (isCreateTrace(trace)) { if (trace.bytecode === undefined) { - this._logger.log(`Contract deployment: ${UNRECOGNIZED_CONTRACT_NAME}`); + this._logger.logWithTitle( + "Contract deployment", + UNRECOGNIZED_CONTRACT_NAME + ); } else { - this._logger.log( - `Contract deployment: ${trace.bytecode.contract.name}` + this._logger.logWithTitle( + "Contract deployment", + trace.bytecode.contract.name ); } if (trace.deployedContract !== undefined) { - this._logger.log( - `Contract address: ${bufferToHex(trace.deployedContract)}` + this._logger.logWithTitle( + "Contract address", + bufferToHex(trace.deployedContract) ); } return; } + const code = await this._node.getCode(trace.address); + if (code.length === 0) { + if (shouldBeContract) { + this._logger.log(`WARNING: Calling an account which is not a contract`); + } + + return; + } + if (trace.bytecode === undefined) { - this._logger.log(`Contract call: ${UNRECOGNIZED_CONTRACT_NAME}`); + this._logger.logWithTitle("Contract call", UNRECOGNIZED_CONTRACT_NAME); return; } @@ -1200,8 +1228,9 @@ export class EthModule { ? FALLBACK_FUNCTION_NAME : func.name; - this._logger.log( - `Contract call: ${trace.bytecode.contract.name}#${functionName}` + this._logger.logWithTitle( + "Contract call", + `${trace.bytecode.contract.name}#${functionName}` ); } @@ -1214,22 +1243,19 @@ export class EthModule { // 0.0001 eth = 1e14 = 1e5gwei // 0.0001 gwei = 1e5 - if (value.eqn(0)) { - this._logger.log(`Value: 0 ETH`); - return; - } - - if (value.lt(new BN(10).pow(new BN(5)))) { - this._logger.log(`Value: ${value} wei`); - return; - } + let valueString: string; - if (value.lt(new BN(10).pow(new BN(14)))) { - this._logger.log(`Value: ${value.sub(new BN(10).pow(new BN(9)))} gwei`); - return; + if (value.eqn(0)) { + valueString = "0 ETH"; + } else if (value.lt(new BN(10).pow(new BN(5)))) { + valueString = `${value} wei`; + } else if (value.lt(new BN(10).pow(new BN(14)))) { + valueString = `${value.sub(new BN(10).pow(new BN(9)))} gwei`; + } else { + valueString = `${value.sub(new BN(10).pow(new BN(18)))} ETH`; } - this._logger.log(`Value: ${value.sub(new BN(10).pow(new BN(18)))} ETH`); + this._logger.logWithTitle("Value", valueString); } private _logError(error: Error) { @@ -1249,7 +1275,7 @@ export class EthModule { return; } - this._logger.log(`From: ${bufferToHex(from)}`); + this._logger.logWithTitle("From", bufferToHex(from)); } private async _sendTransactionAndReturnHash(tx: Transaction) { @@ -1261,7 +1287,7 @@ export class EthModule { error } = await this._node.runTransactionInNewBlock(tx); - this._logTransactionTrace(tx, trace, block, blockResult); + await this._logTransactionTrace(tx, trace, block, blockResult); this._logConsoleLogMessages(consoleLogMessages); @@ -1278,4 +1304,16 @@ export class EthModule { return bufferToRpcData(tx.hash(true)); } + + private _logTo(trace: MessageTrace, to: Buffer) { + if (this._logger === undefined) { + return; + } + + if (isCreateTrace(trace)) { + return; + } + + this._logger.logWithTitle("To", bufferToHex(to)); + } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts index 76482d667c..10e72687c4 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts @@ -1,15 +1,40 @@ +import util from "util"; + export class ModulesLogger { - private _logs: string[] = []; + private _logs: Array = []; + private _titleLength = 0; public log(message: string) { this._logs.push(message); } + public logWithTitle(title: string, message: string) { + // We always use the max title length we've seen. Otherwise the value move + // a lot with each tx/call. + if (title.length > this._titleLength) { + this._titleLength = title.length; + } + + this._logs.push([title, message]); + } + + public debug(...args: any[]) { + this.log(util.format(args[0], ...args.splice(1))); + } + public clearLogs() { this._logs = []; } public getLogs(): string[] { - return [...this._logs]; + return this._logs.map(l => { + if (typeof l === "string") { + return l; + } + + const title = `${l[0]}:`; + + return `${title.padEnd(this._titleLength + 1)} ${l[1]}`; + }); } } diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 08c02212cc..0fd372eb0a 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -83,6 +83,8 @@ export class BuidlerEVMProvider extends EventEmitter method: string, params: any[] = [] ): Promise { + this._logger.clearLogs(); + try { const result = await this._send(method, params); @@ -131,8 +133,6 @@ export class BuidlerEVMProvider extends EventEmitter this._log(""); throw err; - } finally { - this._logger.clearLogs(); } } From 8f0c9abbe15d01f50a61cab99a841cd2f9c4b1da Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:57:03 -0300 Subject: [PATCH 52/99] Better invalid blockTag error message --- .../src/internal/buidler-evm/provider/modules/eth.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index 94437452ed..c7ace5358f 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -1030,7 +1030,11 @@ export class EthModule { blockTag !== "pending" ) { throw new InvalidInputError( - "Only latest and pending block params are supported" + `Received block param ${blockTag.toString()} and latest block is ${latestBlock.toString()}. + +Only latest and pending block params are supported. + +If this error persists, try resetting your wallet's accounts.` ); } } From 875469dcd815f564b3aa0d3414ed55a745c1f62c Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:57:17 -0300 Subject: [PATCH 53/99] Better invalid nonce error message --- .../buidler-core/src/internal/buidler-evm/provider/node.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index b748bb4722..23e8d11de0 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -1250,7 +1250,12 @@ export class BuidlerNode extends EventEmitter { const actualNonce = new BN(tx.nonce); if (!expectedNonce.eq(actualNonce)) { throw new InvalidInputError( - `Invalid nonce. Expected ${expectedNonce} but got ${actualNonce}` + `Invalid nonce. Expected ${expectedNonce} but got ${actualNonce}. + +If you are running a script or test, you may be sending transactions in parallel. +Using JavaScript? You probably forgot an await. + +If you are using a wallet or dapp, try resetting your wallet's accounts.` ); } From f2ee8cae5324388358c2106967c94a872845b636 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 11:57:30 -0300 Subject: [PATCH 54/99] Accept pending block tag for filters as if it were latest --- .../src/internal/buidler-evm/provider/modules/eth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index c7ace5358f..f5dc9070f1 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -1047,7 +1047,7 @@ If this error persists, try resetting your wallet's accounts.` case "latest": return LATEST_BLOCK; case "pending": - throw new InvalidArgumentsError("pending not supported"); + return LATEST_BLOCK; } return blockTag; From 6d2665cfd09e7016cd395457c785cce33f883a5c Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 12:25:23 -0300 Subject: [PATCH 55/99] Remove unnecessary SIGINT handler --- .../src/internal/buidler-evm/jsonrpc/server.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts index 613fe34522..bca4c4996d 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/server.ts @@ -42,8 +42,6 @@ export class JsonRpcServer { }; public listen = (): Promise<{ address: string; port: number }> => { - process.once("SIGINT", this._onSigint); - return new Promise(resolve => { log(`Starting JSON-RPC server on port ${this._config.port}`); this._httpServer.listen(this._config.port, this._config.hostname, () => { @@ -66,8 +64,6 @@ export class JsonRpcServer { }; public close = async () => { - process.removeListener("SIGINT", this._onSigint); - return Promise.all([ new Promise((resolve, reject) => { log("Closing JSON-RPC server"); @@ -97,8 +93,4 @@ export class JsonRpcServer { }) ]); }; - - private _onSigint = async () => { - await this.close(); - }; } From d5dd048fd0179b9fe87ecd31e1c60096db335873 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 13:32:08 -0300 Subject: [PATCH 56/99] Fix Buidler EVM ETH values logging --- .../buidler-evm/provider/modules/eth.ts | 19 +---- .../src/internal/util/wei-values.ts | 67 ++++++++++++++++ .../test/internal/util/wei-values.ts | 80 +++++++++++++++++++ 3 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 packages/buidler-core/src/internal/util/wei-values.ts create mode 100644 packages/buidler-core/test/internal/util/wei-values.ts diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index f5dc9070f1..6997ae0414 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -11,6 +11,7 @@ import { import * as t from "io-ts"; import util from "util"; +import { weiToHumanReadableString } from "../../../util/wei-values"; import { isCreateTrace, isPrecompileTrace, @@ -1242,24 +1243,8 @@ If this error persists, try resetting your wallet's accounts.` if (this._logger === undefined) { return; } - // eth = 1e18 - // gwei = 1e9 - // 0.0001 eth = 1e14 = 1e5gwei - // 0.0001 gwei = 1e5 - - let valueString: string; - - if (value.eqn(0)) { - valueString = "0 ETH"; - } else if (value.lt(new BN(10).pow(new BN(5)))) { - valueString = `${value} wei`; - } else if (value.lt(new BN(10).pow(new BN(14)))) { - valueString = `${value.sub(new BN(10).pow(new BN(9)))} gwei`; - } else { - valueString = `${value.sub(new BN(10).pow(new BN(18)))} ETH`; - } - this._logger.logWithTitle("Value", valueString); + this._logger.logWithTitle("Value", weiToHumanReadableString(value)); } private _logError(error: Error) { diff --git a/packages/buidler-core/src/internal/util/wei-values.ts b/packages/buidler-core/src/internal/util/wei-values.ts new file mode 100644 index 0000000000..5c8e31926f --- /dev/null +++ b/packages/buidler-core/src/internal/util/wei-values.ts @@ -0,0 +1,67 @@ +import { BN } from "ethereumjs-util"; + +/** + * This function turns a wei value in a human readable string. It shows values + * in ETH, gwei or wei, depending on how large it is. + * + * It never show more than 99999 wei or gwei, moving to the larger denominator + * when necessary. + * + * It never shows more than 4 decimal digits. Adapting denominator and + * truncating as necessary. + */ +export function weiToHumanReadableString(wei: BN | number): string { + if (typeof wei === "number") { + wei = new BN(wei); + } + + if (wei.eqn(0)) { + return "0 ETH"; + } + + if (wei.lt(new BN(10).pow(new BN(5)))) { + return `${wei} wei`; + } + + if (wei.lt(new BN(10).pow(new BN(14)))) { + return `${toDecimalString(wei, 9, 4)} gwei`; + } + + return `${toDecimalString(wei, 18, 4)} ETH`; +} + +function toDecimalString( + value: BN, + digitsToInteger: number, + decimalDigits: number = 4 +): string { + const oneUnit = new BN(10).pow(new BN(digitsToInteger)); + const oneDecimal = new BN(10).pow(new BN(digitsToInteger - decimalDigits)); + + const integer = value.div(oneUnit); + + const decimals = value.mod(oneUnit).div(oneDecimal); + if (decimals.eqn(0)) { + return integer.toString(10); + } + + const decimalsString = removeRightZeros( + decimals.toString(10).padStart(decimalDigits, "0") + ); + + return `${integer.toString(10)}.${decimalsString}`; +} + +function removeRightZeros(str: string): string { + let zeros = 0; + + for (let i = str.length - 1; i >= 0; i--) { + if (str.charAt(i) !== "0") { + break; + } + + zeros += 1; + } + + return str.substr(0, str.length - zeros); +} diff --git a/packages/buidler-core/test/internal/util/wei-values.ts b/packages/buidler-core/test/internal/util/wei-values.ts new file mode 100644 index 0000000000..ac4563afd4 --- /dev/null +++ b/packages/buidler-core/test/internal/util/wei-values.ts @@ -0,0 +1,80 @@ +import { assert } from "chai"; +import { BN } from "ethereumjs-util"; + +import { weiToHumanReadableString } from "../../../src/internal/util/wei-values"; + +describe("Wei values formatting", function() { + const ONE_GWEI = new BN(10).pow(new BN(9)); + const ONE_ETH = new BN(10).pow(new BN(18)); + + it("Should show 0 wei as 0 ETH", function() { + assert.equal(weiToHumanReadableString(0), "0 ETH"); + }); + + it("Should show 1 wei as wei", function() { + assert.equal(weiToHumanReadableString(1), "1 wei"); + }); + + it("Should show 10 wei as wei", function() { + assert.equal(weiToHumanReadableString(10), "10 wei"); + }); + + it("Should show 10000 wei as wei", function() { + assert.equal(weiToHumanReadableString(10000), "10000 wei"); + }); + + it("Should show 100000 wei as gwei", function() { + assert.equal(weiToHumanReadableString(100000), "0.0001 gwei"); + }); + + it("Should show 0.0001 gwei as gwei", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI.divn(10000)), "0.0001 gwei"); + }); + + it("Should show 0.1 gwei as gwei", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI.divn(10)), "0.1 gwei"); + }); + + it("Should show 1 gwei as gwei", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI), "1 gwei"); + }); + + it("Should show 10 gwei as gwei", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI.muln(10)), "10 gwei"); + }); + + it("Should show 10 gwei as gwei", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI.muln(10)), "10 gwei"); + }); + + it("Should show 10000 gwei as gwei", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI.muln(10000)), "10000 gwei"); + }); + + it("Should show 100000 gwei as ETH", function() { + assert.equal(weiToHumanReadableString(ONE_GWEI.muln(100000)), "0.0001 ETH"); + }); + + it("Should show 0.0001 ETH as ETH", function() { + assert.equal(weiToHumanReadableString(ONE_ETH.divn(10000)), "0.0001 ETH"); + }); + + it("Should show 0.1 ETH as ETH", function() { + assert.equal(weiToHumanReadableString(ONE_ETH.divn(10)), "0.1 ETH"); + }); + + it("Should show 1 ETH as ETH", function() { + assert.equal(weiToHumanReadableString(ONE_ETH), "1 ETH"); + }); + + it("Should show 1.2 ETH as ETH", function() { + assert.equal( + weiToHumanReadableString(ONE_ETH.add(ONE_ETH.divn(10).muln(2))), + "1.2 ETH" + ); + }); + + it("Should show 43 ETH as ETH", function() { + assert.equal(weiToHumanReadableString(ONE_ETH.muln(43)), "43 ETH"); + }); +}); From f3d31fcb3670a62700468048c6d48833698ededc Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 13:50:53 -0300 Subject: [PATCH 57/99] Make unexpected errors logging nicer --- .../buidler-core/src/internal/buidler-evm/provider/provider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 0fd372eb0a..47998f7230 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -124,6 +124,7 @@ export class BuidlerEVMProvider extends EventEmitter this._log(err.message, true); } else { this._logError(err, true); + this._log(""); this._log( "If you think this is a bug in Buidler, please report it here: https://buidler.dev/reportbug", true From cb02379728e9d95f034da9848ff8ac9e86f3eb2e Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 13:51:07 -0300 Subject: [PATCH 58/99] Add eth_getTransactionCount special case --- .../buidler-evm/provider/modules/eth.ts | 18 ++++++++++++++++++ .../src/internal/buidler-evm/provider/node.ts | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index 6997ae0414..c78cd88715 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -717,6 +717,24 @@ export class EthModule { address: Buffer, blockTag: OptionalBlockTag ): Promise { + // TODO: MetaMask does some eth_getTransactionCount(sender, currentBlock) + // calls right after sending a transaction. + // As we insta-mine, the currentBlock that they send is different from the + // one we have, which results on an error. + // This is not a big deal TBH, MM eventually resynchronizes, but it shows + // some hard to understand errors to our users. + // To avoid confusing our users, we have a special case here, just + // for now. + // This should be changed ASAP. + if ( + BN.isBN(blockTag) && + blockTag.eq((await this._node.getLatestBlockNumber()).subn(1)) + ) { + return numberToRpcQuantity( + await this._node.getAccountNonceInPreviousBlock(address) + ); + } + await this._validateBlockTag(blockTag); return numberToRpcQuantity(await this._node.getAccountNonce(address)); diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts index 23e8d11de0..d2bb43133a 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/node.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/node.ts @@ -500,6 +500,17 @@ export class BuidlerNode extends EventEmitter { return new BN(account.nonce); } + public async getAccountNonceInPreviousBlock(address: Buffer): Promise { + const account = await this._stateManager.getAccount(address); + + const latestBlock = await this._getLatestBlock(); + const latestBlockTxsFromAccount = latestBlock.transactions.filter( + (tx: Transaction) => tx.getSenderAddress().equals(address) + ); + + return new BN(account.nonce).subn(latestBlockTxsFromAccount.length); + } + public async getLatestBlock(): Promise { return this._getLatestBlock(); } From ff92015ff8bbe2cc5ff78e0f1dd35d1135fcaef0 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 14:47:56 -0300 Subject: [PATCH 59/99] Add CORS to Buidler EVM JSON-RPC server --- .../internal/buidler-evm/jsonrpc/handler.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts index 940e6f1823..26b62621c0 100644 --- a/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts +++ b/packages/buidler-core/src/internal/buidler-evm/jsonrpc/handler.ts @@ -30,6 +30,12 @@ export default class JsonRpcHandler { } public handleHttp = async (req: IncomingMessage, res: ServerResponse) => { + this._setCorsHeaders(res); + if (req.method === "OPTIONS") { + this._sendEmptyResponse(res); + return; + } + let jsonHttpRequest: any; try { jsonHttpRequest = await _readJsonHttpRequest(req); @@ -131,6 +137,18 @@ export default class JsonRpcHandler { }); }; + private _sendEmptyResponse(res: ServerResponse) { + res.writeHead(200); + res.end(); + } + + private _setCorsHeaders(res: ServerResponse) { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Request-Method", "*"); + res.setHeader("Access-Control-Allow-Methods", "OPTIONS, GET"); + res.setHeader("Access-Control-Allow-Headers", "*"); + } + private _sendResponse( res: ServerResponse, rpcResp: JsonRpcResponse | JsonRpcResponse[] From d636bca033f07def67626264eb6f0340346208d9 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 14:57:08 -0300 Subject: [PATCH 60/99] Collapse repeated method calls in the logs --- packages/buidler-core/package.json | 1 + .../buidler-evm/provider/modules/logger.ts | 4 ++ .../internal/buidler-evm/provider/provider.ts | 56 ++++++++++++++++++- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index 08d7973ead..0471afd08d 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -73,6 +73,7 @@ "@types/bn.js": "^4.11.5", "@types/lru-cache": "^5.1.0", "abort-controller": "^3.0.0", + "ansi-escapes": "^4.3.0", "bip32": "^2.0.3", "bip39": "^3.0.2", "chalk": "^2.4.2", diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts index 10e72687c4..f4ae68ed39 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/logger.ts @@ -26,6 +26,10 @@ export class ModulesLogger { this._logs = []; } + public hasLogs(): boolean { + return this._logs.length > 0; + } + public getLogs(): string[] { return this._logs.map(l => { if (typeof l === "string") { diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 47998f7230..c3a1bf0306 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -1,3 +1,4 @@ +import ansiEscapes from "ansi-escapes"; import chalk, { Chalk } from "chalk"; import debug from "debug"; import Common from "ethereumjs-common"; @@ -34,6 +35,14 @@ const log = debug("buidler:core:buidler-evm:provider"); // Set of methods that are never logged const PRIVATE_RPC_METHODS = new Set(["buidler_getStackTraceFailuresCount"]); +// These methods are shown every time, even if repeated right next to the other +const NON_COLLAPSIBLE_METHODS = new Set([ + "eth_sendTransaction", + "eth_sendRawTransaction", + "eth_call", + "eth_estimateGas" +]); + // tslint:disable only-buidler-error export class BuidlerEVMProvider extends EventEmitter @@ -48,6 +57,9 @@ export class BuidlerEVMProvider extends EventEmitter private readonly _mutex = new Mutex(); private readonly _logger = new ModulesLogger(); + private _methodBeingCollapsed?: string; + private _methodCollapsedCount: number = 0; + constructor( private readonly _hardfork: string, private readonly _networkName: string, @@ -87,21 +99,30 @@ export class BuidlerEVMProvider extends EventEmitter try { const result = await this._send(method, params); + // We log after running the method, because we want to use different + // colors depending on whether it failed or not // TODO: If an eth_call, eth_sendTransaction, or eth_sendRawTransaction // fails without throwing, this will be displayed in green. It's unclear // if this is correct. See Eth module's TODOs for more info. - // We log after running the method, because we want to use different - // colors depending on whether it failed or not - this._log(method, false, chalk.green); + + if (this._shouldCollapseMethod(method)) { + this._logCollapsedMethod(method); + } else { + this._startCollapsingMethod(method); + this._log(method, false, chalk.green); + } const loggedSomething = this._logModuleMessages(); if (loggedSomething) { + this._stopCollapsingMethod(); this._log(""); } return result; } catch (err) { + this._stopCollapsingMethod(); + if ( err instanceof MethodNotFoundError || err instanceof MethodNotSupportedError @@ -137,6 +158,35 @@ export class BuidlerEVMProvider extends EventEmitter } } + private _logCollapsedMethod(method: string) { + this._methodCollapsedCount += 1; + this._clearLastLogLine(); + this._log(`${method} (${this._methodCollapsedCount})`, false, chalk.green); + } + + private _startCollapsingMethod(method: string) { + this._methodBeingCollapsed = method; + this._methodCollapsedCount = 1; + } + + private _stopCollapsingMethod() { + this._methodBeingCollapsed = undefined; + this._methodCollapsedCount = 0; + } + + private _clearLastLogLine() { + process.stdout.write(ansiEscapes.eraseLines(2)); + } + + private _shouldCollapseMethod(method: string) { + return ( + method === this._methodBeingCollapsed && + !this._logger.hasLogs() && + !NON_COLLAPSIBLE_METHODS.has(method) && + this._methodCollapsedCount > 0 + ); + } + private async _send(method: string, params: any[] = []): Promise { await this._init(); From bb3349bc462d51b5d991f864ada2399d8a336463 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sat, 22 Feb 2020 16:37:34 -0300 Subject: [PATCH 61/99] Less console updates filckering --- .../internal/buidler-evm/provider/provider.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index c3a1bf0306..8273a3e741 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -160,8 +160,16 @@ export class BuidlerEVMProvider extends EventEmitter private _logCollapsedMethod(method: string) { this._methodCollapsedCount += 1; - this._clearLastLogLine(); - this._log(`${method} (${this._methodCollapsedCount})`, false, chalk.green); + + process.stdout.write( + // tslint:disable-next-line:prefer-template + ansiEscapes.cursorHide + + ansiEscapes.cursorPrevLine + + chalk.green(`${method} (${this._methodCollapsedCount})`) + + "\n" + + ansiEscapes.eraseEndLine + + ansiEscapes.cursorShow + ); } private _startCollapsingMethod(method: string) { @@ -174,10 +182,6 @@ export class BuidlerEVMProvider extends EventEmitter this._methodCollapsedCount = 0; } - private _clearLastLogLine() { - process.stdout.write(ansiEscapes.eraseLines(2)); - } - private _shouldCollapseMethod(method: string) { return ( method === this._methodBeingCollapsed && From cbc49d0396aa99f8c162523f9d0fe6dd16029301 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 10:53:17 -0300 Subject: [PATCH 62/99] Make the Waffle plugin's BRE extension lazy (#453) --- packages/buidler-waffle/src/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/buidler-waffle/src/index.ts b/packages/buidler-waffle/src/index.ts index 1293eab5d9..fa62ecac8e 100644 --- a/packages/buidler-waffle/src/index.ts +++ b/packages/buidler-waffle/src/index.ts @@ -1,14 +1,19 @@ import { extendEnvironment, usePlugin } from "@nomiclabs/buidler/config"; - -import { WaffleMockProviderAdapter } from "./waffle-provider-adapter"; +import { lazyObject } from "@nomiclabs/buidler/plugins"; export default function() { extendEnvironment(bre => { // We can't actually implement a MockProvider because of its private // properties, so we cast it here 😢 - bre.waffle = { - provider: new WaffleMockProviderAdapter(bre.network) as any - }; + bre.waffle = lazyObject(() => { + const { + WaffleMockProviderAdapter + } = require("./waffle-provider-adapter"); + + return { + provider: new WaffleMockProviderAdapter(bre.network) as any + }; + }); }); usePlugin("@nomiclabs/buidler-ethers"); From ff43d6788bb94f5dee3317005b553efdff681434 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 11:21:40 -0300 Subject: [PATCH 63/99] Add branching section to CONTRIBUTING.md --- CONTRIBUTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68f166bbc3..2eff39cd22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,6 +48,15 @@ Prettier and forbids some dangerous patterns. The linter is always run in the CI, so make sure it passes before pushing code. You can use `npm run lint` and `npm run lint:fix` inside the packages' folders. +## Branching + +We work on the branch [`development`](https://github.com/nomiclabs/buidler/tree/development) +and keep `master` in sync with the latest release. + +Please, branch from `development` when implementing a new feature or fixing a +bug, and use it as the base branch in every pull request. + + ## Dependencies We keep our dependencies versions in sync between the different projects. From 9a012c6e7994b15ba4d615a1475b85b4d8e46ee5 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 11:24:40 -0300 Subject: [PATCH 64/99] [buidler-ethers] Expose the entire ethers' API and better helpers in the BRE (#454) * Expose the entire ethers and better helpers * Fix buidler-waffle's doc * Improve readme --- packages/buidler-ethers/README.md | 61 +- packages/buidler-ethers/scripts/run-tests.js | 2 +- packages/buidler-ethers/src/helpers.ts | 91 +++ packages/buidler-ethers/src/index.ts | 31 +- .../buidler-ethers/src/type-extensions.d.ts | 41 +- .../buidler-project/artifacts/Greeter.json | 30 +- .../buidler-project/artifacts/IGreeter.json | 1 + .../test/buidler-project/buidler.config.js | 10 +- .../cache/last-solc-config.json | 10 + .../buidler-project/cache/solc-input.json | 34 ++ .../buidler-project/cache/solc-output.json | 541 ++++++++++++++++++ packages/buidler-ethers/test/helpers.ts | 1 + packages/buidler-ethers/test/index.ts | 317 +++++++++- packages/buidler-waffle/README.md | 2 - 14 files changed, 1084 insertions(+), 88 deletions(-) create mode 100644 packages/buidler-ethers/src/helpers.ts create mode 100644 packages/buidler-ethers/test/buidler-project/cache/last-solc-config.json create mode 100644 packages/buidler-ethers/test/buidler-project/cache/solc-input.json create mode 100644 packages/buidler-ethers/test/buidler-project/cache/solc-output.json diff --git a/packages/buidler-ethers/README.md b/packages/buidler-ethers/README.md index 05f3988c21..2654421e7c 100644 --- a/packages/buidler-ethers/README.md +++ b/packages/buidler-ethers/README.md @@ -7,7 +7,7 @@ ## What -This plugin brings to Buidler the Ethereum library ethers.js, which allows you to interact with the Ethereum blockchain in a simple way. +This plugin brings to Buidler the Ethereum library `ethers.js`, which allows you to interact with the Ethereum blockchain in a simple way. ## Installation @@ -27,17 +27,43 @@ This plugin creates no additional tasks. ## Environment extensions -An initialized `ethers` object is injected into the environment: +This plugins adds an `ethers` object to the Buidler Runtime Environment. -```ts -ethers: { - provider: JsonRpcProvider; - getContract: (name: string) => Promise; - signers: () => Promise; -} +This object has the same API than `ethers.js`, with some extra Buidler-specific +functionality. + +### Provider object + +A `provider` field is added to `ethers`, which is an `ethers.providers.Provider` +automatically connected to the selected network. + +### Helpers + +These helpers are added to the `ethers` object: + +```typescript +function getContractFactory(name: string, signer?: ethers.Signer): Promise; + +function getContractFactory(abi: any[], bytecode: ethers.utils.Arrayish | string, signer?: ethers.Signer): Promise; + +function getContractAt(nameOrAbi: string | any[], address: string, signer?: ethers.Signer): Promise; + +function getSigners() => Promise; ``` -The `ContractFactory`s returned by `getContract` are connected by to the first signer returned by `env.ethers.signers`. +The `Contract`s and `ContractFactory`s returned by these helpers are connected to the first signer returned by `getSigners` be default. + +#### Deprecated helpers + +These helpers are also added, but deprecated: + +```typescript +// Use getContractFactory instead +function getContract(name: string): Promise; + +// Use getSigners instead +function signers(): Promise; +``` ## Usage @@ -49,16 +75,19 @@ Install it and access ethers through the Buidler Runtime Environment anywhere yo usePlugin("@nomiclabs/buidler-ethers"); // task action function receives the Buidler Runtime Environment as second argument -task("blockNumber", "Prints the current block number", async (_, { ethers }) => { - - await ethers.provider.getBlockNumber().then((blockNumber) => { - console.log("Current block number: " + blockNumber); - }); - -}); +task( + "blockNumber", + "Prints the current block number", + async (_, { ethers }) => { + await ethers.provider.getBlockNumber().then(blockNumber => { + console.log("Current block number: " + blockNumber); + }); + } +); module.exports = {}; ``` + And then run `npx buidler blockNumber` to try it. Read the documentation on the [Buidler Runtime Environment](https://buidler.dev/documentation/#buidler-runtime-environment-bre) to learn how to access the BRE in different ways to use ethers.js from anywhere the BRE is accessible. diff --git a/packages/buidler-ethers/scripts/run-tests.js b/packages/buidler-ethers/scripts/run-tests.js index e260c17a1e..2766a7490e 100755 --- a/packages/buidler-ethers/scripts/run-tests.js +++ b/packages/buidler-ethers/scripts/run-tests.js @@ -16,7 +16,7 @@ function cleanup() { } async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js"], { + ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js", "-d"], { stdio: "ignore" }); await sleep(4000); diff --git a/packages/buidler-ethers/src/helpers.ts b/packages/buidler-ethers/src/helpers.ts new file mode 100644 index 0000000000..a16a79475e --- /dev/null +++ b/packages/buidler-ethers/src/helpers.ts @@ -0,0 +1,91 @@ +import { readArtifact } from "@nomiclabs/buidler/plugins"; +import { BuidlerRuntimeEnvironment } from "@nomiclabs/buidler/types"; +import { ethers } from "ethers"; + +export async function getSigners(bre: BuidlerRuntimeEnvironment) { + const accounts = await bre.ethers.provider.listAccounts(); + return accounts.map((account: string) => + bre.ethers.provider.getSigner(account) + ); +} + +export function getContractFactory( + bre: BuidlerRuntimeEnvironment, + name: string, + signer?: ethers.Signer +): Promise; + +export function getContractFactory( + bre: BuidlerRuntimeEnvironment, + abi: any[], + bytecode: ethers.utils.Arrayish | string, + signer?: ethers.Signer +): Promise; + +export async function getContractFactory( + bre: BuidlerRuntimeEnvironment, + nameOrAbi: string | any[], + bytecodeOrSigner?: ethers.Signer | ethers.utils.Arrayish | string, + signer?: ethers.Signer +) { + if (typeof nameOrAbi === "string") { + return getContractFactoryByName(bre, nameOrAbi, bytecodeOrSigner as + | ethers.Signer + | undefined); + } + + return getContractFactoryByAbiAndBytecode( + bre, + nameOrAbi, + bytecodeOrSigner as ethers.utils.Arrayish | string, + signer + ); +} + +export async function getContractFactoryByName( + bre: BuidlerRuntimeEnvironment, + name: string, + signer?: ethers.Signer +) { + if (signer === undefined) { + const signers = await bre.ethers.signers(); + signer = signers[0]; + } + + const artifact = await readArtifact(bre.config.paths.artifacts, name); + const bytecode = artifact.bytecode; + return new bre.ethers.ContractFactory(artifact.abi, bytecode, signer); +} + +export async function getContractFactoryByAbiAndBytecode( + bre: BuidlerRuntimeEnvironment, + abi: any[], + bytecode: ethers.utils.Arrayish | string, + signer?: ethers.Signer +) { + if (signer === undefined) { + const signers = await bre.ethers.signers(); + signer = signers[0]; + } + + return new bre.ethers.ContractFactory(abi, bytecode, signer); +} + +export async function getContractAt( + bre: BuidlerRuntimeEnvironment, + nameOrAbi: string | any[], + address: string, + signer?: ethers.Signer +) { + if (typeof nameOrAbi === "string") { + const factory = await getContractFactoryByName(bre, nameOrAbi, signer); + return factory.attach(address); + } + + if (signer === undefined) { + const signers = await bre.ethers.signers(); + signer = signers[0]; + } + + return new bre.ethers.Contract(address, nameOrAbi, signer); +} diff --git a/packages/buidler-ethers/src/index.ts b/packages/buidler-ethers/src/index.ts index c8a3776ac5..a14ce87998 100644 --- a/packages/buidler-ethers/src/index.ts +++ b/packages/buidler-ethers/src/index.ts @@ -1,27 +1,30 @@ import { extendEnvironment } from "@nomiclabs/buidler/config"; -import { lazyObject, readArtifact } from "@nomiclabs/buidler/plugins"; +import { lazyObject } from "@nomiclabs/buidler/plugins"; import { BuidlerRuntimeEnvironment } from "@nomiclabs/buidler/types"; +import EthersT from "ethers"; + +import { getContractAt, getContractFactory, getSigners } from "./helpers"; export default function() { extendEnvironment((env: BuidlerRuntimeEnvironment) => { env.ethers = lazyObject(() => { const { EthersProviderWrapper } = require("./ethers-provider-wrapper"); + const { ethers } = require("ethers") as typeof EthersT; + return { + ...ethers, provider: new EthersProviderWrapper(env.network.provider), - getContract: async (name: string) => { - const { ethers } = await import("ethers"); - const artifact = await readArtifact(env.config.paths.artifacts, name); - const bytecode = artifact.bytecode; - const signers = await env.ethers.signers(); - return new ethers.ContractFactory(artifact.abi, bytecode, signers[0]); - }, - signers: async () => { - const accounts = await env.ethers.provider.listAccounts(); - return accounts.map((account: string) => - env.ethers.provider.getSigner(account) - ); - } + + getSigners: async () => getSigners(env), + // We cast to any here as we hit a limitation of Function#bind and + // overloads. See: https://github.com/microsoft/TypeScript/issues/28582 + getContractFactory: getContractFactory.bind(null, env) as any, + getContractAt: getContractAt.bind(null, env), + + // Deprecated: + getContract: (name: string) => getContractFactory(env, name), + signers: () => getSigners(env) }; }); }); diff --git a/packages/buidler-ethers/src/type-extensions.d.ts b/packages/buidler-ethers/src/type-extensions.d.ts index 8c95c383fd..b9d8841ef7 100644 --- a/packages/buidler-ethers/src/type-extensions.d.ts +++ b/packages/buidler-ethers/src/type-extensions.d.ts @@ -1,13 +1,48 @@ import "@nomiclabs/buidler/types"; -import { ContractFactory, Signer } from "ethers"; +import ethers from "ethers"; import { JsonRpcProvider } from "ethers/providers"; declare module "@nomiclabs/buidler/types" { + function getContractFactory( + name: string, + signer?: ethers.Signer + ): Promise; + function getContractFactory( + abi: any[], + bytecode: ethers.utils.Arrayish | string, + signer?: ethers.Signer + ): Promise; + interface BuidlerRuntimeEnvironment { ethers: { provider: JsonRpcProvider; - getContract: (name: string) => Promise; - signers: () => Promise; + + getContractFactory: typeof getContractFactory; + getContractAt: ( + nameOrAbi: string | any[], + address: string, + signer?: ethers.Signer + ) => Promise; + getSigners: () => Promise; + + // Deprecated + getContract: (name: string) => Promise; + signers: () => Promise; + + // Standard ethers properties + Contract: typeof ethers.Contract; + ContractFactory: typeof ethers.ContractFactory; + VoidSigner: typeof ethers.VoidSigner; + Signer: typeof ethers.Signer; + Wallet: typeof ethers.Wallet; + constants: typeof ethers.constants; + errors: typeof ethers.errors; + providers: typeof ethers.providers; + utils: typeof ethers.utils; + wordlists: typeof ethers.wordlists; + platform: typeof ethers.platform; + version: typeof ethers.version; + getDefaultProvider: typeof ethers.getDefaultProvider; }; } } diff --git a/packages/buidler-ethers/test/buidler-project/artifacts/Greeter.json b/packages/buidler-ethers/test/buidler-project/artifacts/Greeter.json index f11b7ca8d5..f292953092 100644 --- a/packages/buidler-ethers/test/buidler-project/artifacts/Greeter.json +++ b/packages/buidler-ethers/test/buidler-project/artifacts/Greeter.json @@ -2,18 +2,10 @@ "contractName": "Greeter", "abi": [ { - "constant": false, - "inputs": [ - { - "name": "_greeting", - "type": "string" - } - ], - "name": "setGreeting", - "outputs": [], + "inputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "function" + "type": "constructor" }, { "constant": true, @@ -21,6 +13,7 @@ "name": "greet", "outputs": [ { + "internalType": "string", "name": "", "type": "string" } @@ -30,14 +23,23 @@ "type": "function" }, { - "inputs": [], + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "name": "setGreeting", + "outputs": [], "payable": false, "stateMutability": "nonpayable", - "type": "constructor" + "type": "function" } ], - "bytecode": "0x608060405234801561001057600080fd5b506040518060400160405280600281526020017f48690000000000000000000000000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b610306806101166000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe610193565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061018f929190610235565b5050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561022b5780601f106102005761010080835404028352916020019161022b565b820191906000526020600020905b81548152906001019060200180831161020e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027657805160ff19168380011785556102a4565b828001600101855582156102a4579182015b828111156102a3578251825591602001919060010190610288565b5b5090506102b191906102b5565b5090565b6102d791905b808211156102d35760008160009055506001016102bb565b5090565b9056fea165627a7a7230582033dbec182f86089ed857160694b93fdaf8910ab7aebd1b722fc20fd6f028c1050029", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe610193565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061018f929190610235565b5050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561022b5780601f106102005761010080835404028352916020019161022b565b820191906000526020600020905b81548152906001019060200180831161020e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027657805160ff19168380011785556102a4565b828001600101855582156102a4579182015b828111156102a3578251825591602001919060010190610288565b5b5090506102b191906102b5565b5090565b6102d791905b808211156102d35760008160009055506001016102bb565b5090565b9056fea165627a7a7230582033dbec182f86089ed857160694b93fdaf8910ab7aebd1b722fc20fd6f028c1050029", + "bytecode": "0x608060405234801561001057600080fd5b506040518060400160405280600281526020017f48690000000000000000000000000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b61030f806101166000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe610193565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061018f929190610235565b5050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561022b5780601f106102005761010080835404028352916020019161022b565b820191906000526020600020905b81548152906001019060200180831161020e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027657805160ff19168380011785556102a4565b828001600101855582156102a4579182015b828111156102a3578251825591602001919060010190610288565b5b5090506102b191906102b5565b5090565b6102d791905b808211156102d35760008160009055506001016102bb565b5090565b9056fea265627a7a72315820512e3b2004c2b3e021e7b93b79e28e791a10b5ac6daf31b6003b7a77562ae99f64736f6c634300050f0032", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe610193565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061018f929190610235565b5050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561022b5780601f106102005761010080835404028352916020019161022b565b820191906000526020600020905b81548152906001019060200180831161020e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027657805160ff19168380011785556102a4565b828001600101855582156102a4579182015b828111156102a3578251825591602001919060010190610288565b5b5090506102b191906102b5565b5090565b6102d791905b808211156102d35760008160009055506001016102bb565b5090565b9056fea265627a7a72315820512e3b2004c2b3e021e7b93b79e28e791a10b5ac6daf31b6003b7a77562ae99f64736f6c634300050f0032", "linkReferences": {}, "deployedLinkReferences": {} } diff --git a/packages/buidler-ethers/test/buidler-project/artifacts/IGreeter.json b/packages/buidler-ethers/test/buidler-project/artifacts/IGreeter.json index de1f91670b..9b34335058 100644 --- a/packages/buidler-ethers/test/buidler-project/artifacts/IGreeter.json +++ b/packages/buidler-ethers/test/buidler-project/artifacts/IGreeter.json @@ -7,6 +7,7 @@ "name": "greet", "outputs": [ { + "internalType": "string", "name": "", "type": "string" } diff --git a/packages/buidler-ethers/test/buidler-project/buidler.config.js b/packages/buidler-ethers/test/buidler-project/buidler.config.js index 2ffa136996..cf41658335 100644 --- a/packages/buidler-ethers/test/buidler-project/buidler.config.js +++ b/packages/buidler-ethers/test/buidler-project/buidler.config.js @@ -1,12 +1,4 @@ const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); loadPluginFile(__dirname + "/../../src/index"); -module.exports = { - networks: { - localhost: { - accounts: [ - "0xa95f9e3e7ae4e4865c5968828fe7c03fffa8a9f3bb52d36d26243f4c868ee166" - ] - } - } -}; +module.exports = {}; diff --git a/packages/buidler-ethers/test/buidler-project/cache/last-solc-config.json b/packages/buidler-ethers/test/buidler-project/cache/last-solc-config.json new file mode 100644 index 0000000000..4da1022ac8 --- /dev/null +++ b/packages/buidler-ethers/test/buidler-project/cache/last-solc-config.json @@ -0,0 +1,10 @@ +{ + "solc": { + "version": "0.5.15", + "optimizer": { + "enabled": false, + "runs": 200 + } + }, + "buidlerVersion": "1.2.0-rc.1" +} \ No newline at end of file diff --git a/packages/buidler-ethers/test/buidler-project/cache/solc-input.json b/packages/buidler-ethers/test/buidler-project/cache/solc-input.json new file mode 100644 index 0000000000..3e70e15a44 --- /dev/null +++ b/packages/buidler-ethers/test/buidler-project/cache/solc-input.json @@ -0,0 +1,34 @@ +{ + "language": "Solidity", + "sources": { + "contracts/Greeter.sol": { + "content": "pragma solidity ^0.5.1;\n\ncontract Greeter {\n\n string greeting;\n\n constructor() public {\n greeting = \"Hi\";\n }\n\n function setGreeting(string memory _greeting) public {\n greeting = _greeting;\n }\n\n function greet() public view returns (string memory) {\n return greeting;\n }\n\n}\n" + }, + "contracts/IGreeter.sol": { + "content": "pragma solidity ^0.5.1;\n\ninterface IGreeter {\n function greet() external view returns (string memory);\n}" + } + }, + "settings": { + "metadata": { + "useLiteralContent": true + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers" + ], + "": [ + "id", + "ast" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/buidler-ethers/test/buidler-project/cache/solc-output.json b/packages/buidler-ethers/test/buidler-project/cache/solc-output.json new file mode 100644 index 0000000000..b7ac5e5145 --- /dev/null +++ b/packages/buidler-ethers/test/buidler-project/cache/solc-output.json @@ -0,0 +1,541 @@ +{ + "contracts": { + "contracts/Greeter.sol": { + "Greeter": { + "abi": [ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "greet", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "_greeting", + "type": "string" + } + ], + "name": "setGreeting", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b506040518060400160405280600281526020017f48690000000000000000000000000000000000000000000000000000000000008152506000908051906020019061005c929190610062565b50610107565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a357805160ff19168380011785556100d1565b828001600101855582156100d1579182015b828111156100d05782518255916020019190600101906100b5565b5b5090506100de91906100e2565b5090565b61010491905b808211156101005760008160009055506001016100e8565b5090565b90565b61030f806101166000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe610193565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061018f929190610235565b5050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561022b5780601f106102005761010080835404028352916020019161022b565b820191906000526020600020905b81548152906001019060200180831161020e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027657805160ff19168380011785556102a4565b828001600101855582156102a4579182015b828111156102a3578251825591602001919060010190610288565b5b5090506102b191906102b5565b5090565b6102d791905b808211156102d35760008160009055506001016102bb565b5090565b9056fea265627a7a72315820512e3b2004c2b3e021e7b93b79e28e791a10b5ac6daf31b6003b7a77562ae99f64736f6c634300050f0032", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD DUP1 PUSH1 0x40 ADD PUSH1 0x40 MSTORE DUP1 PUSH1 0x2 DUP2 MSTORE PUSH1 0x20 ADD PUSH32 0x4869000000000000000000000000000000000000000000000000000000000000 DUP2 MSTORE POP PUSH1 0x0 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0x5C SWAP3 SWAP2 SWAP1 PUSH2 0x62 JUMP JUMPDEST POP PUSH2 0x107 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0xA3 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0xD1 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0xD1 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0xD0 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0xB5 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0xDE SWAP2 SWAP1 PUSH2 0xE2 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH2 0x104 SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x100 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0xE8 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST SWAP1 JUMP JUMPDEST PUSH2 0x30F DUP1 PUSH2 0x116 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x36 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xA4136862 EQ PUSH2 0x3B JUMPI DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0xF6 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xF4 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x51 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x6E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x80 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0xA2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x179 JUMP JUMPDEST STOP JUMPDEST PUSH2 0xFE PUSH2 0x193 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x13E JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x123 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x16B JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST DUP1 PUSH1 0x0 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0x18F SWAP3 SWAP2 SWAP1 PUSH2 0x235 JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x0 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 ISZERO PUSH2 0x22B JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x200 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x22B JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x20E JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0x276 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x2A4 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x2A4 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x2A3 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x288 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0x2B1 SWAP2 SWAP1 PUSH2 0x2B5 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH2 0x2D7 SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x2D3 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0x2BB JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST SWAP1 JUMP INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 MLOAD 0x2E EXTCODESIZE KECCAK256 DIV 0xC2 0xB3 0xE0 0x21 0xE7 0xB9 EXTCODESIZE PUSH26 0xE28E791A10B5AC6DAF31B6003B7A77562AE99F64736F6C634300 SDIV 0xF STOP ORIGIN ", + "sourceMap": "25:289:0:-;;;71:53;8:9:-1;5:2;;;30:1;27;20:12;5:2;71:53:0;102:15;;;;;;;;;;;;;;;;;:8;:15;;;;;;;;;;;;:::i;:::-;;25:289;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe610193565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b806000908051906020019061018f929190610235565b5050565b606060008054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561022b5780601f106102005761010080835404028352916020019161022b565b820191906000526020600020905b81548152906001019060200180831161020e57829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061027657805160ff19168380011785556102a4565b828001600101855582156102a4579182015b828111156102a3578251825591602001919060010190610288565b5b5090506102b191906102b5565b5090565b6102d791905b808211156102d35760008160009055506001016102bb565b5090565b9056fea265627a7a72315820512e3b2004c2b3e021e7b93b79e28e791a10b5ac6daf31b6003b7a77562ae99f64736f6c634300050f0032", + "opcodes": "PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x36 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xA4136862 EQ PUSH2 0x3B JUMPI DUP1 PUSH4 0xCFAE3217 EQ PUSH2 0xF6 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xF4 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x51 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x6E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x80 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0xA2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x179 JUMP JUMPDEST STOP JUMPDEST PUSH2 0xFE PUSH2 0x193 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x13E JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x123 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x16B JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST DUP1 PUSH1 0x0 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0x18F SWAP3 SWAP2 SWAP1 PUSH2 0x235 JUMP JUMPDEST POP POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x0 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 ISZERO PUSH2 0x22B JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x200 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x22B JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x20E JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0x276 JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x2A4 JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x2A4 JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x2A3 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x288 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0x2B1 SWAP2 SWAP1 PUSH2 0x2B5 JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH2 0x2D7 SWAP2 SWAP1 JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x2D3 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0x2BB JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST SWAP1 JUMP INVALID LOG2 PUSH6 0x627A7A723158 KECCAK256 MLOAD 0x2E EXTCODESIZE KECCAK256 DIV 0xC2 0xB3 0xE0 0x21 0xE7 0xB9 EXTCODESIZE PUSH26 0xE28E791A10B5AC6DAF31B6003B7A77562AE99F64736F6C634300 SDIV 0xF STOP ORIGIN ", + "sourceMap": "25:289:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;25:289:0;;;;;;;;;;;;;;;;;;;;;;;;130:90;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;130:90:0;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;130:90:0;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;130:90:0;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;130:90:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;130:90:0;;;;;;;;;;;;;;;:::i;:::-;;226:85;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;226:85:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;130:90;204:9;193:8;:20;;;;;;;;;;;;:::i;:::-;;130:90;:::o;226:85::-;264:13;296:8;289:15;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;226:85;:::o;25:289::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o" + }, + "methodIdentifiers": { + "greet()": "cfae3217", + "setGreeting(string)": "a4136862" + } + } + } + }, + "contracts/IGreeter.sol": { + "IGreeter": { + "abi": [ + { + "constant": true, + "inputs": [], + "name": "greet", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "evm": { + "bytecode": { + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "deployedBytecode": { + "linkReferences": {}, + "object": "", + "opcodes": "", + "sourceMap": "" + }, + "methodIdentifiers": { + "greet()": "cfae3217" + } + } + } + } + }, + "sources": { + "contracts/Greeter.sol": { + "ast": { + "absolutePath": "contracts/Greeter.sol", + "exportedSymbols": { + "Greeter": [ + 30 + ] + }, + "id": 31, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + "^", + "0.5", + ".1" + ], + "nodeType": "PragmaDirective", + "src": "0:23:0" + }, + { + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 30, + "linearizedBaseContracts": [ + 30 + ], + "name": "Greeter", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": false, + "id": 3, + "name": "greeting", + "nodeType": "VariableDeclaration", + "scope": 30, + "src": "49:15:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string" + }, + "typeName": { + "id": 2, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "49:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + }, + { + "body": { + "id": 10, + "nodeType": "Block", + "src": "92:32:0", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 8, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 6, + "name": "greeting", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3, + "src": "102:8:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "hexValue": "4869", + "id": 7, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "113:4:0", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_c3fea6c221b02b78eb2022dda7219a2f77c4b59cd7768ba47fd888f32dfd4dbf", + "typeString": "literal_string \"Hi\"" + }, + "value": "Hi" + }, + "src": "102:15:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "id": 9, + "nodeType": "ExpressionStatement", + "src": "102:15:0" + } + ] + }, + "documentation": null, + "id": 11, + "implemented": true, + "kind": "constructor", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 4, + "nodeType": "ParameterList", + "parameters": [], + "src": "82:2:0" + }, + "returnParameters": { + "id": 5, + "nodeType": "ParameterList", + "parameters": [], + "src": "92:0:0" + }, + "scope": 30, + "src": "71:53:0", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 20, + "nodeType": "Block", + "src": "183:37:0", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 18, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 16, + "name": "greeting", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3, + "src": "193:8:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 17, + "name": "_greeting", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 13, + "src": "204:9:0", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + }, + "src": "193:20:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "id": 19, + "nodeType": "ExpressionStatement", + "src": "193:20:0" + } + ] + }, + "documentation": null, + "id": 21, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setGreeting", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 14, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 13, + "name": "_greeting", + "nodeType": "VariableDeclaration", + "scope": 21, + "src": "151:23:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 12, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "151:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "150:25:0" + }, + "returnParameters": { + "id": 15, + "nodeType": "ParameterList", + "parameters": [], + "src": "183:0:0" + }, + "scope": 30, + "src": "130:90:0", + "stateMutability": "nonpayable", + "superFunction": null, + "visibility": "public" + }, + { + "body": { + "id": 28, + "nodeType": "Block", + "src": "279:32:0", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 26, + "name": "greeting", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3, + "src": "296:8:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage", + "typeString": "string storage ref" + } + }, + "functionReturnParameters": 25, + "id": 27, + "nodeType": "Return", + "src": "289:15:0" + } + ] + }, + "documentation": null, + "id": 29, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "greet", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 22, + "nodeType": "ParameterList", + "parameters": [], + "src": "240:2:0" + }, + "returnParameters": { + "id": 25, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 24, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 29, + "src": "264:13:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 23, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "264:6:0", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "263:15:0" + }, + "scope": 30, + "src": "226:85:0", + "stateMutability": "view", + "superFunction": null, + "visibility": "public" + } + ], + "scope": 31, + "src": "25:289:0" + } + ], + "src": "0:315:0" + }, + "id": 0 + }, + "contracts/IGreeter.sol": { + "ast": { + "absolutePath": "contracts/IGreeter.sol", + "exportedSymbols": { + "IGreeter": [ + 38 + ] + }, + "id": 39, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 32, + "literals": [ + "solidity", + "^", + "0.5", + ".1" + ], + "nodeType": "PragmaDirective", + "src": "0:23:1" + }, + { + "baseContracts": [], + "contractDependencies": [], + "contractKind": "interface", + "documentation": null, + "fullyImplemented": false, + "id": 38, + "linearizedBaseContracts": [ + 38 + ], + "name": "IGreeter", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": null, + "documentation": null, + "id": 37, + "implemented": false, + "kind": "function", + "modifiers": [], + "name": "greet", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 33, + "nodeType": "ParameterList", + "parameters": [], + "src": "64:2:1" + }, + "returnParameters": { + "id": 36, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 35, + "name": "", + "nodeType": "VariableDeclaration", + "scope": 37, + "src": "90:13:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 34, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "90:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "89:15:1" + }, + "scope": 38, + "src": "50:55:1", + "stateMutability": "view", + "superFunction": null, + "visibility": "external" + } + ], + "scope": 39, + "src": "25:82:1" + } + ], + "src": "0:107:1" + }, + "id": 1 + } + } +} \ No newline at end of file diff --git a/packages/buidler-ethers/test/helpers.ts b/packages/buidler-ethers/test/helpers.ts index 77cb2c3d54..96127318ab 100644 --- a/packages/buidler-ethers/test/helpers.ts +++ b/packages/buidler-ethers/test/helpers.ts @@ -16,6 +16,7 @@ export function useEnvironment(projectPath: string) { }); afterEach("Resetting buidler", function() { + delete process.env.BUIDLER_NETWORK; resetBuidlerContext(); }); } diff --git a/packages/buidler-ethers/test/index.ts b/packages/buidler-ethers/test/index.ts index 646d9c53f0..8cf6ac43f9 100644 --- a/packages/buidler-ethers/test/index.ts +++ b/packages/buidler-ethers/test/index.ts @@ -1,46 +1,305 @@ +import { readArtifact } from "@nomiclabs/buidler/plugins"; import { assert } from "chai"; +import { ethers } from "ethers"; import path from "path"; +import { Artifact } from "../../buidler-core/src/types"; + import { useEnvironment } from "./helpers"; describe("Ethers plugin", function() { useEnvironment(path.join(__dirname, "buidler-project")); - it("should extend buidler runtime environment", function() { - assert.isDefined(this.env.ethers); - assert.containsAllKeys(this.env.ethers, [ - "provider", - "getContract", - "signers" - ]); + describe("BRE extensions", function() { + it("should extend buidler runtime environment", function() { + assert.isDefined(this.env.ethers); + assert.containsAllKeys(this.env.ethers, [ + "provider", + "getContract", + "signers", + "getSigners", + "getContractFactory", + "getContractAt", + ...Object.keys(ethers) + ]); + }); }); - it("the provider should handle requests", async function() { - const accounts = await this.env.ethers.provider.send("eth_accounts", []); - assert.deepEqual(accounts, ["0xf7abeea1b1b97ef714bc9a118b0f095ec54f8221"]); + describe("Provider", function() { + it("the provider should handle requests", async function() { + const accounts = await this.env.ethers.provider.send("eth_accounts", []); + assert.equal(accounts[0], "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1"); + }); }); - it("should return a contract factory", async function() { - // It's already compiled in artifacts/ - const contract = await this.env.ethers.getContract("Greeter"); + describe("Signers and contracts helpers", function() { + let signers: ethers.Signer[]; + let greeterArtifact: Artifact; + let iGreeterArtifact: Artifact; - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()" - ]); - }); + beforeEach(async function() { + signers = await this.env.ethers.getSigners(); + greeterArtifact = await readArtifact( + this.env.config.paths.artifacts, + "Greeter" + ); - it("should return a contract factory for an interface", async function() { - const contract = await this.env.ethers.getContract("IGreeter"); - assert.equal(contract.bytecode, "0x"); - assert.containsAllKeys(contract.interface.functions, ["greet()"]); - }); + iGreeterArtifact = await readArtifact( + this.env.config.paths.artifacts, + "IGreeter" + ); + }); + + describe("getSigners", function() { + it("should return the signers", async function() { + const sigs = await this.env.ethers.getSigners(); + assert.equal( + await sigs[0].getAddress(), + "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1" + ); + }); + }); + + describe("getContractFactory", function() { + describe("By name", function() { + it("should return a contract factory", async function() { + // It's already compiled in artifacts/ + const contract = await this.env.ethers.getContractFactory("Greeter"); + + assert.containsAllKeys(contract.interface.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + it("should return a contract factory for an interface", async function() { + const contract = await this.env.ethers.getContractFactory("IGreeter"); + assert.equal(contract.bytecode, "0x"); + assert.containsAllKeys(contract.interface.functions, ["greet()"]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + describe("with custom signer", function() { + it("should return a contract factory connected to the custom signer", async function() { + // It's already compiled in artifacts/ + const contract = await this.env.ethers.getContractFactory( + "Greeter", + signers[1] + ); + + assert.containsAllKeys(contract.interface.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[1].getAddress() + ); + }); + }); + }); + + describe("by abi and bytecode", function() { + describe("By name", function() { + it("should return a contract factory", async function() { + // It's already compiled in artifacts/ + const contract = await this.env.ethers.getContractFactory( + greeterArtifact.abi, + greeterArtifact.bytecode + ); + + assert.containsAllKeys(contract.interface.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + it("should return a contract factory for an interface", async function() { + const contract = await this.env.ethers.getContractFactory( + iGreeterArtifact.abi, + iGreeterArtifact.bytecode + ); + assert.equal(contract.bytecode, "0x"); + assert.containsAllKeys(contract.interface.functions, ["greet()"]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + describe("with custom signer", function() { + it("should return a contract factory connected to the custom signer", async function() { + // It's already compiled in artifacts/ + const contract = await this.env.ethers.getContractFactory( + greeterArtifact.abi, + greeterArtifact.bytecode, + signers[1] + ); + + assert.containsAllKeys(contract.interface.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[1].getAddress() + ); + }); + }); + }); + }); + }); + + describe("getContractAt", function() { + let deployedGreeter: ethers.Contract; + + beforeEach(async function() { + const Greeter = await this.env.ethers.getContractFactory("Greeter"); + deployedGreeter = await Greeter.deploy(); + }); + + describe("by name and address", function() { + it("Should return an instance of a contract", async function() { + const contract = await this.env.ethers.getContractAt( + "Greeter", + deployedGreeter.address + ); + + assert.containsAllKeys(contract.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + it("Should return an instance of an interface", async function() { + const contract = await this.env.ethers.getContractAt( + "IGreeter", + deployedGreeter.address + ); + + assert.containsAllKeys(contract.functions, ["greet()"]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + describe("with custom signer", function() { + it("Should return an instance of a contract associated to a custom signer", async function() { + const contract = await this.env.ethers.getContractAt( + "Greeter", + deployedGreeter.address, + signers[1] + ); + + assert.equal( + await contract.signer.getAddress(), + await signers[1].getAddress() + ); + }); + }); + }); + + describe("by abi and address", function() { + it("Should return an instance of a contract", async function() { + const contract = await this.env.ethers.getContractAt( + greeterArtifact.abi, + deployedGreeter.address + ); + + assert.containsAllKeys(contract.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + it("Should return an instance of an interface", async function() { + const contract = await this.env.ethers.getContractAt( + iGreeterArtifact.abi, + deployedGreeter.address + ); + + assert.containsAllKeys(contract.functions, ["greet()"]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + describe("with custom signer", function() { + it("Should return an instance of a contract associated to a custom signer", async function() { + const contract = await this.env.ethers.getContractAt( + greeterArtifact.abi, + deployedGreeter.address, + signers[1] + ); + + assert.equal( + await contract.signer.getAddress(), + await signers[1].getAddress() + ); + }); + }); + }); + }); + + describe("Deprecated functions", function() { + describe("signers", function() { + it("should return the signers", async function() { + const sigs = await this.env.ethers.signers(); + assert.equal( + await sigs[0].getAddress(), + "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1" + ); + }); + }); + + describe("getContract", function() { + it("should return a contract factory", async function() { + // It's already compiled in artifacts/ + const contract = await this.env.ethers.getContract("Greeter"); + + assert.containsAllKeys(contract.interface.functions, [ + "setGreeting(string)", + "greet()" + ]); + }); - it("should return the signers", async function() { - const signers = await this.env.ethers.signers(); - assert.equal( - await signers[0].getAddress(), - "0xF7ABeeA1B1B97Ef714bc9A118B0f095EC54f8221" - ); + it("should return a contract factory for an interface", async function() { + const contract = await this.env.ethers.getContract("IGreeter"); + assert.equal(contract.bytecode, "0x"); + assert.containsAllKeys(contract.interface.functions, ["greet()"]); + }); + }); + }); }); }); diff --git a/packages/buidler-waffle/README.md b/packages/buidler-waffle/README.md index 2fe04190e6..a40198896e 100644 --- a/packages/buidler-waffle/README.md +++ b/packages/buidler-waffle/README.md @@ -33,8 +33,6 @@ mock provider. ```ts waffle: { provider: JsonRpcProvider; - getContract: (name: string) => Promise; - signers: () => Promise; } ``` From 0a203e5c032bd8860bc06dc0d64ffc76faf9afe2 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 12:21:09 -0300 Subject: [PATCH 65/99] Improve buidler-ethers tests (#455) --- packages/buidler-ethers/test/index.ts | 122 +++++++++++++++++--------- 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/packages/buidler-ethers/test/index.ts b/packages/buidler-ethers/test/index.ts index 8cf6ac43f9..cba157ba63 100644 --- a/packages/buidler-ethers/test/index.ts +++ b/packages/buidler-ethers/test/index.ts @@ -1,10 +1,9 @@ import { readArtifact } from "@nomiclabs/buidler/plugins"; +import { Artifact } from "@nomiclabs/buidler/types"; import { assert } from "chai"; import { ethers } from "ethers"; import path from "path"; -import { Artifact } from "../../buidler-core/src/types"; - import { useEnvironment } from "./helpers"; describe("Ethers plugin", function() { @@ -88,6 +87,15 @@ describe("Ethers plugin", function() { ); }); + it("Should be able to send txs and make calls", async function() { + const Greeter = await this.env.ethers.getContractFactory("Greeter"); + const greeter = await Greeter.deploy(); + + assert.equal(await greeter.functions.greet(), "Hi"); + await greeter.functions.setGreeting("Hola"); + assert.equal(await greeter.functions.greet(), "Hola"); + }); + describe("with custom signer", function() { it("should return a contract factory connected to the custom signer", async function() { // It's already compiled in artifacts/ @@ -110,12 +118,57 @@ describe("Ethers plugin", function() { }); describe("by abi and bytecode", function() { - describe("By name", function() { - it("should return a contract factory", async function() { + it("should return a contract factory", async function() { + // It's already compiled in artifacts/ + const contract = await this.env.ethers.getContractFactory( + greeterArtifact.abi, + greeterArtifact.bytecode + ); + + assert.containsAllKeys(contract.interface.functions, [ + "setGreeting(string)", + "greet()" + ]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + it("should return a contract factory for an interface", async function() { + const contract = await this.env.ethers.getContractFactory( + iGreeterArtifact.abi, + iGreeterArtifact.bytecode + ); + assert.equal(contract.bytecode, "0x"); + assert.containsAllKeys(contract.interface.functions, ["greet()"]); + + assert.equal( + await contract.signer.getAddress(), + await signers[0].getAddress() + ); + }); + + it("Should be able to send txs and make calls", async function() { + const Greeter = await this.env.ethers.getContractFactory( + greeterArtifact.abi, + greeterArtifact.bytecode + ); + const greeter = await Greeter.deploy(); + + assert.equal(await greeter.functions.greet(), "Hi"); + await greeter.functions.setGreeting("Hola"); + assert.equal(await greeter.functions.greet(), "Hola"); + }); + + describe("with custom signer", function() { + it("should return a contract factory connected to the custom signer", async function() { // It's already compiled in artifacts/ const contract = await this.env.ethers.getContractFactory( greeterArtifact.abi, - greeterArtifact.bytecode + greeterArtifact.bytecode, + signers[1] ); assert.containsAllKeys(contract.interface.functions, [ @@ -125,44 +178,9 @@ describe("Ethers plugin", function() { assert.equal( await contract.signer.getAddress(), - await signers[0].getAddress() - ); - }); - - it("should return a contract factory for an interface", async function() { - const contract = await this.env.ethers.getContractFactory( - iGreeterArtifact.abi, - iGreeterArtifact.bytecode - ); - assert.equal(contract.bytecode, "0x"); - assert.containsAllKeys(contract.interface.functions, ["greet()"]); - - assert.equal( - await contract.signer.getAddress(), - await signers[0].getAddress() + await signers[1].getAddress() ); }); - - describe("with custom signer", function() { - it("should return a contract factory connected to the custom signer", async function() { - // It's already compiled in artifacts/ - const contract = await this.env.ethers.getContractFactory( - greeterArtifact.abi, - greeterArtifact.bytecode, - signers[1] - ); - - assert.containsAllKeys(contract.interface.functions, [ - "setGreeting(string)", - "greet()" - ]); - - assert.equal( - await contract.signer.getAddress(), - await signers[1].getAddress() - ); - }); - }); }); }); }); @@ -207,6 +225,17 @@ describe("Ethers plugin", function() { ); }); + it("Should be able to send txs and make calls", async function() { + const greeter = await this.env.ethers.getContractAt( + "Greeter", + deployedGreeter.address + ); + + assert.equal(await greeter.functions.greet(), "Hi"); + await greeter.functions.setGreeting("Hola"); + assert.equal(await greeter.functions.greet(), "Hola"); + }); + describe("with custom signer", function() { it("Should return an instance of a contract associated to a custom signer", async function() { const contract = await this.env.ethers.getContractAt( @@ -255,6 +284,17 @@ describe("Ethers plugin", function() { ); }); + it("Should be able to send txs and make calls", async function() { + const greeter = await this.env.ethers.getContractAt( + greeterArtifact.abi, + deployedGreeter.address + ); + + assert.equal(await greeter.functions.greet(), "Hi"); + await greeter.functions.setGreeting("Hola"); + assert.equal(await greeter.functions.greet(), "Hola"); + }); + describe("with custom signer", function() { it("Should return an instance of a contract associated to a custom signer", async function() { const contract = await this.env.ethers.getContractAt( From c5c077226f8a27fb07293b60636b103bcbbef6bf Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 13:50:20 -0300 Subject: [PATCH 66/99] Automatically initialize the Waffle Chai matchers (#456) --- packages/buidler-waffle/README.md | 8 ++++++-- packages/buidler-waffle/src/index.ts | 17 +++++++++++++++++ .../test/buidler-project/test/tests.js | 12 ++++++++++++ packages/buidler-waffle/test/index.ts | 10 ++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 packages/buidler-waffle/test/buidler-project/test/tests.js diff --git a/packages/buidler-waffle/README.md b/packages/buidler-waffle/README.md index a40198896e..bf076df3cd 100644 --- a/packages/buidler-waffle/README.md +++ b/packages/buidler-waffle/README.md @@ -7,7 +7,11 @@ ## What -You can use this plugin to build smart contract tests using Waffle in Buidler, taking advantage of both. +You can use this plugin to build smart contract tests using Waffle in Buidler, +taking advantage of both. + +This plugin adds a Waffle-compatible provider to the Buidler Runtime Environment, +and automatically initializes the [Waffle Chai matchers](https://ethereum-waffle.readthedocs.io/en/latest/matchers.html). ## Installation @@ -45,7 +49,7 @@ Once installed, you can build your tests just like in Waffle. The only differenc instead of `createMockProvider()`. Note that by default, Buidler save its compilation output into `artifacts/` instead of `build/`. You can either use -that directory in your tests, or [customize your Buidler config](https://buidler.dev/config/#path-configuration). +that directory in your tests, or [customize your Buidler config](https://buidler.dev/config/#path-configuration). ## TypeScript support diff --git a/packages/buidler-waffle/src/index.ts b/packages/buidler-waffle/src/index.ts index fa62ecac8e..caee7be85f 100644 --- a/packages/buidler-waffle/src/index.ts +++ b/packages/buidler-waffle/src/index.ts @@ -1,7 +1,24 @@ import { extendEnvironment, usePlugin } from "@nomiclabs/buidler/config"; import { lazyObject } from "@nomiclabs/buidler/plugins"; +function initializeWaffleMatchers() { + const wafflePath = require.resolve("ethereum-waffle"); + const waffleChaiPath = require.resolve("@ethereum-waffle/chai", { + paths: [wafflePath] + }); + const { waffleChai } = require(waffleChaiPath); + + try { + const chai = require("chai"); + chai.use(waffleChai); + } catch (error) { + // If chai isn't installed we just don't initialize the matchers + } +} + export default function() { + initializeWaffleMatchers(); + extendEnvironment(bre => { // We can't actually implement a MockProvider because of its private // properties, so we cast it here 😢 diff --git a/packages/buidler-waffle/test/buidler-project/test/tests.js b/packages/buidler-waffle/test/buidler-project/test/tests.js new file mode 100644 index 0000000000..53d768a79b --- /dev/null +++ b/packages/buidler-waffle/test/buidler-project/test/tests.js @@ -0,0 +1,12 @@ +describe("Internal test suite of buidler-waffle's test project", function() { + it("Should have waffle assertions loaded", function() { + const chai = require("chai"); + if (!("revertedWith" in chai.Assertion.prototype)) { + throw new Error("Failed to load it"); + } + }); + + it("Should fail", function() { + throw new Error("Failed on purpose"); + }); +}); diff --git a/packages/buidler-waffle/test/index.ts b/packages/buidler-waffle/test/index.ts index 891809d96f..d4d2dbe070 100644 --- a/packages/buidler-waffle/test/index.ts +++ b/packages/buidler-waffle/test/index.ts @@ -89,4 +89,14 @@ describe("Waffle plugin plugin", function() { }); }); }); + + describe("Test environment initialization", function() { + useEnvironment(path.join(__dirname, "buidler-project")); + + it("Should load the Waffle chai matchers", async function() { + await this.env.run("test", { testFiles: [] }); + assert.equal(process.exitCode, 1); + process.exitCode = 0; + }); + }); }); From e5fe13304b4f3f7a6187f3a37a083cc06b0e8c6b Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 17:54:27 -0300 Subject: [PATCH 67/99] Fix bug in the compilation cache (#457) --- .../src/builtin-tasks/utils/cache.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/buidler-core/src/builtin-tasks/utils/cache.ts b/packages/buidler-core/src/builtin-tasks/utils/cache.ts index 5dfe1857ea..3d068c5298 100644 --- a/packages/buidler-core/src/builtin-tasks/utils/cache.ts +++ b/packages/buidler-core/src/builtin-tasks/utils/cache.ts @@ -42,6 +42,14 @@ export async function areArtifactsCached( return false; } + const lastConfigTimestamp = await getLastUsedConfigTimestamp(paths.cache); + if ( + lastConfigTimestamp !== undefined && + lastConfigTimestamp > maxSourceDate + ) { + return true; + } + return maxSourceDate < minArtifactDate; } @@ -88,6 +96,18 @@ async function getLastUsedConfig( return module.require(pathToConfig); } +async function getLastUsedConfigTimestamp( + cachePath: string +): Promise { + const pathToConfig = getPathToCachedLastConfigPath(cachePath); + + if (!(await fsExtra.pathExists(pathToConfig))) { + return undefined; + } + + return (await fsExtra.stat(pathToConfig)).ctimeMs; +} + export async function cacheBuidlerConfig( paths: ProjectPaths, config: SolcConfig From c561f88aae9be6bf00af83cc04392322a167faf3 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Sun, 23 Feb 2020 20:29:55 -0300 Subject: [PATCH 68/99] Buidler waffle fixes (#458) * Simplify buidler-waffle installation * Fix chai matchers loading when using the linked version of the plugin * Simplify package.json * Improve readme --- docs/guides/waffle-testing.md | 6 ------ packages/buidler-waffle/README.md | 5 ++++- packages/buidler-waffle/package.json | 4 ++-- packages/buidler-waffle/src/index.ts | 20 ++++++++++++++++---- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/guides/waffle-testing.md b/docs/guides/waffle-testing.md index 8ab417c216..c80216bc78 100644 --- a/docs/guides/waffle-testing.md +++ b/docs/guides/waffle-testing.md @@ -10,12 +10,6 @@ Done? Great. Let's now install `ethers.js`, `Waffle` and their Buidler plugins, npm install --save-dev @nomiclabs/buidler-ethers ethers @nomiclabs/buidler-waffle ethereum-waffle ``` -Waffle also depends on [`sinon-chai`](https://www.chaijs.com/plugins/sinon-chai/), so let's install its typings: - -```sh -npm install --save-dev @types/sinon-chai -``` - Add the `buidler-ethers` and `buidler-waffle` type extensions to your `tsconfig.json` that you should've created following the TypeScript guide: ```json{8,13,14} diff --git a/packages/buidler-waffle/README.md b/packages/buidler-waffle/README.md index bf076df3cd..057c1ecd3e 100644 --- a/packages/buidler-waffle/README.md +++ b/packages/buidler-waffle/README.md @@ -59,7 +59,10 @@ This plugin supports TypeScript by following these steps: 1.1. `"node_modules/@nomiclabs/buidler-ethers/src/type-extensions.d.ts"` 1.2. `"node_modules/@nomiclabs/buidler-waffle/src/type-extensions.d.ts"` -2. Install this packages: `npm install --save-dev @types/mocha @types/chai @types/sinon-chai` +2. Install this packages: `npm install --save-dev @types/mocha @types/chai` We also recommend enabling `resolveJsonModule` in your `tsconfig.json`, as it's common to import JSON files directly when using Waffle. + +There's no need to import the Waffle's `solidity` Chai matchers. They are +automatically imported and initialized by this plugin, including its types. diff --git a/packages/buidler-waffle/package.json b/packages/buidler-waffle/package.json index a674042129..3a72aa4ab5 100644 --- a/packages/buidler-waffle/package.json +++ b/packages/buidler-waffle/package.json @@ -36,7 +36,6 @@ "@nomiclabs/buidler-ethers": "^1.1.2", "@types/chai": "^4.2.0", "@types/fs-extra": "^5.1.0", - "@types/sinon-chai": "^3.2.3", "chai": "^4.2.0", "ethereum-waffle": "^2.3.0", "ethers": "^4.0.27" @@ -48,6 +47,7 @@ "ethereum-waffle": "^2.3.0" }, "dependencies": { - "@types/web3": "1.0.19" + "@types/web3": "1.0.19", + "@types/sinon-chai": "^3.2.3" } } diff --git a/packages/buidler-waffle/src/index.ts b/packages/buidler-waffle/src/index.ts index caee7be85f..022c84ffba 100644 --- a/packages/buidler-waffle/src/index.ts +++ b/packages/buidler-waffle/src/index.ts @@ -1,7 +1,8 @@ import { extendEnvironment, usePlugin } from "@nomiclabs/buidler/config"; import { lazyObject } from "@nomiclabs/buidler/plugins"; +import path from "path"; -function initializeWaffleMatchers() { +function initializeWaffleMatchers(projectRoot: string) { const wafflePath = require.resolve("ethereum-waffle"); const waffleChaiPath = require.resolve("@ethereum-waffle/chai", { paths: [wafflePath] @@ -9,7 +10,18 @@ function initializeWaffleMatchers() { const { waffleChai } = require(waffleChaiPath); try { - const chai = require("chai"); + let chaiPath = require.resolve("chai"); + + // When using this plugin linked from sources, we'd end up with the chai + // used to test it, not the project's version of chai, so we correct it. + if (chaiPath.startsWith(path.join(__dirname, "..", "node_modules"))) { + chaiPath = require.resolve("chai", { + paths: [projectRoot] + }); + } + + const chai = require(chaiPath); + chai.use(waffleChai); } catch (error) { // If chai isn't installed we just don't initialize the matchers @@ -17,8 +29,6 @@ function initializeWaffleMatchers() { } export default function() { - initializeWaffleMatchers(); - extendEnvironment(bre => { // We can't actually implement a MockProvider because of its private // properties, so we cast it here 😢 @@ -31,6 +41,8 @@ export default function() { provider: new WaffleMockProviderAdapter(bre.network) as any }; }); + + initializeWaffleMatchers(bre.config.paths.root); }); usePlugin("@nomiclabs/buidler-ethers"); From 540fcde3ba23de8de2163bc5b6f2b1826828ab2c Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Mon, 24 Feb 2020 11:52:14 -0300 Subject: [PATCH 69/99] Fix node task default network check (#460) --- packages/buidler-core/src/builtin-tasks/node.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/buidler-core/src/builtin-tasks/node.ts b/packages/buidler-core/src/builtin-tasks/node.ts index 80c9912f13..552e9eb2bf 100644 --- a/packages/buidler-core/src/builtin-tasks/node.ts +++ b/packages/buidler-core/src/builtin-tasks/node.ts @@ -80,7 +80,12 @@ export default function() { async ({ hostname, port }, { network, buidlerArguments, config }) => { if ( network.name !== BUIDLEREVM_NETWORK_NAME && - buidlerArguments.network !== undefined + // We normally set the default network as buidlerArguments.network, + // so this check isn't enough, and we add the next one. This has the + // effect of `--network ` being a false negative, but + // not a big deal. + buidlerArguments.network !== undefined && + buidlerArguments.network !== config.defaultNetwork ) { throw new BuidlerError( ERRORS.BUILTIN_TASKS.JSONRPC_UNSUPPORTED_NETWORK From ad50571339f599bdaf5ba5020968874095415fa4 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Mon, 24 Feb 2020 11:54:02 -0300 Subject: [PATCH 70/99] Include typescript info in vscode guide (#459) --- docs/guides/vscode-tests.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/guides/vscode-tests.md b/docs/guides/vscode-tests.md index ee10e697bb..142b68590a 100644 --- a/docs/guides/vscode-tests.md +++ b/docs/guides/vscode-tests.md @@ -23,4 +23,12 @@ npm install --save-dev mocha Now, you can set a shortcut for this VS Code command `test-explorer.run-test-at-cursor`, and you will be to run the test you are currently editing with it. +## Running TypeScript test + +Running tests written in TypeScript from [Visual Studio Code](https://code.visualstudio.com) requires two extra steps. + +First, you have to add this property to your `.mocharc.json`: `"extension": ["ts"]`. + +Then, you have to set the vscode option `"mochaExplorer.files"` to `"test/**/*.{j,t}s"`. + For any help or feedback you may have, you can find us in the [Buidler Support Telegram group](http://t.me/BuidlerSupport). From 5ce0665473af18902fa82033cc66188e03f13f90 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Mon, 24 Feb 2020 11:54:19 -0300 Subject: [PATCH 71/99] Sample project creation improvements (#461) * Donn't automatically install dependencies on win * Improve example commands on project creation * Linter fixes --- .../buidler-core/src/internal/cli/project-creation.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/buidler-core/src/internal/cli/project-creation.ts b/packages/buidler-core/src/internal/cli/project-creation.ts index c6651808ca..16a79d0864 100644 --- a/packages/buidler-core/src/internal/cli/project-creation.ts +++ b/packages/buidler-core/src/internal/cli/project-creation.ts @@ -1,5 +1,6 @@ import chalk from "chalk"; import fsExtra from "fs-extra"; +import os from "os"; import path from "path"; import { BUIDLER_NAME } from "../constants"; @@ -111,7 +112,8 @@ function printSuggestedCommands() { console.log(` ${npx}buidler accounts`); console.log(` ${npx}buidler compile`); console.log(` ${npx}buidler test`); - console.log(` ${npx}node scripts/sample-script.js`); + console.log(` ${npx}buidler node`); + console.log(` node scripts/sample-script.js`); console.log(` ${npx}buidler help`); } @@ -312,7 +314,9 @@ async function canInstallTrufflePlugin() { return ( (await fsExtra.pathExists("package.json")) && (getExecutionMode() === ExecutionMode.EXECUTION_MODE_LOCAL_INSTALLATION || - getExecutionMode() === ExecutionMode.EXECUTION_MODE_LINKED) + getExecutionMode() === ExecutionMode.EXECUTION_MODE_LINKED) && + // TODO: Figure out why this doesn't work on Win + os.type() !== "Windows_NT" ); } From c29b16c54e86b40b4ffc4d1f2a8dfbabdf29fb14 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Wed, 26 Feb 2020 17:27:20 +0000 Subject: [PATCH 72/99] Improve node task's description --- packages/buidler-core/src/builtin-tasks/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/src/builtin-tasks/node.ts b/packages/buidler-core/src/builtin-tasks/node.ts index 552e9eb2bf..6c6655e1aa 100644 --- a/packages/buidler-core/src/builtin-tasks/node.ts +++ b/packages/buidler-core/src/builtin-tasks/node.ts @@ -63,7 +63,7 @@ Private Key: ${privateKey} } export default function() { - task(TASK_NODE, "Starts a Buidler EVM as a JSON-RPC server") + task(TASK_NODE, "Starts a JSON-RPC server on top of Buidler EVM") .addOptionalParam( "hostname", "The host to which to bind to for new connections", From d834a6ae4414210d09a37e07607571cae6781db5 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 10:21:43 +0000 Subject: [PATCH 73/99] RPC log improvements (#463) * Only show the deployed contract if the tx didn't fail * Remove non-collapsible methods logic --- .../src/internal/buidler-evm/provider/modules/eth.ts | 2 +- .../src/internal/buidler-evm/provider/provider.ts | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts index c78cd88715..1da11c0a84 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/modules/eth.ts @@ -1216,7 +1216,7 @@ If this error persists, try resetting your wallet's accounts.` ); } - if (trace.deployedContract !== undefined) { + if (trace.deployedContract !== undefined && trace.error === undefined) { this._logger.logWithTitle( "Contract address", bufferToHex(trace.deployedContract) diff --git a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts index 8273a3e741..d612e95cb9 100644 --- a/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts +++ b/packages/buidler-core/src/internal/buidler-evm/provider/provider.ts @@ -35,14 +35,6 @@ const log = debug("buidler:core:buidler-evm:provider"); // Set of methods that are never logged const PRIVATE_RPC_METHODS = new Set(["buidler_getStackTraceFailuresCount"]); -// These methods are shown every time, even if repeated right next to the other -const NON_COLLAPSIBLE_METHODS = new Set([ - "eth_sendTransaction", - "eth_sendRawTransaction", - "eth_call", - "eth_estimateGas" -]); - // tslint:disable only-buidler-error export class BuidlerEVMProvider extends EventEmitter @@ -186,7 +178,6 @@ export class BuidlerEVMProvider extends EventEmitter return ( method === this._methodBeingCollapsed && !this._logger.hasLogs() && - !NON_COLLAPSIBLE_METHODS.has(method) && this._methodCollapsedCount > 0 ); } From c3b10973740da1c9a44db05f82169bd0a6b1b95a Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 10:53:38 +0000 Subject: [PATCH 74/99] Add development branch warning --- packages/buidler-core/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/buidler-core/README.md b/packages/buidler-core/README.md index 3c0711049e..ecad404321 100644 --- a/packages/buidler-core/README.md +++ b/packages/buidler-core/README.md @@ -8,6 +8,8 @@ Developed by [Nomic Labs](https://nomiclabs.io/) and funded by an Ethereum Found Join our [Buidler Telegram group](http://t.me/BuidlerSupport) to stay up to date on new releases, plugins and tutorials. +🚧 **This the development branch of Buidler. For the latest version of the code, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 + ## Installation ### Local installation (recommended) From 726a62ada1402e28d6d8df69ef206c4f22b6fee4 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 11:02:40 +0000 Subject: [PATCH 75/99] Bump versions --- packages/buidler-core/package.json | 2 +- packages/buidler-ethers/package.json | 6 +++--- packages/buidler-etherscan/package.json | 6 +++--- packages/buidler-ganache/package.json | 6 +++--- packages/buidler-solhint/package.json | 6 +++--- packages/buidler-solpp/package.json | 6 +++--- packages/buidler-truffle4/package.json | 10 +++++----- packages/buidler-truffle5/package.json | 10 +++++----- packages/buidler-vyper/package.json | 6 +++--- packages/buidler-waffle/package.json | 10 +++++----- packages/buidler-web3-legacy/package.json | 6 +++--- packages/buidler-web3/package.json | 6 +++--- 12 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index 0471afd08d..c923dd2735 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler", - "version": "1.1.2", + "version": "1.2.0", "author": "Nomic Labs LLC", "license": "SEE LICENSE IN LICENSE", "homepage": "https://buidler.dev", diff --git a/packages/buidler-ethers/package.json b/packages/buidler-ethers/package.json index 421173b7db..b43ac6c74e 100644 --- a/packages/buidler-ethers/package.json +++ b/packages/buidler-ethers/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-ethers", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin for ethers", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-ethers", "repository": "github:nomiclabs/buidler", @@ -32,14 +32,14 @@ "README.md" ], "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "@types/fs-extra": "^5.1.0", "chai": "^4.2.0", "ethers": "^4.0.27" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "ethers": "^4.0.27" } } diff --git a/packages/buidler-etherscan/package.json b/packages/buidler-etherscan/package.json index 333727c0e1..4e3c8f598b 100644 --- a/packages/buidler-etherscan/package.json +++ b/packages/buidler-etherscan/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-etherscan", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin for verifying contracts on etherscan", "repository": "github:nomiclabs/buidler", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-etherscan", @@ -41,7 +41,7 @@ "request-promise": "^4.2.4" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "@types/ethereumjs-abi": "^0.6.3", "@types/nock": "^9.3.1", @@ -53,6 +53,6 @@ "solc": "0.5.15" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2" + "@nomiclabs/buidler": "^1.2.0" } } diff --git a/packages/buidler-ganache/package.json b/packages/buidler-ganache/package.json index 4cb9aa9632..f56ac8e104 100644 --- a/packages/buidler-ganache/package.json +++ b/packages/buidler-ganache/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-ganache", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin for managing Ganache", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-ganache", "repository": "github:nomiclabs/buidler", @@ -39,7 +39,7 @@ "ts-interface-checker": "^0.1.9" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "@types/debug": "^4.1.4", "@types/fs-extra": "^5.1.0", @@ -49,6 +49,6 @@ "typescript": "~3.5.3" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2" + "@nomiclabs/buidler": "^1.2.0" } } diff --git a/packages/buidler-solhint/package.json b/packages/buidler-solhint/package.json index 0d698ea734..98203a69f9 100644 --- a/packages/buidler-solhint/package.json +++ b/packages/buidler-solhint/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-solhint", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin for solhint", "repository": "github:nomiclabs/buidler", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-solhint", @@ -37,13 +37,13 @@ "solhint": "^2.0.0" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "@types/fs-extra": "^5.1.0", "chai": "^4.2.0", "fs-extra": "^7.0.1" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2" + "@nomiclabs/buidler": "^1.2.0" } } diff --git a/packages/buidler-solpp/package.json b/packages/buidler-solpp/package.json index 1867d21ba7..d5b9eb59ed 100644 --- a/packages/buidler-solpp/package.json +++ b/packages/buidler-solpp/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-solpp", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin for solpp", "repository": "github:nomiclabs/buidler", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-solpp", @@ -38,12 +38,12 @@ "solpp": "^0.10.1" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "@types/fs-extra": "^5.1.0", "chai": "^4.2.0" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2" + "@nomiclabs/buidler": "^1.2.0" } } diff --git a/packages/buidler-truffle4/package.json b/packages/buidler-truffle4/package.json index 5e7fbd4d54..165a8f3431 100644 --- a/packages/buidler-truffle4/package.json +++ b/packages/buidler-truffle4/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-truffle4", - "version": "1.1.2", + "version": "1.2.0", "description": "Truffle 4 Buidler compatibility plugin", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-truffle4", "repository": "github:nomiclabs/buidler", @@ -40,15 +40,15 @@ "fs-extra": "^7.0.1" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", - "@nomiclabs/buidler-web3-legacy": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", + "@nomiclabs/buidler-web3-legacy": "^1.2.0", "@types/fs-extra": "^5.1.0", "@types/glob": "^7.1.1", "web3": "^0.20.0" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2", - "@nomiclabs/buidler-web3-legacy": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", + "@nomiclabs/buidler-web3-legacy": "^1.2.0", "web3": "^0.20.0" } } diff --git a/packages/buidler-truffle5/package.json b/packages/buidler-truffle5/package.json index 45d96eac12..043ec124bb 100644 --- a/packages/buidler-truffle5/package.json +++ b/packages/buidler-truffle5/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-truffle5", - "version": "1.1.2", + "version": "1.2.0", "description": "Truffle 5 Buidler compatibility plugin", "repository": "github:nomiclabs/buidler", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-truffle5", @@ -40,15 +40,15 @@ "fs-extra": "^7.0.1" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", - "@nomiclabs/buidler-web3": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", + "@nomiclabs/buidler-web3": "^1.2.0", "@types/fs-extra": "^5.1.0", "@types/glob": "^7.1.1", "web3": "^1.2.0" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2", - "@nomiclabs/buidler-web3": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", + "@nomiclabs/buidler-web3": "^1.2.0", "web3": "^1.2.0" } } diff --git a/packages/buidler-vyper/package.json b/packages/buidler-vyper/package.json index e89262a281..dc7d8ec7ab 100644 --- a/packages/buidler-vyper/package.json +++ b/packages/buidler-vyper/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-vyper", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin to develop smart contracts in Vyper", "repository": "github:nomiclabs/buidler", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-vyper", @@ -38,13 +38,13 @@ "@nomiclabs/buidler-docker": "^0.1.2" }, "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "@types/fs-extra": "^5.1.0", "@types/glob": "^7.1.1", "chai": "^4.2.0" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2" + "@nomiclabs/buidler": "^1.2.0" } } diff --git a/packages/buidler-waffle/package.json b/packages/buidler-waffle/package.json index 3a72aa4ab5..dc9f0463f5 100644 --- a/packages/buidler-waffle/package.json +++ b/packages/buidler-waffle/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-waffle", - "version": "1.1.2", + "version": "1.2.0", "description": "Buidler plugin to test smart contracts with Waffle", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-waffle", "repository": "github:nomiclabs/buidler", @@ -32,8 +32,8 @@ "README.md" ], "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", - "@nomiclabs/buidler-ethers": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", + "@nomiclabs/buidler-ethers": "^1.2.0", "@types/chai": "^4.2.0", "@types/fs-extra": "^5.1.0", "chai": "^4.2.0", @@ -41,8 +41,8 @@ "ethers": "^4.0.27" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2", - "@nomiclabs/buidler-ethers": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", + "@nomiclabs/buidler-ethers": "^1.2.0", "ethers": "^4.0.27", "ethereum-waffle": "^2.3.0" }, diff --git a/packages/buidler-web3-legacy/package.json b/packages/buidler-web3-legacy/package.json index 98fbfa1cc3..a757ab4f64 100644 --- a/packages/buidler-web3-legacy/package.json +++ b/packages/buidler-web3-legacy/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-web3-legacy", - "version": "1.1.2", + "version": "1.2.0", "author": "Nomic Labs LLC", "license": "MIT", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-web3-legacy", @@ -33,13 +33,13 @@ "README.md" ], "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "chai": "^4.2.0", "web3": "^0.20.0" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "web3": "^0.20.0" } } diff --git a/packages/buidler-web3/package.json b/packages/buidler-web3/package.json index d45f226126..d613424692 100644 --- a/packages/buidler-web3/package.json +++ b/packages/buidler-web3/package.json @@ -1,6 +1,6 @@ { "name": "@nomiclabs/buidler-web3", - "version": "1.1.2", + "version": "1.2.0", "author": "Nomic Labs LLC", "license": "MIT", "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-web3", @@ -33,13 +33,13 @@ "README.md" ], "devDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "@types/chai": "^4.2.0", "chai": "^4.2.0", "web3": "^1.2.0" }, "peerDependencies": { - "@nomiclabs/buidler": "^1.1.2", + "@nomiclabs/buidler": "^1.2.0", "web3": "^1.2.0" }, "dependencies": { From b451b0a4e7e8603b15601fa750a8e34368a55a3b Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 11:21:01 +0000 Subject: [PATCH 76/99] Run GH Actions when pushing to development --- .github/workflows/CI.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 04c81d43f2..23608f2de0 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - development pull_request: branches: - "*" From 8187f661678acdc52c53d0750a8e0b68101c969f Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 11:21:29 +0000 Subject: [PATCH 77/99] Improve development branch warning --- packages/buidler-core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/README.md b/packages/buidler-core/README.md index ecad404321..9d5d0b9168 100644 --- a/packages/buidler-core/README.md +++ b/packages/buidler-core/README.md @@ -8,7 +8,7 @@ Developed by [Nomic Labs](https://nomiclabs.io/) and funded by an Ethereum Found Join our [Buidler Telegram group](http://t.me/BuidlerSupport) to stay up to date on new releases, plugins and tutorials. -🚧 **This the development branch of Buidler. For the latest version of the code, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 +🚧 **This is the development branch of Buidler. For the most recently published version of the code, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 ## Installation From 482f1d07d3cfe67fa84fc6439a0cfaab9164bdd6 Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 11:24:20 +0000 Subject: [PATCH 78/99] Make the development branch warning shorter --- packages/buidler-core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/README.md b/packages/buidler-core/README.md index 9d5d0b9168..32db77dad7 100644 --- a/packages/buidler-core/README.md +++ b/packages/buidler-core/README.md @@ -8,7 +8,7 @@ Developed by [Nomic Labs](https://nomiclabs.io/) and funded by an Ethereum Found Join our [Buidler Telegram group](http://t.me/BuidlerSupport) to stay up to date on new releases, plugins and tutorials. -🚧 **This is the development branch of Buidler. For the most recently published version of the code, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 +🚧 **This is the development branch of Buidler. For the most recently published version of Buidler, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 ## Installation From 602f05566b55350fe91b1a4fc30ac1249a1039ed Mon Sep 17 00:00:00 2001 From: Patricio Palladino Date: Thu, 27 Feb 2020 11:26:48 +0000 Subject: [PATCH 79/99] Upadte development branch warning --- packages/buidler-core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/README.md b/packages/buidler-core/README.md index 32db77dad7..1ff8e84d64 100644 --- a/packages/buidler-core/README.md +++ b/packages/buidler-core/README.md @@ -8,7 +8,7 @@ Developed by [Nomic Labs](https://nomiclabs.io/) and funded by an Ethereum Found Join our [Buidler Telegram group](http://t.me/BuidlerSupport) to stay up to date on new releases, plugins and tutorials. -🚧 **This is the development branch of Buidler. For the most recently published version of Buidler, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 +🚧 **This is the development branch of Buidler. For the most recently published code, look at [`master`](https://github.com/nomiclabs/buidler/tree/master)** 🚧 ## Installation From 71ed2e9705578376ae9ce377c10fcc20871af30d Mon Sep 17 00:00:00 2001 From: Franco Zeoli Date: Fri, 28 Feb 2020 11:11:19 +0000 Subject: [PATCH 80/99] [buidler-core] Update README banner image. --- packages/buidler-core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/README.md b/packages/buidler-core/README.md index 1ff8e84d64..1dbf77a52f 100644 --- a/packages/buidler-core/README.md +++ b/packages/buidler-core/README.md @@ -1,4 +1,4 @@ -![](https://user-images.githubusercontent.com/232174/57331293-9a042100-70ee-11e9-8c37-8a5d52875bf4.png) +![](https://user-images.githubusercontent.com/232174/75543992-f1c39e00-5a1a-11ea-8fd4-8933638b5910.png) [![NPM Package](https://img.shields.io/npm/v/@nomiclabs/buidler.svg?style=flat-square)](https://www.npmjs.org/package/@nomiclabs/buidler) ![Build Status](https://github.com/nomiclabs/buidler/workflows/CI/badge.svg) --------- From 5c902622e45a7c519e77c28ddb1ebeea83de73d2 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Sun, 8 Mar 2020 21:47:40 -0300 Subject: [PATCH 81/99] buidler-core: move '--recursive' files opt from 'mocha.opts' to 'package.json' 'test' script. * this enables running specific mocha tests without enforcing ts-node parsing of ALL test files, since the .opts config is always loaded. * individual test run time goes from about ~35 seconds to ~4 seconds. * global test run time is unaffected, so this is only useful for local testing. --- packages/buidler-core/package.json | 4 ++-- packages/buidler-core/test/mocha.opts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index c923dd2735..828e697cb1 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -31,8 +31,8 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node -r ts-node/register ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node -r ts-node/register ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node ../../node_modules/mocha/bin/mocha", - "coverage": "node ../../node_modules/nyc/bin/nyc.js ../../node_modules/mocha/bin/mocha", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts ", + "coverage": "node ../../node_modules/nyc/bin/nyc.js ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js builtin-tasks internal *.d.ts *.map *.js build-test tsconfig.tsbuildinfo" diff --git a/packages/buidler-core/test/mocha.opts b/packages/buidler-core/test/mocha.opts index bc0d4234ce..774e87cc4a 100644 --- a/packages/buidler-core/test/mocha.opts +++ b/packages/buidler-core/test/mocha.opts @@ -1,4 +1,4 @@ --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --exclude test/fixture-projects/**/*.ts --exclude test/fixture-projects/**/*.js --exclude test/helpers/**/*.ts +--exclude test/fixture-projects/**/*.ts --exclude test/fixture-projects/**/*.js --exclude test/helpers/**/*.ts --timeout 25000 From 4e18bdb42ef99ae2866964b162b5a2bdfee7addd Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Thu, 12 Mar 2020 12:30:27 -0300 Subject: [PATCH 82/99] buidler-core: ensure ts-node skips transpile in package-level & individual tests. --- packages/buidler-core/test/mocha.env.js | 8 ++++++++ packages/buidler-core/test/mocha.opts | 1 + 2 files changed, 9 insertions(+) create mode 100644 packages/buidler-core/test/mocha.env.js diff --git a/packages/buidler-core/test/mocha.env.js b/packages/buidler-core/test/mocha.env.js new file mode 100644 index 0000000000..08024ba200 --- /dev/null +++ b/packages/buidler-core/test/mocha.env.js @@ -0,0 +1,8 @@ +// From: https://github.com/mochajs/mocha/issues/185#issuecomment-321566188 + +/* + * Force ts-node to skip compilation, for test environment. + * This is useful for package-level or even individual-level testing, + * since this config is already provided in the project root global test runner. + */ +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-core/test/mocha.opts b/packages/buidler-core/test/mocha.opts index 774e87cc4a..44bf34ca7d 100644 --- a/packages/buidler-core/test/mocha.opts +++ b/packages/buidler-core/test/mocha.opts @@ -1,3 +1,4 @@ +--require test/mocha.env --require ts-node/register --require source-map-support/register --exclude test/fixture-projects/**/*.ts --exclude test/fixture-projects/**/*.js --exclude test/helpers/**/*.ts From a8d9cac8881802d6b2e8e3f918a51bae310c1695 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Fri, 13 Mar 2020 11:42:00 -0300 Subject: [PATCH 83/99] Ensure ts-node skips transpile when running package or individual tests, for all packages. --- packages/buidler-docker/.env | 1 + packages/buidler-ethers/.env | 1 + packages/buidler-etherscan/.env | 1 + packages/buidler-ganache/.env | 1 + packages/buidler-solhint/.env | 1 + packages/buidler-solpp/.env | 1 + packages/buidler-truffle4/.env | 1 + packages/buidler-truffle5/.env | 1 + packages/buidler-vyper/.env | 1 + packages/buidler-waffle/.env | 1 + packages/buidler-web3-legacy/.env | 1 + packages/buidler-web3/.env | 1 + 12 files changed, 12 insertions(+) diff --git a/packages/buidler-docker/.env b/packages/buidler-docker/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-docker/.env +++ b/packages/buidler-docker/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-ethers/.env b/packages/buidler-ethers/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-ethers/.env +++ b/packages/buidler-ethers/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-etherscan/.env b/packages/buidler-etherscan/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-etherscan/.env +++ b/packages/buidler-etherscan/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-ganache/.env b/packages/buidler-ganache/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-ganache/.env +++ b/packages/buidler-ganache/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-solhint/.env b/packages/buidler-solhint/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-solhint/.env +++ b/packages/buidler-solhint/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-solpp/.env b/packages/buidler-solpp/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-solpp/.env +++ b/packages/buidler-solpp/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-truffle4/.env b/packages/buidler-truffle4/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-truffle4/.env +++ b/packages/buidler-truffle4/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-truffle5/.env b/packages/buidler-truffle5/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-truffle5/.env +++ b/packages/buidler-truffle5/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-vyper/.env b/packages/buidler-vyper/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-vyper/.env +++ b/packages/buidler-vyper/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-waffle/.env b/packages/buidler-waffle/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-waffle/.env +++ b/packages/buidler-waffle/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-web3-legacy/.env b/packages/buidler-web3-legacy/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-web3-legacy/.env +++ b/packages/buidler-web3-legacy/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-web3/.env b/packages/buidler-web3/.env index 7ff5e18c5f..cebe36c032 100644 --- a/packages/buidler-web3/.env +++ b/packages/buidler-web3/.env @@ -1 +1,2 @@ TS_NODE_FILES=true +TS_NODE_TRANSPILE_ONLY=true From 26c90899611ac04d2b5370566ce520cb030a5a43 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Fri, 13 Mar 2020 17:52:48 -0300 Subject: [PATCH 84/99] Refactor: move .env configs inside packages test/ folder, drop 'dotenv' lib. * This is just a refactor. * The reasoning behind this is that we had .env files inside all packages roots, just to load them in each test environment - a leaky abstraction that actually pollutes the project architecture. * Such env vars were not being re-configured or used neither in other environments nor in production (at all). * With this change, we can drop the unneeded 'dotenv' lib, and also we remove pollution from each package root folder, hiding the implementation inside each test/ folder. * A future change could simplify the mocha tests configuration further, and also we now can do a refactor to move the repeated logic to some shared place. --- .gitignore | 8 ++------ package.json | 1 - packages/buidler-core/.gitignore | 8 ++------ packages/buidler-core/test/mocha.env.js | 8 +------- packages/buidler-docker/.env | 2 -- packages/buidler-docker/.gitignore | 8 ++------ packages/buidler-docker/test/mocha.env.js | 2 ++ packages/buidler-docker/test/mocha.opts | 2 +- packages/buidler-ethers/.env | 2 -- packages/buidler-ethers/.gitignore | 8 ++------ packages/buidler-ethers/test/mocha.env.js | 2 ++ packages/buidler-ethers/test/mocha.opts | 2 +- packages/buidler-etherscan/.env | 2 -- packages/buidler-etherscan/.gitignore | 8 ++------ packages/buidler-etherscan/test/mocha.env.js | 2 ++ packages/buidler-etherscan/test/mocha.opts | 2 +- packages/buidler-ganache/.env | 2 -- packages/buidler-ganache/.gitignore | 8 ++------ packages/buidler-ganache/test/mocha.env.js | 2 ++ packages/buidler-ganache/test/mocha.opts | 2 +- packages/buidler-solhint/.env | 2 -- packages/buidler-solhint/.gitignore | 8 ++------ packages/buidler-solhint/test/mocha.env.js | 2 ++ packages/buidler-solhint/test/mocha.opts | 2 +- packages/buidler-solpp/.env | 2 -- packages/buidler-solpp/.gitignore | 8 ++------ packages/buidler-solpp/test/mocha.env.js | 2 ++ packages/buidler-solpp/test/mocha.opts | 2 +- packages/buidler-truffle4/.env | 2 -- packages/buidler-truffle4/.gitignore | 8 ++------ packages/buidler-truffle4/test/mocha.env.js | 2 ++ packages/buidler-truffle4/test/mocha.opts | 2 +- packages/buidler-truffle5/.env | 2 -- packages/buidler-truffle5/.gitignore | 8 ++------ packages/buidler-truffle5/test/mocha.env.js | 2 ++ packages/buidler-truffle5/test/mocha.opts | 2 +- packages/buidler-vyper/.env | 2 -- packages/buidler-vyper/.gitignore | 8 ++------ packages/buidler-vyper/test/mocha.env.js | 2 ++ packages/buidler-vyper/test/mocha.opts | 2 +- packages/buidler-waffle/.env | 2 -- packages/buidler-waffle/.gitignore | 8 ++------ packages/buidler-waffle/test/mocha.env.js | 2 ++ packages/buidler-waffle/test/mocha.opts | 2 +- packages/buidler-web3-legacy/.env | 2 -- packages/buidler-web3-legacy/.gitignore | 8 ++------ packages/buidler-web3-legacy/test/mocha.env.js | 2 ++ packages/buidler-web3-legacy/test/mocha.opts | 2 +- packages/buidler-web3/.env | 2 -- packages/buidler-web3/.gitignore | 8 ++------ packages/buidler-web3/test/mocha.env.js | 2 ++ packages/buidler-web3/test/mocha.opts | 2 +- 52 files changed, 65 insertions(+), 128 deletions(-) delete mode 100644 packages/buidler-docker/.env create mode 100644 packages/buidler-docker/test/mocha.env.js delete mode 100644 packages/buidler-ethers/.env create mode 100644 packages/buidler-ethers/test/mocha.env.js delete mode 100644 packages/buidler-etherscan/.env create mode 100644 packages/buidler-etherscan/test/mocha.env.js delete mode 100644 packages/buidler-ganache/.env create mode 100644 packages/buidler-ganache/test/mocha.env.js delete mode 100644 packages/buidler-solhint/.env create mode 100644 packages/buidler-solhint/test/mocha.env.js delete mode 100644 packages/buidler-solpp/.env create mode 100644 packages/buidler-solpp/test/mocha.env.js delete mode 100644 packages/buidler-truffle4/.env create mode 100644 packages/buidler-truffle4/test/mocha.env.js delete mode 100644 packages/buidler-truffle5/.env create mode 100644 packages/buidler-truffle5/test/mocha.env.js delete mode 100644 packages/buidler-vyper/.env create mode 100644 packages/buidler-vyper/test/mocha.env.js delete mode 100644 packages/buidler-waffle/.env create mode 100644 packages/buidler-waffle/test/mocha.env.js delete mode 100644 packages/buidler-web3-legacy/.env create mode 100644 packages/buidler-web3-legacy/test/mocha.env.js delete mode 100644 packages/buidler-web3/.env create mode 100644 packages/buidler-web3/test/mocha.env.js diff --git a/.gitignore b/.gitignore index 99f60ee9b6..aa59384d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,8 @@ package-lock.json .DS_Store -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -71,10 +71,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/package.json b/package.json index 5ab166db36..89b6505e05 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "@types/mocha": "^5.2.6", "@types/node": "^8.10.44", "chai": "^4.2.0", - "dotenv": "^6.2.0", "ganache-cli": "^6.4.3", "lerna": "^3.13.4", "mocha": "^5.2.0", diff --git a/packages/buidler-core/.gitignore b/packages/buidler-core/.gitignore index f11d17aff2..9ec9522d98 100644 --- a/packages/buidler-core/.gitignore +++ b/packages/buidler-core/.gitignore @@ -20,8 +20,8 @@ /builtin-tasks /internal -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -83,10 +83,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-core/test/mocha.env.js b/packages/buidler-core/test/mocha.env.js index 08024ba200..b7716e1acd 100644 --- a/packages/buidler-core/test/mocha.env.js +++ b/packages/buidler-core/test/mocha.env.js @@ -1,8 +1,2 @@ -// From: https://github.com/mochajs/mocha/issues/185#issuecomment-321566188 - -/* - * Force ts-node to skip compilation, for test environment. - * This is useful for package-level or even individual-level testing, - * since this config is already provided in the project root global test runner. - */ +process.env.TS_NODE_FILES = "true"; process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-docker/.env b/packages/buidler-docker/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-docker/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-docker/.gitignore b/packages/buidler-docker/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-docker/.gitignore +++ b/packages/buidler-docker/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-docker/test/mocha.env.js b/packages/buidler-docker/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-docker/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-docker/test/mocha.opts b/packages/buidler-docker/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-docker/test/mocha.opts +++ b/packages/buidler-docker/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-ethers/.env b/packages/buidler-ethers/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-ethers/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-ethers/.gitignore b/packages/buidler-ethers/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-ethers/.gitignore +++ b/packages/buidler-ethers/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-ethers/test/mocha.env.js b/packages/buidler-ethers/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-ethers/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-ethers/test/mocha.opts b/packages/buidler-ethers/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-ethers/test/mocha.opts +++ b/packages/buidler-ethers/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-etherscan/.env b/packages/buidler-etherscan/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-etherscan/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-etherscan/.gitignore b/packages/buidler-etherscan/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-etherscan/.gitignore +++ b/packages/buidler-etherscan/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-etherscan/test/mocha.env.js b/packages/buidler-etherscan/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-etherscan/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-etherscan/test/mocha.opts b/packages/buidler-etherscan/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-etherscan/test/mocha.opts +++ b/packages/buidler-etherscan/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-ganache/.env b/packages/buidler-ganache/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-ganache/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-ganache/.gitignore b/packages/buidler-ganache/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-ganache/.gitignore +++ b/packages/buidler-ganache/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-ganache/test/mocha.env.js b/packages/buidler-ganache/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-ganache/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-ganache/test/mocha.opts b/packages/buidler-ganache/test/mocha.opts index a8c9e4b076..a2a6944adc 100644 --- a/packages/buidler-ganache/test/mocha.opts +++ b/packages/buidler-ganache/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/*.ts diff --git a/packages/buidler-solhint/.env b/packages/buidler-solhint/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-solhint/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-solhint/.gitignore b/packages/buidler-solhint/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-solhint/.gitignore +++ b/packages/buidler-solhint/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-solhint/test/mocha.env.js b/packages/buidler-solhint/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-solhint/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-solhint/test/mocha.opts b/packages/buidler-solhint/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-solhint/test/mocha.opts +++ b/packages/buidler-solhint/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-solpp/.env b/packages/buidler-solpp/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-solpp/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-solpp/.gitignore b/packages/buidler-solpp/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-solpp/.gitignore +++ b/packages/buidler-solpp/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-solpp/test/mocha.env.js b/packages/buidler-solpp/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-solpp/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-solpp/test/mocha.opts b/packages/buidler-solpp/test/mocha.opts index efe668fb2a..2d53262e62 100644 --- a/packages/buidler-solpp/test/mocha.opts +++ b/packages/buidler-solpp/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-truffle4/.env b/packages/buidler-truffle4/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-truffle4/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-truffle4/.gitignore b/packages/buidler-truffle4/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-truffle4/.gitignore +++ b/packages/buidler-truffle4/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-truffle4/test/mocha.env.js b/packages/buidler-truffle4/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-truffle4/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-truffle4/test/mocha.opts b/packages/buidler-truffle4/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-truffle4/test/mocha.opts +++ b/packages/buidler-truffle4/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-truffle5/.env b/packages/buidler-truffle5/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-truffle5/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-truffle5/.gitignore b/packages/buidler-truffle5/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-truffle5/.gitignore +++ b/packages/buidler-truffle5/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-truffle5/test/mocha.env.js b/packages/buidler-truffle5/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-truffle5/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-truffle5/test/mocha.opts b/packages/buidler-truffle5/test/mocha.opts index 50687f7f43..a94a3a118b 100644 --- a/packages/buidler-truffle5/test/mocha.opts +++ b/packages/buidler-truffle5/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-vyper/.env b/packages/buidler-vyper/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-vyper/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-vyper/.gitignore b/packages/buidler-vyper/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-vyper/.gitignore +++ b/packages/buidler-vyper/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-vyper/test/mocha.env.js b/packages/buidler-vyper/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-vyper/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-vyper/test/mocha.opts b/packages/buidler-vyper/test/mocha.opts index 50687f7f43..a94a3a118b 100644 --- a/packages/buidler-vyper/test/mocha.opts +++ b/packages/buidler-vyper/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-waffle/.env b/packages/buidler-waffle/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-waffle/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-waffle/.gitignore b/packages/buidler-waffle/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-waffle/.gitignore +++ b/packages/buidler-waffle/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-waffle/test/mocha.env.js b/packages/buidler-waffle/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-waffle/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-waffle/test/mocha.opts b/packages/buidler-waffle/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-waffle/test/mocha.opts +++ b/packages/buidler-waffle/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-web3-legacy/.env b/packages/buidler-web3-legacy/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-web3-legacy/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-web3-legacy/.gitignore b/packages/buidler-web3-legacy/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-web3-legacy/.gitignore +++ b/packages/buidler-web3-legacy/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-web3-legacy/test/mocha.env.js b/packages/buidler-web3-legacy/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-web3-legacy/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-web3-legacy/test/mocha.opts b/packages/buidler-web3-legacy/test/mocha.opts index 347590b559..ff3252e022 100644 --- a/packages/buidler-web3-legacy/test/mocha.opts +++ b/packages/buidler-web3-legacy/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts diff --git a/packages/buidler-web3/.env b/packages/buidler-web3/.env deleted file mode 100644 index cebe36c032..0000000000 --- a/packages/buidler-web3/.env +++ /dev/null @@ -1,2 +0,0 @@ -TS_NODE_FILES=true -TS_NODE_TRANSPILE_ONLY=true diff --git a/packages/buidler-web3/.gitignore b/packages/buidler-web3/.gitignore index 35b6819fa9..c00d7e7296 100644 --- a/packages/buidler-web3/.gitignore +++ b/packages/buidler-web3/.gitignore @@ -9,8 +9,8 @@ /coverage /.nyc_output -# Below is Github's node gitignore template, except for dotenv's entries as it's used by mocha to pass flags to ts-node, -# and ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing # Logs logs @@ -72,10 +72,6 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file -#.env -#.env.test - # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/packages/buidler-web3/test/mocha.env.js b/packages/buidler-web3/test/mocha.env.js new file mode 100644 index 0000000000..b7716e1acd --- /dev/null +++ b/packages/buidler-web3/test/mocha.env.js @@ -0,0 +1,2 @@ +process.env.TS_NODE_FILES = "true"; +process.env.TS_NODE_TRANSPILE_ONLY = "true"; diff --git a/packages/buidler-web3/test/mocha.opts b/packages/buidler-web3/test/mocha.opts index 11c39b9de4..6b95811638 100644 --- a/packages/buidler-web3/test/mocha.opts +++ b/packages/buidler-web3/test/mocha.opts @@ -1,4 +1,4 @@ ---require dotenv/config +--require test/mocha.env --require ts-node/register --require source-map-support/register --recursive test/**/*.ts From 8055a87f0f81c63a904e95d6d8f7f3a752e67632 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Sat, 14 Mar 2020 19:41:44 -0300 Subject: [PATCH 85/99] Move mocha config '--recursive' from 'mocha.opts' to 'scripts/run-tests.js'. * This enables running tests individually in each package, without having to always include all the files in ts-node compiler. --- packages/buidler-docker/scripts/run-tests.js | 2 +- packages/buidler-docker/test/mocha.opts | 1 - packages/buidler-ethers/scripts/run-tests.js | 2 +- packages/buidler-ethers/test/mocha.opts | 1 - packages/buidler-etherscan/scripts/run-tests.js | 2 +- packages/buidler-etherscan/test/mocha.opts | 1 - packages/buidler-ganache/scripts/run-tests.js | 2 +- packages/buidler-ganache/test/mocha.opts | 1 - packages/buidler-solhint/scripts/run-tests.js | 2 +- packages/buidler-solhint/test/mocha.opts | 1 - packages/buidler-solpp/scripts/run-tests.js | 2 +- packages/buidler-solpp/test/mocha.opts | 1 - packages/buidler-truffle4/scripts/run-tests.js | 2 +- packages/buidler-truffle4/test/mocha.opts | 1 - packages/buidler-truffle5/scripts/run-tests.js | 2 +- packages/buidler-truffle5/test/mocha.opts | 1 - packages/buidler-vyper/scripts/run-tests.js | 2 +- packages/buidler-vyper/test/mocha.opts | 1 - packages/buidler-waffle/scripts/run-tests.js | 2 +- packages/buidler-waffle/test/mocha.opts | 1 - packages/buidler-web3-legacy/scripts/run-tests.js | 2 +- packages/buidler-web3-legacy/test/mocha.opts | 1 - packages/buidler-web3/scripts/run-tests.js | 4 +++- packages/buidler-web3/test/mocha.opts | 1 - 24 files changed, 14 insertions(+), 24 deletions(-) diff --git a/packages/buidler-docker/scripts/run-tests.js b/packages/buidler-docker/scripts/run-tests.js index fed6f603cf..ba69ff12ff 100755 --- a/packages/buidler-docker/scripts/run-tests.js +++ b/packages/buidler-docker/scripts/run-tests.js @@ -4,4 +4,4 @@ const shell = require("shelljs"); shell.config.fatal = true; process.env.FORCE_COLOR = "3"; -shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); +shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-docker/test/mocha.opts b/packages/buidler-docker/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-docker/test/mocha.opts +++ b/packages/buidler-docker/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-ethers/scripts/run-tests.js b/packages/buidler-ethers/scripts/run-tests.js index 2766a7490e..ff9423f656 100755 --- a/packages/buidler-ethers/scripts/run-tests.js +++ b/packages/buidler-ethers/scripts/run-tests.js @@ -38,7 +38,7 @@ async function main() { try { shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); + shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); } finally { cleanup(); } diff --git a/packages/buidler-ethers/test/mocha.opts b/packages/buidler-ethers/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-ethers/test/mocha.opts +++ b/packages/buidler-ethers/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-etherscan/scripts/run-tests.js b/packages/buidler-etherscan/scripts/run-tests.js index e260c17a1e..0289c4a414 100755 --- a/packages/buidler-etherscan/scripts/run-tests.js +++ b/packages/buidler-etherscan/scripts/run-tests.js @@ -38,7 +38,7 @@ async function main() { try { shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); + shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); } finally { cleanup(); } diff --git a/packages/buidler-etherscan/test/mocha.opts b/packages/buidler-etherscan/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-etherscan/test/mocha.opts +++ b/packages/buidler-etherscan/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-ganache/scripts/run-tests.js b/packages/buidler-ganache/scripts/run-tests.js index fed6f603cf..35b7be74b5 100755 --- a/packages/buidler-ganache/scripts/run-tests.js +++ b/packages/buidler-ganache/scripts/run-tests.js @@ -4,4 +4,4 @@ const shell = require("shelljs"); shell.config.fatal = true; process.env.FORCE_COLOR = "3"; -shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); +shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/*.ts --exit"); diff --git a/packages/buidler-ganache/test/mocha.opts b/packages/buidler-ganache/test/mocha.opts index a2a6944adc..8bb0ca46ed 100644 --- a/packages/buidler-ganache/test/mocha.opts +++ b/packages/buidler-ganache/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/*.ts --timeout 5000 diff --git a/packages/buidler-solhint/scripts/run-tests.js b/packages/buidler-solhint/scripts/run-tests.js index fed6f603cf..ba69ff12ff 100755 --- a/packages/buidler-solhint/scripts/run-tests.js +++ b/packages/buidler-solhint/scripts/run-tests.js @@ -4,4 +4,4 @@ const shell = require("shelljs"); shell.config.fatal = true; process.env.FORCE_COLOR = "3"; -shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); +shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-solhint/test/mocha.opts b/packages/buidler-solhint/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-solhint/test/mocha.opts +++ b/packages/buidler-solhint/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-solpp/scripts/run-tests.js b/packages/buidler-solpp/scripts/run-tests.js index fed6f603cf..ba69ff12ff 100755 --- a/packages/buidler-solpp/scripts/run-tests.js +++ b/packages/buidler-solpp/scripts/run-tests.js @@ -4,4 +4,4 @@ const shell = require("shelljs"); shell.config.fatal = true; process.env.FORCE_COLOR = "3"; -shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); +shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-solpp/test/mocha.opts b/packages/buidler-solpp/test/mocha.opts index 2d53262e62..678fd2d58b 100644 --- a/packages/buidler-solpp/test/mocha.opts +++ b/packages/buidler-solpp/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 7000 diff --git a/packages/buidler-truffle4/scripts/run-tests.js b/packages/buidler-truffle4/scripts/run-tests.js index e260c17a1e..0289c4a414 100755 --- a/packages/buidler-truffle4/scripts/run-tests.js +++ b/packages/buidler-truffle4/scripts/run-tests.js @@ -38,7 +38,7 @@ async function main() { try { shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); + shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); } finally { cleanup(); } diff --git a/packages/buidler-truffle4/test/mocha.opts b/packages/buidler-truffle4/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-truffle4/test/mocha.opts +++ b/packages/buidler-truffle4/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-truffle5/scripts/run-tests.js b/packages/buidler-truffle5/scripts/run-tests.js index e260c17a1e..0289c4a414 100755 --- a/packages/buidler-truffle5/scripts/run-tests.js +++ b/packages/buidler-truffle5/scripts/run-tests.js @@ -38,7 +38,7 @@ async function main() { try { shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); + shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); } finally { cleanup(); } diff --git a/packages/buidler-truffle5/test/mocha.opts b/packages/buidler-truffle5/test/mocha.opts index a94a3a118b..7d9cd122fa 100644 --- a/packages/buidler-truffle5/test/mocha.opts +++ b/packages/buidler-truffle5/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 10000 diff --git a/packages/buidler-vyper/scripts/run-tests.js b/packages/buidler-vyper/scripts/run-tests.js index fed6f603cf..ba69ff12ff 100755 --- a/packages/buidler-vyper/scripts/run-tests.js +++ b/packages/buidler-vyper/scripts/run-tests.js @@ -4,4 +4,4 @@ const shell = require("shelljs"); shell.config.fatal = true; process.env.FORCE_COLOR = "3"; -shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); +shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-vyper/test/mocha.opts b/packages/buidler-vyper/test/mocha.opts index a94a3a118b..7d9cd122fa 100644 --- a/packages/buidler-vyper/test/mocha.opts +++ b/packages/buidler-vyper/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 10000 diff --git a/packages/buidler-waffle/scripts/run-tests.js b/packages/buidler-waffle/scripts/run-tests.js index fed6f603cf..ba69ff12ff 100755 --- a/packages/buidler-waffle/scripts/run-tests.js +++ b/packages/buidler-waffle/scripts/run-tests.js @@ -4,4 +4,4 @@ const shell = require("shelljs"); shell.config.fatal = true; process.env.FORCE_COLOR = "3"; -shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); +shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-waffle/test/mocha.opts b/packages/buidler-waffle/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-waffle/test/mocha.opts +++ b/packages/buidler-waffle/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-web3-legacy/scripts/run-tests.js b/packages/buidler-web3-legacy/scripts/run-tests.js index 1fbcdee43c..c95f3094b5 100755 --- a/packages/buidler-web3-legacy/scripts/run-tests.js +++ b/packages/buidler-web3-legacy/scripts/run-tests.js @@ -38,7 +38,7 @@ async function main() { try { shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); + shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); shell.exec("node web3-lazy-object-tests/when-accessing-web3-class.js"); shell.exec("node web3-lazy-object-tests/when-accessing-web3-object.js"); shell.exec("node web3-lazy-object-tests/when-requiring-web3-module.js"); diff --git a/packages/buidler-web3-legacy/test/mocha.opts b/packages/buidler-web3-legacy/test/mocha.opts index ff3252e022..136307c76b 100644 --- a/packages/buidler-web3-legacy/test/mocha.opts +++ b/packages/buidler-web3-legacy/test/mocha.opts @@ -1,5 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts --timeout 6000 diff --git a/packages/buidler-web3/scripts/run-tests.js b/packages/buidler-web3/scripts/run-tests.js index 1fbcdee43c..9a80c5f998 100755 --- a/packages/buidler-web3/scripts/run-tests.js +++ b/packages/buidler-web3/scripts/run-tests.js @@ -38,7 +38,9 @@ async function main() { try { shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --exit"); + shell.exec( + "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit" + ); shell.exec("node web3-lazy-object-tests/when-accessing-web3-class.js"); shell.exec("node web3-lazy-object-tests/when-accessing-web3-object.js"); shell.exec("node web3-lazy-object-tests/when-requiring-web3-module.js"); diff --git a/packages/buidler-web3/test/mocha.opts b/packages/buidler-web3/test/mocha.opts index 6b95811638..69143c4f0b 100644 --- a/packages/buidler-web3/test/mocha.opts +++ b/packages/buidler-web3/test/mocha.opts @@ -1,4 +1,3 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---recursive test/**/*.ts From 051a984a03831cfff3b4fef124ff6887f22f8dd9 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Sat, 14 Mar 2020 19:58:18 -0300 Subject: [PATCH 86/99] Remove unneeded run-tests scripts and directly run mocha test from package.json. --- packages/buidler-docker/package.json | 2 +- packages/buidler-docker/scripts/run-tests.js | 7 ------- packages/buidler-ganache/package.json | 2 +- packages/buidler-ganache/scripts/run-tests.js | 7 ------- packages/buidler-solhint/package.json | 2 +- packages/buidler-solhint/scripts/run-tests.js | 7 ------- packages/buidler-solpp/package.json | 2 +- packages/buidler-solpp/scripts/run-tests.js | 7 ------- packages/buidler-vyper/package.json | 2 +- packages/buidler-vyper/scripts/run-tests.js | 7 ------- packages/buidler-waffle/package.json | 2 +- packages/buidler-waffle/scripts/run-tests.js | 7 ------- 12 files changed, 6 insertions(+), 48 deletions(-) delete mode 100755 packages/buidler-docker/scripts/run-tests.js delete mode 100755 packages/buidler-ganache/scripts/run-tests.js delete mode 100755 packages/buidler-solhint/scripts/run-tests.js delete mode 100755 packages/buidler-solpp/scripts/run-tests.js delete mode 100755 packages/buidler-vyper/scripts/run-tests.js delete mode 100755 packages/buidler-waffle/scripts/run-tests.js diff --git a/packages/buidler-docker/package.json b/packages/buidler-docker/package.json index c80815efda..cb57ba13cf 100644 --- a/packages/buidler-docker/package.json +++ b/packages/buidler-docker/package.json @@ -18,7 +18,7 @@ "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "watch": "node ../../node_modules/typescript/bin/tsc -w", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node ./scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-docker/scripts/run-tests.js b/packages/buidler-docker/scripts/run-tests.js deleted file mode 100755 index ba69ff12ff..0000000000 --- a/packages/buidler-docker/scripts/run-tests.js +++ /dev/null @@ -1,7 +0,0 @@ -const process = require("process"); -const shell = require("shelljs"); - -shell.config.fatal = true; -process.env.FORCE_COLOR = "3"; - -shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-ganache/package.json b/packages/buidler-ganache/package.json index f56ac8e104..39e5e5ca32 100644 --- a/packages/buidler-ganache/package.json +++ b/packages/buidler-ganache/package.json @@ -21,7 +21,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-ganache/scripts/run-tests.js b/packages/buidler-ganache/scripts/run-tests.js deleted file mode 100755 index 35b7be74b5..0000000000 --- a/packages/buidler-ganache/scripts/run-tests.js +++ /dev/null @@ -1,7 +0,0 @@ -const process = require("process"); -const shell = require("shelljs"); - -shell.config.fatal = true; -process.env.FORCE_COLOR = "3"; - -shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/*.ts --exit"); diff --git a/packages/buidler-solhint/package.json b/packages/buidler-solhint/package.json index 98203a69f9..1a35eb2c3e 100644 --- a/packages/buidler-solhint/package.json +++ b/packages/buidler-solhint/package.json @@ -22,7 +22,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-solhint/scripts/run-tests.js b/packages/buidler-solhint/scripts/run-tests.js deleted file mode 100755 index ba69ff12ff..0000000000 --- a/packages/buidler-solhint/scripts/run-tests.js +++ /dev/null @@ -1,7 +0,0 @@ -const process = require("process"); -const shell = require("shelljs"); - -shell.config.fatal = true; -process.env.FORCE_COLOR = "3"; - -shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-solpp/package.json b/packages/buidler-solpp/package.json index d5b9eb59ed..102df523e0 100644 --- a/packages/buidler-solpp/package.json +++ b/packages/buidler-solpp/package.json @@ -22,7 +22,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-solpp/scripts/run-tests.js b/packages/buidler-solpp/scripts/run-tests.js deleted file mode 100755 index ba69ff12ff..0000000000 --- a/packages/buidler-solpp/scripts/run-tests.js +++ /dev/null @@ -1,7 +0,0 @@ -const process = require("process"); -const shell = require("shelljs"); - -shell.config.fatal = true; -process.env.FORCE_COLOR = "3"; - -shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-vyper/package.json b/packages/buidler-vyper/package.json index dc7d8ec7ab..b715442778 100644 --- a/packages/buidler-vyper/package.json +++ b/packages/buidler-vyper/package.json @@ -20,7 +20,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-vyper/scripts/run-tests.js b/packages/buidler-vyper/scripts/run-tests.js deleted file mode 100755 index ba69ff12ff..0000000000 --- a/packages/buidler-vyper/scripts/run-tests.js +++ /dev/null @@ -1,7 +0,0 @@ -const process = require("process"); -const shell = require("shelljs"); - -shell.config.fatal = true; -process.env.FORCE_COLOR = "3"; - -shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); diff --git a/packages/buidler-waffle/package.json b/packages/buidler-waffle/package.json index dc9f0463f5..cd572afe65 100644 --- a/packages/buidler-waffle/package.json +++ b/packages/buidler-waffle/package.json @@ -20,7 +20,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-waffle/scripts/run-tests.js b/packages/buidler-waffle/scripts/run-tests.js deleted file mode 100755 index ba69ff12ff..0000000000 --- a/packages/buidler-waffle/scripts/run-tests.js +++ /dev/null @@ -1,7 +0,0 @@ -const process = require("process"); -const shell = require("shelljs"); - -shell.config.fatal = true; -process.env.FORCE_COLOR = "3"; - -shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); From 5c6aeee401e206c2abf009eee740989357b2c1fe Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Mon, 16 Mar 2020 18:17:55 -0300 Subject: [PATCH 87/99] Refactor: move ganache instance setup to a commmon mocha hooks script. * The ganache instance setup/teardown repeated logic is now implemented in a set of mocha global before() and after() hooks, which are loaded from mocha.opts, for any level of testing (global, package, individual). * The now (rather futile) remaining script/run-tests scripts are removed, and we directly run the mocha tests from each package npm scripts. This simplifies a lot of the logic required for testing. Now, for ALL packages. * This simplified arch also gives us the ability to apply global test settings, for example from lerna, we could try a different reporter: `npx lerna exec -- npm run test -- --reporter dot` * A small downside is that the time in setting a Ganache process must finish before the test timeout (since it's being done in a mocha before() hook), so the timeout config had to be increased for some packages. We can optimize this for a faster "load" in a future change. --- package.json | 1 + packages/buidler-ethers/package.json | 2 +- packages/buidler-ethers/scripts/run-tests.js | 47 ----------------- packages/buidler-ethers/test/mocha.env.js | 3 ++ packages/buidler-ethers/test/mocha.opts | 1 + packages/buidler-etherscan/package.json | 2 +- .../buidler-etherscan/scripts/run-tests.js | 47 ----------------- packages/buidler-etherscan/test/mocha.opts | 1 + packages/buidler-ganache/test/mocha.opts | 2 +- packages/buidler-truffle4/package.json | 2 +- .../buidler-truffle4/scripts/run-tests.js | 47 ----------------- packages/buidler-truffle4/test/mocha.opts | 1 + packages/buidler-truffle5/package.json | 2 +- .../buidler-truffle5/scripts/run-tests.js | 47 ----------------- packages/buidler-truffle5/test/mocha.opts | 1 + packages/buidler-web3-legacy/package.json | 2 +- .../buidler-web3-legacy/scripts/run-tests.js | 50 ------------------ packages/buidler-web3-legacy/test/mocha.opts | 1 + packages/buidler-web3/package.json | 2 +- packages/buidler-web3/scripts/run-tests.js | 52 ------------------- packages/buidler-web3/test/mocha.opts | 2 + .../common/test/helper/ganache-provider.ts | 52 +++++++++++++++++++ packages/common/test/run-with-ganache.ts | 38 ++++++++++++++ packages/common/tslint.json | 3 ++ 24 files changed, 111 insertions(+), 297 deletions(-) delete mode 100755 packages/buidler-ethers/scripts/run-tests.js delete mode 100755 packages/buidler-etherscan/scripts/run-tests.js delete mode 100755 packages/buidler-truffle4/scripts/run-tests.js delete mode 100755 packages/buidler-truffle5/scripts/run-tests.js delete mode 100755 packages/buidler-web3-legacy/scripts/run-tests.js delete mode 100755 packages/buidler-web3/scripts/run-tests.js create mode 100644 packages/common/test/helper/ganache-provider.ts create mode 100644 packages/common/test/run-with-ganache.ts create mode 100644 packages/common/tslint.json diff --git a/package.json b/package.json index 89b6505e05..cd11de4f4a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "devDependencies": { "@types/mocha": "^5.2.6", "@types/node": "^8.10.44", + "@types/shelljs": "^0.8.6", "chai": "^4.2.0", "ganache-cli": "^6.4.3", "lerna": "^3.13.4", diff --git a/packages/buidler-ethers/package.json b/packages/buidler-ethers/package.json index b43ac6c74e..bfd9ae146d 100644 --- a/packages/buidler-ethers/package.json +++ b/packages/buidler-ethers/package.json @@ -20,7 +20,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-ethers/scripts/run-tests.js b/packages/buidler-ethers/scripts/run-tests.js deleted file mode 100755 index ff9423f656..0000000000 --- a/packages/buidler-ethers/scripts/run-tests.js +++ /dev/null @@ -1,47 +0,0 @@ -const process = require("process"); -const { spawn } = require("child_process"); -const shell = require("shelljs"); -const os = require("os"); - -process.env.FORCE_COLOR = "3"; - -const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); - -let ganacheChild = null; - -function cleanup() { - if (ganacheChild) { - ganacheChild.kill(); - } -} - -async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js", "-d"], { - stdio: "ignore" - }); - await sleep(4000); -} - -function isGanacheRunning() { - const nc = shell.exec("nc -z localhost 8545"); - - return nc.code === 0; -} - -async function main() { - if (os.type() !== "Windows_NT" && isGanacheRunning()) { - console.log("Using existing ganache instance"); - } else { - console.log("Starting our own ganache instance"); - await startGanache(); - } - - try { - shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); - } finally { - cleanup(); - } -} - -main().catch(() => process.exit(1)); diff --git a/packages/buidler-ethers/test/mocha.env.js b/packages/buidler-ethers/test/mocha.env.js index b7716e1acd..a640f975d9 100644 --- a/packages/buidler-ethers/test/mocha.env.js +++ b/packages/buidler-ethers/test/mocha.env.js @@ -1,2 +1,5 @@ process.env.TS_NODE_FILES = "true"; process.env.TS_NODE_TRANSPILE_ONLY = "true"; + +// Args string to be passed to the Ganache CLI instance for test, splitted by "," or " " (space) +process.env.GANACHE_CLI_ARGS = "-d"; diff --git a/packages/buidler-ethers/test/mocha.opts b/packages/buidler-ethers/test/mocha.opts index 136307c76b..1275fa4b08 100644 --- a/packages/buidler-ethers/test/mocha.opts +++ b/packages/buidler-ethers/test/mocha.opts @@ -1,4 +1,5 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register +--file ../common/test/run-with-ganache --timeout 6000 diff --git a/packages/buidler-etherscan/package.json b/packages/buidler-etherscan/package.json index 4e3c8f598b..8f615605c1 100644 --- a/packages/buidler-etherscan/package.json +++ b/packages/buidler-etherscan/package.json @@ -24,7 +24,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-etherscan/scripts/run-tests.js b/packages/buidler-etherscan/scripts/run-tests.js deleted file mode 100755 index 0289c4a414..0000000000 --- a/packages/buidler-etherscan/scripts/run-tests.js +++ /dev/null @@ -1,47 +0,0 @@ -const process = require("process"); -const { spawn } = require("child_process"); -const shell = require("shelljs"); -const os = require("os"); - -process.env.FORCE_COLOR = "3"; - -const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); - -let ganacheChild = null; - -function cleanup() { - if (ganacheChild) { - ganacheChild.kill(); - } -} - -async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js"], { - stdio: "ignore" - }); - await sleep(4000); -} - -function isGanacheRunning() { - const nc = shell.exec("nc -z localhost 8545"); - - return nc.code === 0; -} - -async function main() { - if (os.type() !== "Windows_NT" && isGanacheRunning()) { - console.log("Using existing ganache instance"); - } else { - console.log("Starting our own ganache instance"); - await startGanache(); - } - - try { - shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); - } finally { - cleanup(); - } -} - -main().catch(() => process.exit(1)); diff --git a/packages/buidler-etherscan/test/mocha.opts b/packages/buidler-etherscan/test/mocha.opts index 136307c76b..1275fa4b08 100644 --- a/packages/buidler-etherscan/test/mocha.opts +++ b/packages/buidler-etherscan/test/mocha.opts @@ -1,4 +1,5 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register +--file ../common/test/run-with-ganache --timeout 6000 diff --git a/packages/buidler-ganache/test/mocha.opts b/packages/buidler-ganache/test/mocha.opts index 8bb0ca46ed..404f39560e 100644 --- a/packages/buidler-ganache/test/mocha.opts +++ b/packages/buidler-ganache/test/mocha.opts @@ -1,4 +1,4 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register ---timeout 5000 +--timeout 8000 diff --git a/packages/buidler-truffle4/package.json b/packages/buidler-truffle4/package.json index 165a8f3431..7b09f897ae 100644 --- a/packages/buidler-truffle4/package.json +++ b/packages/buidler-truffle4/package.json @@ -21,7 +21,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-truffle4/scripts/run-tests.js b/packages/buidler-truffle4/scripts/run-tests.js deleted file mode 100755 index 0289c4a414..0000000000 --- a/packages/buidler-truffle4/scripts/run-tests.js +++ /dev/null @@ -1,47 +0,0 @@ -const process = require("process"); -const { spawn } = require("child_process"); -const shell = require("shelljs"); -const os = require("os"); - -process.env.FORCE_COLOR = "3"; - -const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); - -let ganacheChild = null; - -function cleanup() { - if (ganacheChild) { - ganacheChild.kill(); - } -} - -async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js"], { - stdio: "ignore" - }); - await sleep(4000); -} - -function isGanacheRunning() { - const nc = shell.exec("nc -z localhost 8545"); - - return nc.code === 0; -} - -async function main() { - if (os.type() !== "Windows_NT" && isGanacheRunning()) { - console.log("Using existing ganache instance"); - } else { - console.log("Starting our own ganache instance"); - await startGanache(); - } - - try { - shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); - } finally { - cleanup(); - } -} - -main().catch(() => process.exit(1)); diff --git a/packages/buidler-truffle4/test/mocha.opts b/packages/buidler-truffle4/test/mocha.opts index 136307c76b..1275fa4b08 100644 --- a/packages/buidler-truffle4/test/mocha.opts +++ b/packages/buidler-truffle4/test/mocha.opts @@ -1,4 +1,5 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register +--file ../common/test/run-with-ganache --timeout 6000 diff --git a/packages/buidler-truffle5/package.json b/packages/buidler-truffle5/package.json index 043ec124bb..c7f9adcd59 100644 --- a/packages/buidler-truffle5/package.json +++ b/packages/buidler-truffle5/package.json @@ -21,7 +21,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-truffle5/scripts/run-tests.js b/packages/buidler-truffle5/scripts/run-tests.js deleted file mode 100755 index 0289c4a414..0000000000 --- a/packages/buidler-truffle5/scripts/run-tests.js +++ /dev/null @@ -1,47 +0,0 @@ -const process = require("process"); -const { spawn } = require("child_process"); -const shell = require("shelljs"); -const os = require("os"); - -process.env.FORCE_COLOR = "3"; - -const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); - -let ganacheChild = null; - -function cleanup() { - if (ganacheChild) { - ganacheChild.kill(); - } -} - -async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js"], { - stdio: "ignore" - }); - await sleep(4000); -} - -function isGanacheRunning() { - const nc = shell.exec("nc -z localhost 8545"); - - return nc.code === 0; -} - -async function main() { - if (os.type() !== "Windows_NT" && isGanacheRunning()) { - console.log("Using existing ganache instance"); - } else { - console.log("Starting our own ganache instance"); - await startGanache(); - } - - try { - shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); - } finally { - cleanup(); - } -} - -main().catch(() => process.exit(1)); diff --git a/packages/buidler-truffle5/test/mocha.opts b/packages/buidler-truffle5/test/mocha.opts index 7d9cd122fa..b57f6db329 100644 --- a/packages/buidler-truffle5/test/mocha.opts +++ b/packages/buidler-truffle5/test/mocha.opts @@ -1,4 +1,5 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register +--file ../common/test/run-with-ganache --timeout 10000 diff --git a/packages/buidler-web3-legacy/package.json b/packages/buidler-web3-legacy/package.json index a757ab4f64..f8cf0f883c 100644 --- a/packages/buidler-web3-legacy/package.json +++ b/packages/buidler-web3-legacy/package.json @@ -21,7 +21,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --recursive web3-lazy-object-tests/*.js --exit", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-web3-legacy/scripts/run-tests.js b/packages/buidler-web3-legacy/scripts/run-tests.js deleted file mode 100755 index c95f3094b5..0000000000 --- a/packages/buidler-web3-legacy/scripts/run-tests.js +++ /dev/null @@ -1,50 +0,0 @@ -const process = require("process"); -const { spawn } = require("child_process"); -const shell = require("shelljs"); -const os = require("os"); - -process.env.FORCE_COLOR = "3"; - -const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); - -let ganacheChild = null; - -function cleanup() { - if (ganacheChild) { - ganacheChild.kill(); - } -} - -async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js"], { - stdio: "ignore" - }); - await sleep(4000); -} - -function isGanacheRunning() { - const nc = shell.exec("nc -z localhost 8545"); - - return nc.code === 0; -} - -async function main() { - if (os.type() !== "Windows_NT" && isGanacheRunning()) { - console.log("Using existing ganache instance"); - } else { - console.log("Starting our own ganache instance"); - await startGanache(); - } - - try { - shell.config.fatal = true; - shell.exec("node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit"); - shell.exec("node web3-lazy-object-tests/when-accessing-web3-class.js"); - shell.exec("node web3-lazy-object-tests/when-accessing-web3-object.js"); - shell.exec("node web3-lazy-object-tests/when-requiring-web3-module.js"); - } finally { - cleanup(); - } -} - -main().catch(() => process.exit(1)); diff --git a/packages/buidler-web3-legacy/test/mocha.opts b/packages/buidler-web3-legacy/test/mocha.opts index 136307c76b..1275fa4b08 100644 --- a/packages/buidler-web3-legacy/test/mocha.opts +++ b/packages/buidler-web3-legacy/test/mocha.opts @@ -1,4 +1,5 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register +--file ../common/test/run-with-ganache --timeout 6000 diff --git a/packages/buidler-web3/package.json b/packages/buidler-web3/package.json index d613424692..67ac8f3b0f 100644 --- a/packages/buidler-web3/package.json +++ b/packages/buidler-web3/package.json @@ -21,7 +21,7 @@ "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", - "test": "node scripts/run-tests.js", + "test": "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --recursive web3-lazy-object-tests/*.js --exit ", "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" diff --git a/packages/buidler-web3/scripts/run-tests.js b/packages/buidler-web3/scripts/run-tests.js deleted file mode 100755 index 9a80c5f998..0000000000 --- a/packages/buidler-web3/scripts/run-tests.js +++ /dev/null @@ -1,52 +0,0 @@ -const process = require("process"); -const { spawn } = require("child_process"); -const shell = require("shelljs"); -const os = require("os"); - -process.env.FORCE_COLOR = "3"; - -const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout)); - -let ganacheChild = null; - -function cleanup() { - if (ganacheChild) { - ganacheChild.kill(); - } -} - -async function startGanache() { - ganacheChild = spawn("node", ["../../node_modules/ganache-cli/cli.js"], { - stdio: "ignore" - }); - await sleep(4000); -} - -function isGanacheRunning() { - const nc = shell.exec("nc -z localhost 8545"); - - return nc.code === 0; -} - -async function main() { - if (os.type() !== "Windows_NT" && isGanacheRunning()) { - console.log("Using existing ganache instance"); - } else { - console.log("Starting our own ganache instance"); - await startGanache(); - } - - try { - shell.config.fatal = true; - shell.exec( - "node ../../node_modules/mocha/bin/mocha --recursive test/**/*.ts --exit" - ); - shell.exec("node web3-lazy-object-tests/when-accessing-web3-class.js"); - shell.exec("node web3-lazy-object-tests/when-accessing-web3-object.js"); - shell.exec("node web3-lazy-object-tests/when-requiring-web3-module.js"); - } finally { - cleanup(); - } -} - -main().catch(() => process.exit(1)); diff --git a/packages/buidler-web3/test/mocha.opts b/packages/buidler-web3/test/mocha.opts index 69143c4f0b..1275fa4b08 100644 --- a/packages/buidler-web3/test/mocha.opts +++ b/packages/buidler-web3/test/mocha.opts @@ -1,3 +1,5 @@ --require test/mocha.env --require ts-node/register --require source-map-support/register +--file ../common/test/run-with-ganache +--timeout 6000 diff --git a/packages/common/test/helper/ganache-provider.ts b/packages/common/test/helper/ganache-provider.ts new file mode 100644 index 0000000000..2216b49b27 --- /dev/null +++ b/packages/common/test/helper/ganache-provider.ts @@ -0,0 +1,52 @@ +import { ChildProcess, spawn } from "child_process"; +import * as os from "os"; +// tslint:disable-next-line:no-implicit-dependencies +import * as shell from "shelljs"; + +const sleep = (timeout: number) => + new Promise(resolve => setTimeout(resolve, timeout)); + +export function cleanup(ganacheChild: ChildProcess) { + if (!ganacheChild) { + return; + } + ganacheChild.kill(); +} + +async function startGanache(args: string[] = []): Promise { + const ganacheCliPath = "../../node_modules/ganache-cli/cli.js"; + + const ganacheChild = spawn("node", [ganacheCliPath, ...args], { + stdio: "ignore" + }); + + // TODO would be better if here we wait for instance to effectively start... + await sleep(4000); + return ganacheChild; +} + +function isWindows() { + return os.type() === "Windows_NT"; +} + +function isGanacheRunning() { + if (isWindows()) { + // not checking for running ganache instance in Windows + return false; + } + + const nc = shell.exec("nc -z localhost 8545"); + + return nc.code === 0; +} + +export async function ganacheSetup( + args: string[] = [] +): Promise { + if (isGanacheRunning()) { + // if ganache is already running, we just reuse the instance + return null; + } + + return startGanache(args); +} diff --git a/packages/common/test/run-with-ganache.ts b/packages/common/test/run-with-ganache.ts new file mode 100644 index 0000000000..814d6d8444 --- /dev/null +++ b/packages/common/test/run-with-ganache.ts @@ -0,0 +1,38 @@ +import { ChildProcess } from "child_process"; + +import { cleanup, ganacheSetup } from "./helper/ganache-provider"; + +const ganacheCliArgs = (process.env.GANACHE_CLI_ARGS || "") + .split(/[\s,]+/) + .filter(arg => arg.length > 0); + +let ganacheInstance: ChildProcess | null; + +/** + * Ensure ganache is running, for tests that require it. + */ +before(async () => { + const ganacheArgsStr = + Array.isArray(ganacheCliArgs) && ganacheCliArgs.length > 0 + ? `with args: '${JSON.stringify(ganacheCliArgs)}'` + : ""; + + console.log(`### Setting up ganache instance ${ganacheArgsStr}###\n`); + ganacheInstance = await ganacheSetup(ganacheCliArgs); + if (ganacheInstance) { + console.log("### Started our own ganache instance ###"); + } else { + console.log("### Using existing ganache instance ###"); + } +}); + +/** + * Cleanup ganache instance down after test finishes. + */ +after(async () => { + if (!ganacheInstance) { + return; + } + cleanup(ganacheInstance); + console.log("\n### Stopped ganache instance ###"); +}); diff --git a/packages/common/tslint.json b/packages/common/tslint.json new file mode 100644 index 0000000000..74d5e082d5 --- /dev/null +++ b/packages/common/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../config/tslint/tslint.json" +} From edc79379da3cb973af66a5d88e7ba934c87b6353 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Mon, 16 Mar 2020 21:48:00 -0300 Subject: [PATCH 88/99] Properly wait for ganache instance to be up and running, in tests setup. * Instead of simply waiting for a constant "4000ms" timer. --- .../common/test/helper/ganache-provider.ts | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/common/test/helper/ganache-provider.ts b/packages/common/test/helper/ganache-provider.ts index 2216b49b27..08e7c3e49c 100644 --- a/packages/common/test/helper/ganache-provider.ts +++ b/packages/common/test/helper/ganache-provider.ts @@ -3,9 +3,6 @@ import * as os from "os"; // tslint:disable-next-line:no-implicit-dependencies import * as shell from "shelljs"; -const sleep = (timeout: number) => - new Promise(resolve => setTimeout(resolve, timeout)); - export function cleanup(ganacheChild: ChildProcess) { if (!ganacheChild) { return; @@ -16,12 +13,31 @@ export function cleanup(ganacheChild: ChildProcess) { async function startGanache(args: string[] = []): Promise { const ganacheCliPath = "../../node_modules/ganache-cli/cli.js"; - const ganacheChild = spawn("node", [ganacheCliPath, ...args], { - stdio: "ignore" - }); + const ganacheChild = spawn("node", [ganacheCliPath, ...args]); + console.time("Ganache spawn"); + // wait for ganache child process to start + await new Promise((resolve, reject) => { + ganacheChild.stdout.setEncoding("utf8"); + ganacheChild.stderr.setEncoding("utf8"); + + function checkIsRunning(data) { + const log = data.toString(); - // TODO would be better if here we wait for instance to effectively start... - await sleep(4000); + const logLower = log.toLowerCase(); + const isRunning = logLower.includes("listening on"); + if (isRunning) { + return resolve(); + } + const isError = logLower.includes("error") && !log.includes("mnemonic"); + if (isError) { + return reject(new Error(log)); + } + } + + ganacheChild.stdout.on("data", checkIsRunning); + ganacheChild.stderr.on("data", checkIsRunning); + }); + console.timeEnd("Ganache spawn"); return ganacheChild; } From 33c4ca04f4e69ffed298da7e34e62babe6fb48af Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Mon, 16 Mar 2020 21:50:05 -0300 Subject: [PATCH 89/99] Global scripts/run-test: pass node args to the test run command. --- scripts/run-tests.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/run-tests.js b/scripts/run-tests.js index c57ae88d1e..436d4bb937 100644 --- a/scripts/run-tests.js +++ b/scripts/run-tests.js @@ -28,10 +28,17 @@ if (shouldBuildTests) { process.env.TS_NODE_TRANSPILE_ONLY = "true"; +const nodeArgs = process.argv.slice(2); +const testArgs = nodeArgs.length > 0 && `-- ${nodeArgs.join(" ")}`; + +const testRunCommand = `npm run test ${testArgs || ""}`; + + + shell.exec( `npx lerna exec ${ shouldIgnoreVyperTests ? '--ignore "@nomiclabs/buidler-vyper"' : "" } ${ shouldIgnoreSolppTests ? '--ignore "@nomiclabs/buidler-solpp"' : "" - } --concurrency 1 -- npm run test` + } --concurrency 1 -- ${testRunCommand}` ); From 82bb1d6b0146169c3fb09ae799859dfcea236d0d Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Mon, 16 Mar 2020 23:39:53 -0300 Subject: [PATCH 90/99] Refactor in global scripts/run-tests.js for clearer commands logic. --- scripts/run-tests.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/scripts/run-tests.js b/scripts/run-tests.js index 436d4bb937..fde8987d24 100644 --- a/scripts/run-tests.js +++ b/scripts/run-tests.js @@ -1,9 +1,11 @@ const os = require("os"); -const process = require("process"); const shell = require("shelljs"); process.env.FORCE_COLOR = "3"; +// skip ts-node type checks (this is already covered in previous 'build-test' script) +process.env.TS_NODE_TRANSPILE_ONLY = "true"; + // throw if a command fails shell.config.fatal = true; @@ -14,31 +16,46 @@ const isWindows = os.type() === "Windows_NT"; // only Build tests in local environment const shouldBuildTests = !isGithubActions; -// only run Vyper tests in Linux CI environment, and ignore if using a Windows machine (since Docker Desktop is required, only available windows Pro) -const shouldIgnoreVyperTests = isGithubActions && !isLinux || isWindows; +shell.exec("npm run build"); + +if (shouldBuildTests) { + shell.exec("npm run build-test"); +} + +// ** check for packages to be ignored ** // + +// only run Vyper tests in Linux CI environment, +// and ignore if using a Windows machine (since Docker Desktop is required, only available windows Pro) +const shouldIgnoreVyperTests = (isGithubActions && !isLinux) || isWindows; // Solpp tests don't work in Windows const shouldIgnoreSolppTests = isWindows; -shell.exec("npm run build"); +const ignoredPackages = []; -if (shouldBuildTests) { - shell.exec("npm run build-test"); +if (shouldIgnoreVyperTests) { + ignoredPackages.push("@nomiclabs/buidler-vyper"); } -process.env.TS_NODE_TRANSPILE_ONLY = "true"; +if (shouldIgnoreSolppTests) { + ignoredPackages.push("@nomiclabs/buidler-solpp"); +} const nodeArgs = process.argv.slice(2); const testArgs = nodeArgs.length > 0 && `-- ${nodeArgs.join(" ")}`; const testRunCommand = `npm run test ${testArgs || ""}`; +function packagesToGlobStr(packages) { + return packages.length === 1 ? packages[0] : `{${packages.join(",")}}`; +} +const ignoredPackagesFilter = + Array.isArray(ignoredPackages) && ignoredPackages.length > 0 + ? `--ignore "${packagesToGlobStr(ignoredPackages)}"` + : ""; shell.exec( - `npx lerna exec ${ - shouldIgnoreVyperTests ? '--ignore "@nomiclabs/buidler-vyper"' : "" - } ${ - shouldIgnoreSolppTests ? '--ignore "@nomiclabs/buidler-solpp"' : "" - } --concurrency 1 -- ${testRunCommand}` + `npx lerna exec ${ignoredPackagesFilter} ` + + `--concurrency 1 -- ${testRunCommand}` ); From b663106f6161da251314157d3e1047ccaa5f5b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Milar?= Date: Wed, 18 Mar 2020 18:27:50 -0300 Subject: [PATCH 91/99] Fix windows dev support (#462) * Use .gitattributes to prevent tslint 'Delete CR' errors in Windows. * Fix tslint custom 'onlyBuidlerErrorRule' to properly work in Windows. * Escaped double quotes to fix 'lint:fix' npm script to work on Windows. * Auto-reformat (prettier). --- .gitattributes | 2 ++ packages/buidler-core/package.json | 2 +- packages/buidler-core/tslint/onlyBuidlerErrorRule.ts | 4 ++-- packages/buidler-docker/package.json | 2 +- packages/buidler-ethers/package.json | 2 +- packages/buidler-etherscan/package.json | 2 +- packages/buidler-ganache/package.json | 2 +- packages/buidler-solhint/package.json | 2 +- packages/buidler-solpp/package.json | 2 +- packages/buidler-truffle4/package.json | 2 +- packages/buidler-truffle5/package.json | 2 +- packages/buidler-vyper/package.json | 2 +- packages/buidler-waffle/package.json | 2 +- packages/buidler-web3-legacy/package.json | 2 +- packages/buidler-web3/package.json | 2 +- 15 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..64b99afa83 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# prevent github actions to checkout files with crlf line endings +* -text diff --git a/packages/buidler-core/package.json b/packages/buidler-core/package.json index c923dd2735..bce21a11c3 100644 --- a/packages/buidler-core/package.json +++ b/packages/buidler-core/package.json @@ -27,7 +27,7 @@ "node": ">=8.2.0" }, "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node -r ts-node/register ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node -r ts-node/register ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-core/tslint/onlyBuidlerErrorRule.ts b/packages/buidler-core/tslint/onlyBuidlerErrorRule.ts index b48cd021fe..07107cfbbf 100644 --- a/packages/buidler-core/tslint/onlyBuidlerErrorRule.ts +++ b/packages/buidler-core/tslint/onlyBuidlerErrorRule.ts @@ -20,8 +20,8 @@ export class Rule extends Lint.Rules.TypedRule { sourceFile: ts.SourceFile, program: ts.Program ): Lint.RuleFailure[] { - const srcDir = path.normalize(__dirname + "/../test"); - if (sourceFile.fileName.startsWith(srcDir)) { + const srcDir = path.normalize(`${__dirname}/../test`); + if (path.normalize(sourceFile.fileName).startsWith(srcDir)) { return []; } diff --git a/packages/buidler-docker/package.json b/packages/buidler-docker/package.json index c80815efda..969c5c1391 100644 --- a/packages/buidler-docker/package.json +++ b/packages/buidler-docker/package.json @@ -13,7 +13,7 @@ "docker" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "watch": "node ../../node_modules/typescript/bin/tsc -w", diff --git a/packages/buidler-ethers/package.json b/packages/buidler-ethers/package.json index b43ac6c74e..109cb8a154 100644 --- a/packages/buidler-ethers/package.json +++ b/packages/buidler-ethers/package.json @@ -16,7 +16,7 @@ "ethers.js" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-etherscan/package.json b/packages/buidler-etherscan/package.json index 4e3c8f598b..844398e3ab 100644 --- a/packages/buidler-etherscan/package.json +++ b/packages/buidler-etherscan/package.json @@ -20,7 +20,7 @@ "etherscan" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-ganache/package.json b/packages/buidler-ganache/package.json index f56ac8e104..3830747a6f 100644 --- a/packages/buidler-ganache/package.json +++ b/packages/buidler-ganache/package.json @@ -17,7 +17,7 @@ "testing-network" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-solhint/package.json b/packages/buidler-solhint/package.json index 98203a69f9..d1f256827a 100644 --- a/packages/buidler-solhint/package.json +++ b/packages/buidler-solhint/package.json @@ -18,7 +18,7 @@ "linter" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-solpp/package.json b/packages/buidler-solpp/package.json index d5b9eb59ed..930ba4f582 100644 --- a/packages/buidler-solpp/package.json +++ b/packages/buidler-solpp/package.json @@ -18,7 +18,7 @@ "solpp" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-truffle4/package.json b/packages/buidler-truffle4/package.json index 165a8f3431..957c0d0011 100644 --- a/packages/buidler-truffle4/package.json +++ b/packages/buidler-truffle4/package.json @@ -17,7 +17,7 @@ "truffle-contract" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-truffle5/package.json b/packages/buidler-truffle5/package.json index 043ec124bb..caa87e19be 100644 --- a/packages/buidler-truffle5/package.json +++ b/packages/buidler-truffle5/package.json @@ -17,7 +17,7 @@ "truffle-contract" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-vyper/package.json b/packages/buidler-vyper/package.json index dc7d8ec7ab..5d5cb2eb50 100644 --- a/packages/buidler-vyper/package.json +++ b/packages/buidler-vyper/package.json @@ -16,7 +16,7 @@ "vyper" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-waffle/package.json b/packages/buidler-waffle/package.json index dc9f0463f5..6022766930 100644 --- a/packages/buidler-waffle/package.json +++ b/packages/buidler-waffle/package.json @@ -16,7 +16,7 @@ "ethers.js" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-web3-legacy/package.json b/packages/buidler-web3-legacy/package.json index a757ab4f64..1b93f4b61d 100644 --- a/packages/buidler-web3-legacy/package.json +++ b/packages/buidler-web3-legacy/package.json @@ -17,7 +17,7 @@ "web3.js" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", diff --git a/packages/buidler-web3/package.json b/packages/buidler-web3/package.json index d613424692..84b0be8514 100644 --- a/packages/buidler-web3/package.json +++ b/packages/buidler-web3/package.json @@ -17,7 +17,7 @@ "web3.js" ], "scripts": { - "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write 'src/**/*.{js,ts}' 'test/**/*.{js,ts}' && npm run lint-src -- --fix && npm run lint-tests -- --fix", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint-src -- --fix && npm run lint-tests -- --fix", "lint": "npm run lint-src && npm run lint-tests", "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", From 7c1f976794879af78de3e0205042d22e1f9ac374 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Wed, 18 Mar 2020 19:17:13 -0300 Subject: [PATCH 92/99] Surround dependency@versionSpec with quotes in error message (#474) --- packages/buidler-core/src/internal/core/errors-list.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/buidler-core/src/internal/core/errors-list.ts b/packages/buidler-core/src/internal/core/errors-list.ts index 5ce423e32a..b130741ade 100644 --- a/packages/buidler-core/src/internal/core/errors-list.ts +++ b/packages/buidler-core/src/internal/core/errors-list.ts @@ -632,7 +632,7 @@ Please follow Buidler's instructions to resolve this.` MISSING_DEPENDENCY: { number: 801, message: `Plugin %plugin% requires %dependency% to be installed. -%extraMessage%Please run: npm install --save-dev%extraFlags% %dependency%@%versionSpec%`, +%extraMessage%Please run: npm install --save-dev%extraFlags% "%dependency%@%versionSpec%"`, title: "Plugin dependencies not installed", description: `You are trying to use a plugin with unmet dependencies. @@ -641,7 +641,7 @@ Please follow Buidler's instructions to resolve this.` DEPENDENCY_VERSION_MISMATCH: { number: 802, message: `Plugin %plugin% requires %dependency% version %versionSpec% but got %installedVersion%. -%extraMessage%If you haven't installed %dependency% manually, please run: npm install --save-dev%extraFlags% %dependency%@%versionSpec% +%extraMessage%If you haven't installed %dependency% manually, please run: npm install --save-dev%extraFlags% "%dependency%@%versionSpec%" If you have installed %dependency% yourself, please reinstall it with a valid version.`, title: "Plugin dependencies's version mismatch", description: `You are trying to use a plugin that requires a different version of one of its dependencies. From e367952b1c4d8be43d159ae761ba63986d4dd352 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Tue, 17 Mar 2020 22:36:10 -0300 Subject: [PATCH 93/99] Global test hooks: detect if ganache is running (reuse if available). * Now working in Windows platform as well. --- packages/common/.gitignore | 95 +++++++++++++++++++ packages/common/.npmrc | 1 + packages/common/package.json | 21 ++++ .../common/test/helper/ganache-provider.ts | 34 ++++--- packages/common/test/run-with-ganache.ts | 24 +++-- packages/common/tsconfig.json | 10 ++ packages/common/tslint.json | 5 +- 7 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 packages/common/.gitignore create mode 100644 packages/common/.npmrc create mode 100644 packages/common/package.json create mode 100644 packages/common/tsconfig.json diff --git a/packages/common/.gitignore b/packages/common/.gitignore new file mode 100644 index 0000000000..c00d7e7296 --- /dev/null +++ b/packages/common/.gitignore @@ -0,0 +1,95 @@ +# Node modules +/node_modules + +# Compilation output +/build-test/ +/dist + +# Code coverage artifacts +/coverage +/.nyc_output + +# Below is Github's node gitignore template, +# ignoring the node_modules part, as it'd ignore every node_modules, and we have some for testing + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +#node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + diff --git a/packages/common/.npmrc b/packages/common/.npmrc new file mode 100644 index 0000000000..43c97e719a --- /dev/null +++ b/packages/common/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/common/package.json b/packages/common/package.json new file mode 100644 index 0000000000..7c10203c65 --- /dev/null +++ b/packages/common/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nomiclabs/common", + "version": "1.2.0", + "description": "Common logic and utilities to use throughout the project", + "homepage": "https://github.com/nomiclabs/buidler/tree/master/packages/buidler-truffle4", + "repository": "github:nomiclabs/buidler", + "author": "Nomic Labs LLC", + "license": "MIT", + "private": true, + "scripts": { + "lint": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", + "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint -- --fix", + "build": "echo \"No 'src/' folder to build\"", + "build-test": "node ../../node_modules/typescript/bin/tsc --build .", + "clean": "node ../../node_modules/rimraf/bin.js dist build-test" + }, + "devDependencies": { + "@types/detect-port": "^1.1.0", + "detect-port": "^1.3.0" + } +} diff --git a/packages/common/test/helper/ganache-provider.ts b/packages/common/test/helper/ganache-provider.ts index 08e7c3e49c..3edbe1cff4 100644 --- a/packages/common/test/helper/ganache-provider.ts +++ b/packages/common/test/helper/ganache-provider.ts @@ -1,10 +1,12 @@ import { ChildProcess, spawn } from "child_process"; -import * as os from "os"; -// tslint:disable-next-line:no-implicit-dependencies -import * as shell from "shelljs"; +import * as detect from "detect-port"; + +const { GANACHE_PORT } = process.env; + +const port = GANACHE_PORT !== undefined ? Number(GANACHE_PORT) : 8545; export function cleanup(ganacheChild: ChildProcess) { - if (!ganacheChild) { + if (ganacheChild === undefined || ganacheChild === null) { return; } ganacheChild.kill(); @@ -15,12 +17,13 @@ async function startGanache(args: string[] = []): Promise { const ganacheChild = spawn("node", [ganacheCliPath, ...args]); console.time("Ganache spawn"); + // wait for ganache child process to start await new Promise((resolve, reject) => { ganacheChild.stdout.setEncoding("utf8"); ganacheChild.stderr.setEncoding("utf8"); - function checkIsRunning(data) { + function checkIsRunning(data: string | Buffer) { const log = data.toString(); const logLower = log.toLowerCase(); @@ -41,25 +44,20 @@ async function startGanache(args: string[] = []): Promise { return ganacheChild; } -function isWindows() { - return os.type() === "Windows_NT"; -} - -function isGanacheRunning() { - if (isWindows()) { - // not checking for running ganache instance in Windows - return false; - } - - const nc = shell.exec("nc -z localhost 8545"); +/** + * Returns true if port is already in use. + */ +async function isGanacheRunning() { + const suggestedFreePort = await detect(port); + const isPortInUse = suggestedFreePort !== port; - return nc.code === 0; + return isPortInUse; } export async function ganacheSetup( args: string[] = [] ): Promise { - if (isGanacheRunning()) { + if (await isGanacheRunning()) { // if ganache is already running, we just reuse the instance return null; } diff --git a/packages/common/test/run-with-ganache.ts b/packages/common/test/run-with-ganache.ts index 814d6d8444..f36eb5e904 100644 --- a/packages/common/test/run-with-ganache.ts +++ b/packages/common/test/run-with-ganache.ts @@ -2,7 +2,9 @@ import { ChildProcess } from "child_process"; import { cleanup, ganacheSetup } from "./helper/ganache-provider"; -const ganacheCliArgs = (process.env.GANACHE_CLI_ARGS || "") +const { GANACHE_CLI_ARGS } = process.env; + +const ganacheCliArgs = (GANACHE_CLI_ARGS !== undefined ? GANACHE_CLI_ARGS : "") .split(/[\s,]+/) .filter(arg => arg.length > 0); @@ -17,10 +19,20 @@ before(async () => { ? `with args: '${JSON.stringify(ganacheCliArgs)}'` : ""; - console.log(`### Setting up ganache instance ${ganacheArgsStr}###\n`); - ganacheInstance = await ganacheSetup(ganacheCliArgs); - if (ganacheInstance) { - console.log("### Started our own ganache instance ###"); + let setupMs = Date.now(); + try { + ganacheInstance = await ganacheSetup(ganacheCliArgs); + setupMs = Date.now() - setupMs; + } catch (error) { + console.log( + `Could not setup a ganache instance: ${error.message || error}` + ); + } + + if (ganacheInstance !== null) { + console.log( + `### Started our own ganache instance ${ganacheArgsStr} in ${setupMs}ms ###` + ); } else { console.log("### Using existing ganache instance ###"); } @@ -30,7 +42,7 @@ before(async () => { * Cleanup ganache instance down after test finishes. */ after(async () => { - if (!ganacheInstance) { + if (ganacheInstance === null) { return; } cleanup(ganacheInstance); diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json new file mode 100644 index 0000000000..58792e329b --- /dev/null +++ b/packages/common/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "./build-test", + "rootDirs": ["./test"] + }, + "include": ["./test/**/*.ts", "./src/type-extensions.d.ts"], + "exclude": ["./test/**/buidler.config.ts"], + "references": [] +} diff --git a/packages/common/tslint.json b/packages/common/tslint.json index 74d5e082d5..57232b6485 100644 --- a/packages/common/tslint.json +++ b/packages/common/tslint.json @@ -1,3 +1,6 @@ { - "extends": "../../config/tslint/tslint.json" + "extends": "../../config/tslint/tslint.json", + "rules": { + "no-implicit-dependencies": [true, "dev"] + } } From 17fb6bb854b5f6154cf94f9e3d6d97a1497bf2fb Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Tue, 17 Mar 2020 23:54:50 -0300 Subject: [PATCH 94/99] Run a single ganache instance for all tests. --- packages/common/package.json | 6 ++- .../{test => src}/helper/ganache-provider.ts | 4 +- packages/common/src/tsconfig.json | 10 ++++ packages/common/test/run-with-ganache.ts | 2 +- packages/common/tsconfig.json | 6 ++- scripts/run-tests.js | 49 +++++++++++++++++-- 6 files changed, 67 insertions(+), 10 deletions(-) rename packages/common/{test => src}/helper/ganache-provider.ts (94%) create mode 100644 packages/common/src/tsconfig.json diff --git a/packages/common/package.json b/packages/common/package.json index 7c10203c65..5da221a612 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -8,9 +8,11 @@ "license": "MIT", "private": true, "scripts": { - "lint": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", "lint:fix": "node ../../node_modules/prettier/bin-prettier.js --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" && npm run lint -- --fix", - "build": "echo \"No 'src/' folder to build\"", + "lint": "npm run lint-src && npm run lint-tests", + "lint-tests": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project ./tsconfig.json", + "lint-src": "node ../../node_modules/tslint/bin/tslint --config tslint.json --project src/tsconfig.json", + "build": "node ../../node_modules/typescript/bin/tsc --build src", "build-test": "node ../../node_modules/typescript/bin/tsc --build .", "clean": "node ../../node_modules/rimraf/bin.js dist build-test" }, diff --git a/packages/common/test/helper/ganache-provider.ts b/packages/common/src/helper/ganache-provider.ts similarity index 94% rename from packages/common/test/helper/ganache-provider.ts rename to packages/common/src/helper/ganache-provider.ts index 3edbe1cff4..bb197c4696 100644 --- a/packages/common/test/helper/ganache-provider.ts +++ b/packages/common/src/helper/ganache-provider.ts @@ -1,5 +1,5 @@ import { ChildProcess, spawn } from "child_process"; -import * as detect from "detect-port"; +import detect from "detect-port"; const { GANACHE_PORT } = process.env; @@ -13,7 +13,7 @@ export function cleanup(ganacheChild: ChildProcess) { } async function startGanache(args: string[] = []): Promise { - const ganacheCliPath = "../../node_modules/ganache-cli/cli.js"; + const ganacheCliPath = require.resolve("ganache-cli/cli.js"); const ganacheChild = spawn("node", [ganacheCliPath, ...args]); console.time("Ganache spawn"); diff --git a/packages/common/src/tsconfig.json b/packages/common/src/tsconfig.json new file mode 100644 index 0000000000..0858817dde --- /dev/null +++ b/packages/common/src/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "../dist", + "rootDirs": ["."], + "composite": true + }, + "include": ["./**/*.ts"], + "exclude": [] +} diff --git a/packages/common/test/run-with-ganache.ts b/packages/common/test/run-with-ganache.ts index f36eb5e904..79005f9b4f 100644 --- a/packages/common/test/run-with-ganache.ts +++ b/packages/common/test/run-with-ganache.ts @@ -1,6 +1,6 @@ import { ChildProcess } from "child_process"; -import { cleanup, ganacheSetup } from "./helper/ganache-provider"; +import { cleanup, ganacheSetup } from "../src/helper/ganache-provider"; const { GANACHE_CLI_ARGS } = process.env; diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json index 58792e329b..703d240ddd 100644 --- a/packages/common/tsconfig.json +++ b/packages/common/tsconfig.json @@ -6,5 +6,9 @@ }, "include": ["./test/**/*.ts", "./src/type-extensions.d.ts"], "exclude": ["./test/**/buidler.config.ts"], - "references": [] + "references": [ + { + "path": "./src" + } + ] } diff --git a/scripts/run-tests.js b/scripts/run-tests.js index fde8987d24..b1e0e60605 100644 --- a/scripts/run-tests.js +++ b/scripts/run-tests.js @@ -55,7 +55,48 @@ const ignoredPackagesFilter = ? `--ignore "${packagesToGlobStr(ignoredPackages)}"` : ""; -shell.exec( - `npx lerna exec ${ignoredPackagesFilter} ` + - `--concurrency 1 -- ${testRunCommand}` -); +const { + cleanup, + ganacheSetup +} = require("../packages/common/dist/helper/ganache-provider"); + +async function useGanacheInstance() { + try { + return await ganacheSetup(["--deterministic"]); + } catch (error) { + error.message = `Could not setup own ganache instance: ${error.message}`; + throw error; + } +} + +async function main() { + /* Ensure a ganache instance is running */ + const ganacheInstance = await useGanacheInstance(); + if (ganacheInstance) { + console.log("** Running a Ganache instance for tests **"); + } else { + console.log("** Using existing Ganache instance for tests **"); + } + + try { + /* Run all tests */ + console.time("test all"); + shell.exec( + `npx lerna exec ${ignoredPackagesFilter} --no-private ` + + ` --concurrency 1 ` + + ` -- ${testRunCommand}` + ); + console.timeEnd("test all"); + } finally { + /* Cleanup ganache instance */ + if (ganacheInstance) { + console.log("** Tearing ganache instance down **"); + cleanup(ganacheInstance); + } + } +} + +main().catch(error => { + console.error(error); + process.exit(1); +}); From e1ba0a4e9074f1de2326737b74256e058af82cfe Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Thu, 19 Mar 2020 15:14:51 -0300 Subject: [PATCH 95/99] Global test script: run non-ganache-dependant packages tests in parallel. * This gives us a shorter total test execution time. --- scripts/run-tests.js | 65 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/scripts/run-tests.js b/scripts/run-tests.js index b1e0e60605..5d16a75e03 100644 --- a/scripts/run-tests.js +++ b/scripts/run-tests.js @@ -44,7 +44,10 @@ if (shouldIgnoreSolppTests) { const nodeArgs = process.argv.slice(2); const testArgs = nodeArgs.length > 0 && `-- ${nodeArgs.join(" ")}`; -const testRunCommand = `npm run test ${testArgs || ""}`; +// default unlimited timeout for tests as we are running most of them in parallel. +const defaultTestArgs = '-- --timeout "0"'; + +const testRunCommand = `npm run test ${testArgs || defaultTestArgs}`; function packagesToGlobStr(packages) { return packages.length === 1 ? packages[0] : `{${packages.join(",")}}`; @@ -55,6 +58,31 @@ const ignoredPackagesFilter = ? `--ignore "${packagesToGlobStr(ignoredPackages)}"` : ""; +// ** setup packages to be run in parallel and in series ** // + +// Packages requiring a running ganache instance are run in series +// as an attempt to not overload the process resulting in a slower +// operation. The rest of packages, are all run in parallel. +const ganacheDependantPackages = [ + "@nomiclabs/buidler-ethers", + "@nomiclabs/buidler-etherscan", + "@nomiclabs/buidler-truffle4", + "@nomiclabs/buidler-truffle5", + "@nomiclabs/buidler-web3", + "@nomiclabs/buidler-web3-legacy" +].filter(p => !ignoredPackages.includes(p)); + +const ignoredPackagesGlobStr = packagesToGlobStr(ignoredPackages); +const ganacheDependantPackagesGlobStr = packagesToGlobStr( + ganacheDependantPackages +); + +const parallelPackageFilter = `${ignoredPackagesFilter} --ignore "${ganacheDependantPackagesGlobStr}"`; +const seriesPackageFilter = ` ${ignoredPackagesFilter} --scope "${ganacheDependantPackagesGlobStr}"`; + +const lernaExecParallel = `npx lerna exec --parallel ${parallelPackageFilter} -- ${testRunCommand}`; +const lernaExecSeries = `npx lerna exec --concurrency 1 --stream ${seriesPackageFilter} -- ${testRunCommand}`; + const { cleanup, ganacheSetup @@ -69,6 +97,33 @@ async function useGanacheInstance() { } } +function shellExecAsync(cmd, opts = {}) { + return new Promise(function(resolve, reject) { + // Execute the command, reject if we exit non-zero (i.e. error) + shell.exec(cmd, opts, function(code, stdout, stderr) { + if (code !== 0) return reject(new Error(stderr)); + return resolve(stdout); + }); + }); +} + +async function runTests() { + console.log("Running: ", { lernaExecParallel }, { lernaExecSeries }); + + // Measure execution times + console.time("Total test time"); + console.time("parallel exec"); + console.time("series exec"); + await Promise.all([ + shellExecAsync(lernaExecParallel).then(() => + console.timeEnd("parallel exec") + ), + shellExecAsync(lernaExecSeries).then(() => console.timeEnd("series exec")) + ]); + + console.timeEnd("Total test time"); +} + async function main() { /* Ensure a ganache instance is running */ const ganacheInstance = await useGanacheInstance(); @@ -80,13 +135,7 @@ async function main() { try { /* Run all tests */ - console.time("test all"); - shell.exec( - `npx lerna exec ${ignoredPackagesFilter} --no-private ` + - ` --concurrency 1 ` + - ` -- ${testRunCommand}` - ); - console.timeEnd("test all"); + await runTests(); } finally { /* Cleanup ganache instance */ if (ganacheInstance) { From 7cebfb1384f744c5f51e34d9d704c059b6eeb829 Mon Sep 17 00:00:00 2001 From: Franco Victorio Date: Thu, 19 Mar 2020 17:07:28 -0300 Subject: [PATCH 96/99] Add .gitattributes for solidity highlighting (#465) Co-authored-by: Patricio Palladino --- .gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 64b99afa83..6eac374b65 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ +*.sol linguist-language=Solidity # prevent github actions to checkout files with crlf line endings -* -text +* -text \ No newline at end of file From 68c6f0c7c0427a09b60e974a158e8fd63252635b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Milar?= Date: Thu, 19 Mar 2020 17:28:43 -0300 Subject: [PATCH 97/99] Script runner perf optimization (#473) * Fix buidler runScript() '--inspect' flag so it can properly work with the debugger. * Script runner omit re-compiling with ts-node, unless requesting a TS script. * This way the run task will run much faster, the same goes for tests that otherwise can even timeout. * Fix script runner failing test, stop skipping on Windows, remove unneeded timeout. * Parallelize runScript() calls in scripts-runner tests for a faster execution time. * Add JSDocs explaining why enforce '--inspect' VS '--inspect-brk=' in runScript(). --- .../src/internal/util/scripts-runner.ts | 71 +++++++++-- .../test/internal/util/scripts-runner.ts | 119 ++++++++++++------ 2 files changed, 143 insertions(+), 47 deletions(-) diff --git a/packages/buidler-core/src/internal/util/scripts-runner.ts b/packages/buidler-core/src/internal/util/scripts-runner.ts index c32f448455..120430f912 100644 --- a/packages/buidler-core/src/internal/util/scripts-runner.ts +++ b/packages/buidler-core/src/internal/util/scripts-runner.ts @@ -16,9 +16,11 @@ export async function runScript( const { fork } = await import("child_process"); return new Promise((resolve, reject) => { + const processExecArgv = withFixedInspectArg(process.execArgv); + const nodeArgs = [ - ...process.execArgv, - ...getTsNodeArgsIfNeeded(), + ...processExecArgv, + ...getTsNodeArgsIfNeeded(scriptPath), ...extraNodeArgs ]; @@ -46,14 +48,12 @@ export async function runScriptWithBuidler( ): Promise { log(`Creating Buidler subprocess to run ${scriptPath}`); + const buidlerRegisterPath = resolveBuidlerRegisterPath(); + return runScript( scriptPath, scriptArgs, - [ - ...extraNodeArgs, - "--require", - path.join(__dirname, "..", "..", "register") - ], + [...extraNodeArgs, "--require", buidlerRegisterPath], { ...getEnvVariablesMap(buidlerArguments), ...extraEnvVars @@ -61,11 +61,66 @@ export async function runScriptWithBuidler( ); } -function getTsNodeArgsIfNeeded() { +/** + * Fix debugger "inspect" arg from process.argv, if present. + * + * When running this process with a debugger, a debugger port + * is specified via the "--inspect-brk=" arg param in some IDEs/setups. + * + * This normally works, but if we do a fork afterwards, we'll get an error stating + * that the port is already in use (since the fork would also use the same args, + * therefore the same port number). To prevent this issue, we could replace the port number with + * a different free one, or simply use the port-agnostic --inspect" flag, and leave the debugger + * port selection to the Node process itself, which will pick an empty AND valid one. + * + * This way, we can properly use the debugger for this process AND for the executed + * script itself - even if it's compiled using ts-node. + */ +function withFixedInspectArg(argv: string[]) { + const fixIfInspectArg = (arg: string) => { + if (arg.toLowerCase().includes("--inspect-brk=")) { + return "--inspect"; + } + return arg; + }; + return argv.map(fixIfInspectArg); +} + +/** + * Ensure buidler/register source file path is resolved to compiled JS file + * instead of TS source file, so we don't need to run ts-node unnecessarily. + */ +export function resolveBuidlerRegisterPath() { + const executionMode = getExecutionMode(); + const isCompiledInstallation = [ + ExecutionMode.EXECUTION_MODE_LOCAL_INSTALLATION, + ExecutionMode.EXECUTION_MODE_GLOBAL_INSTALLATION, + ExecutionMode.EXECUTION_MODE_LINKED + ].includes(executionMode); + + const buidlerCoreBaseDir = path.join(__dirname, "..", ".."); + + const buidlerCoreCompiledDir = isCompiledInstallation + ? buidlerCoreBaseDir + : path.join(buidlerCoreBaseDir, ".."); + + const buidlerCoreRegisterCompiledPath = path.join( + buidlerCoreCompiledDir, + "register" + ); + + return buidlerCoreRegisterCompiledPath; +} + +function getTsNodeArgsIfNeeded(scriptPath: string) { if (getExecutionMode() !== ExecutionMode.EXECUTION_MODE_TS_NODE_TESTS) { return []; } + if (!/\.tsx?$/i.test(scriptPath)) { + return []; + } + const extraNodeArgs = []; if (!process.execArgv.includes("ts-node/register")) { diff --git a/packages/buidler-core/test/internal/util/scripts-runner.ts b/packages/buidler-core/test/internal/util/scripts-runner.ts index 5481489a58..bd408a4999 100644 --- a/packages/buidler-core/test/internal/util/scripts-runner.ts +++ b/packages/buidler-core/test/internal/util/scripts-runner.ts @@ -1,8 +1,7 @@ import { assert } from "chai"; -import os from "os"; -import path from "path"; import { + resolveBuidlerRegisterPath, runScript, runScriptWithBuidler } from "../../../src/internal/util/scripts-runner"; @@ -13,12 +12,18 @@ describe("Scripts runner", function() { useFixtureProject("project-with-scripts"); it("Should pass params to the script", async function() { - const statusCode = await runScript("./params-script.js", ["a", "b", "c"]); - assert.equal(statusCode, 0); + const [ + statusCodeWithScriptParams, + statusCodeWithNoParams + ] = await Promise.all([ + runScript("./params-script.js", ["a", "b", "c"]), + runScript("./params-script.js") + ]); + + assert.equal(statusCodeWithScriptParams, 0); // We check here that the script is correctly testing this: - const statusCode2 = await runScript("./params-script.js"); - assert.notEqual(statusCode2, 0); + assert.notEqual(statusCodeWithNoParams, 0); }); it("Should run the script to completion", async function() { @@ -30,54 +35,90 @@ describe("Scripts runner", function() { }); it("Should resolve to the status code of the script run", async function() { - this.timeout(35000); - - if (os.type() === "Windows_NT") { - this.skip(); - } - - const statusCode1 = await runScript( - "./async-script.js", - [], - ["--require", path.join(__dirname, "..", "..", "..", "src", "register")] + const buidlerRegisterPath = resolveBuidlerRegisterPath(); + + const extraNodeArgs = ["--require", buidlerRegisterPath]; + const scriptArgs: string[] = []; + + const runScriptCases = [ + { + scriptPath: "./async-script.js", + extraNodeArgs, + expectedStatusCode: 0 + }, + { + scriptPath: "./failing-script.js", + expectedStatusCode: 123 + }, + { + scriptPath: "./successful-script.js", + extraNodeArgs, + expectedStatusCode: 0 + } + ]; + + const runScriptTestResults = await Promise.all( + runScriptCases.map( + async ({ scriptPath, extraNodeArgs: _extraNodeArgs }) => { + const statusCode = + _extraNodeArgs === undefined + ? await runScript(scriptPath) + : await runScript(scriptPath, scriptArgs, _extraNodeArgs); + return { scriptPath, statusCode }; + } + ) ); - assert.equal(statusCode1, 0); - - const statusCode2 = await runScript("./failing-script.js"); - assert.equal(statusCode2, 123); - const statusCode3 = await runScript( - "./successful-script.js", - [], - ["--require", path.join(__dirname, "..", "..", "..", "src", "register")] + const expectedResults = runScriptCases.map( + ({ expectedStatusCode, scriptPath }) => ({ + scriptPath, + statusCode: expectedStatusCode + }) ); - assert.equal(statusCode3, 0); + + assert.deepEqual(runScriptTestResults, expectedResults); }); it("Should pass env variables to the script", async function() { - const statusCode = await runScript("./env-var-script.js", [], [], { - TEST_ENV_VAR: "test" - }); - assert.equal(statusCode, 0); + const [statusCodeWithEnvVars, statusCodeWithNoEnvArgs] = await Promise.all([ + runScript("./env-var-script.js", [], [], { + TEST_ENV_VAR: "test" + }), + runScript("./env-var-script.js") + ]); + + assert.equal( + statusCodeWithEnvVars, + 0, + "Status code with env vars should be 0" + ); - // We check here that the script is correctly testing this: - const statusCode2 = await runScript("./env-var-script.js"); - assert.notEqual(statusCode2, 0); + assert.notEqual( + statusCodeWithNoEnvArgs, + 0, + "Status code with no env vars should not be 0" + ); }); describe("runWithBuidler", function() { useEnvironment(); it("Should load buidler/register successfully", async function() { - const statusCode = await runScriptWithBuidler( - this.env.buidlerArguments, - "./successful-script.js" - ); - assert.equal(statusCode, 0); + const [ + statusCodeWithBuidler, + statusCodeWithoutBuidler + ] = await Promise.all([ + runScriptWithBuidler( + this.env.buidlerArguments, + "./successful-script.js" + ), + runScript("./successful-script.js") + ]); + + assert.equal(statusCodeWithBuidler, 0); // We check here that the script is correctly testing this: - const statusCode2 = await runScript("./successful-script.js"); - assert.notEqual(statusCode2, 0); + assert.notEqual(statusCodeWithoutBuidler, 0); }); it("Should forward all the buidler arguments", async function() { From 8ffc057ee7a4925453722ec2f7918f37dbed6785 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Fri, 20 Mar 2020 14:42:15 -0300 Subject: [PATCH 98/99] Use default reporter 'dot' in global test runner. --- scripts/run-tests.js | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/scripts/run-tests.js b/scripts/run-tests.js index 5d16a75e03..17db85245a 100644 --- a/scripts/run-tests.js +++ b/scripts/run-tests.js @@ -41,23 +41,31 @@ if (shouldIgnoreSolppTests) { ignoredPackages.push("@nomiclabs/buidler-solpp"); } -const nodeArgs = process.argv.slice(2); -const testArgs = nodeArgs.length > 0 && `-- ${nodeArgs.join(" ")}`; +function getTestArgsOrDefaults() { + const testNodeArgs = process.argv.slice(2); + const testNodeArgsLookupStr = testNodeArgs.join(" "); -// default unlimited timeout for tests as we are running most of them in parallel. -const defaultTestArgs = '-- --timeout "0"'; + // use default unlimited timeout, as we are running most of them in parallel and some tests may take too long. + if (!/(no-)?timeout/.test(testNodeArgsLookupStr)) { + testNodeArgs.push('--timeout "0"'); + } + + // use default reporter "dot", for less verbose output + // this reporter is good for limited output of mostly failed errors and elapsed times + if (!/reporter(?!-)/.test(testNodeArgsLookupStr)) { + testNodeArgs.push('--reporter "dot"'); + } + + return testNodeArgs.length > 0 ? `-- ${testNodeArgs.join(" ")}` : ""; +} -const testRunCommand = `npm run test ${testArgs || defaultTestArgs}`; +const testRunCommand = `npm run test ${getTestArgsOrDefaults()}`; +console.log({testRunCommand}); function packagesToGlobStr(packages) { return packages.length === 1 ? packages[0] : `{${packages.join(",")}}`; } -const ignoredPackagesFilter = - Array.isArray(ignoredPackages) && ignoredPackages.length > 0 - ? `--ignore "${packagesToGlobStr(ignoredPackages)}"` - : ""; - // ** setup packages to be run in parallel and in series ** // // Packages requiring a running ganache instance are run in series @@ -72,11 +80,15 @@ const ganacheDependantPackages = [ "@nomiclabs/buidler-web3-legacy" ].filter(p => !ignoredPackages.includes(p)); -const ignoredPackagesGlobStr = packagesToGlobStr(ignoredPackages); const ganacheDependantPackagesGlobStr = packagesToGlobStr( ganacheDependantPackages ); +const ignoredPackagesFilter = + Array.isArray(ignoredPackages) && ignoredPackages.length > 0 + ? `--ignore "${packagesToGlobStr(ignoredPackages)}"` + : ""; + const parallelPackageFilter = `${ignoredPackagesFilter} --ignore "${ganacheDependantPackagesGlobStr}"`; const seriesPackageFilter = ` ${ignoredPackagesFilter} --scope "${ganacheDependantPackagesGlobStr}"`; @@ -108,7 +120,8 @@ function shellExecAsync(cmd, opts = {}) { } async function runTests() { - console.log("Running: ", { lernaExecParallel }, { lernaExecSeries }); + console.log({lernaExecParallel}); + console.log({lernaExecSeries}); // Measure execution times console.time("Total test time"); From 92a2f1b9d10b1ed276770c6798a6378bd0882721 Mon Sep 17 00:00:00 2001 From: Tomas Milar Date: Fri, 20 Mar 2020 14:43:06 -0300 Subject: [PATCH 99/99] Explicitly skip 'Typescript support' test when no transpiling, instead of silently ignoring it. --- packages/buidler-core/test/internal/core/typescript-support.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/buidler-core/test/internal/core/typescript-support.ts b/packages/buidler-core/test/internal/core/typescript-support.ts index 8492ec402a..44bfb163f2 100644 --- a/packages/buidler-core/test/internal/core/typescript-support.ts +++ b/packages/buidler-core/test/internal/core/typescript-support.ts @@ -17,7 +17,7 @@ describe("Typescript support", function() { it("Should fail if an implicit any is used and the tsconfig forbids them", function() { // If we run this test in transpilation only mode, it will fail if (process.env.TS_NODE_TRANSPILE_ONLY === "true") { - return; + this.skip(); } assert.throws(