From 3123c7b87924b30eae94451c5510875e895e791b Mon Sep 17 00:00:00 2001 From: cds-amal Date: Wed, 14 Dec 2022 13:23:36 -0500 Subject: [PATCH] Implement console.log in Truffle (#5687) * Enable Ganache console log - Introduce a config namespace for `solidityLog` (see below) - Make managed Ganache instances aware of truffle-config because `connectOrStart` (ganache) needs to know relevant solidityLog settings to hook up Ganache events. - Use asynchronous swawn instead of synchronous option in core(console). It is necessary to interleave ganache's console stdout events (child process), with mocha's test runner (parent process). - Modify the GanacheMixin in `chain.js` to register for and, forward Ganache's console log events using the `truffle.solidity.log` channel/topic. - Use chalk decorated `console.log` to display solidity console log which will allow users to redirect `truffle test` output to a file. The debug module uses `stderr` insted of stdout which would make redirect to file awkward and complicated. - Update truffle resolver to handle `@truffle/Console` or `@truffle/console` Truffle will include @ganache/console.log/console.sol in its asset and allow smart contract writers users to integrate console.log by importing "truffle/Console.sol". This dependency is managed through npm and is currently pinned. The user does not have to import any console.log packages. yay! - Add @truffle/[Cc]onsole.log as a resolver dependency. This dependency is pinned for the time being - Make `includeTruffleSources` the default resolver option. Settings ======== The settings are namespaced in solidityLog ``` solidityLog: { displayPrefix: string; preventConsoleLogMigration: boolean; } ``` - solidityLog.displayPrefix - string [ default: "" ]. it is the display prefix for all detected console-log messages. NOTE: there is some defensive guards that resolve, null, undefined to behave exactly like the default setting () - solidityLog.preventConsoleLogMigration - boolean [ default: false]. If set to true, `truffle migrate` will not allow deployment on Mainnet. File changes ============ packages/config/src/configDefaults.ts packages/config/test/unit.test.ts - add defaults and tests for solidityLog.{displayPrefix,preventConsoleLogMigration} packages/core/lib/commands/develop/run.js - pass configOptions to connectOrStart packages/core/lib/commands/migrate/runMigrations.js - add migration guards to log when a deployment set has consoleLog assets and additionally prevent deployment based on settings. packages/core/lib/commands/test/run.js - hook up consoleLog for test command packages/core/lib/console.js - use spawn instead of spawnSync so that child process commands can be printed in-order instead of buffering and printing when process ends. This allows consoleLog events from the child process to be interleaved correctly with test outline from parent process. - modify provision method to eliminate need for filter loop packages/core/package.json - add JSONStream dependency packages/environment/chain.js packages/environment/develop.js packages/environment/package.json packages/environment/test/connectOrStartTest.js - hook into the "ganache:vm:tx:console.log" provider event and forward it across IPC infra as SolidityConsoleLogs - hook up client side IPC to listen for SolidityConsoleLogs - add @truffle/config and chalk dependencies - update tests to handle updated interfaces packages/resolver/.eslintrc.json - add eslintrc settings for truffle packages packages/core/lib/debug/compiler.js packages/test/src/Test.ts packages/test/src/TestRunner.ts - allow resolver to includeTruffleSources by default packages/resolver/lib/sources/truffle/index.ts packages/resolver/lib/resolver.ts packages/resolver/test/truffle.js packages/resolver/package.json - resolve "truffle/[cC]onsole.sol" - add tests for console.sol resolutions - add pinned @ganache/console.log/console.sol dependency - use for-of instead of forEach to utilize short circuit breakout. for-each will visit every item, even after a match has been found because there's no way to [1] break out of a for-each loop. > >There is no way to stop or break a forEach() loop other than by >throwing an exception. If you need such behavior, the forEach() method >is the wrong tool. > >Early termination may be accomplished with looping statements like for, >for...of, and for...in. Array methods like every(), some(), find(), and >findIndex() also stops iteration immediately when further iteration is >not necessary. packages/test/src/SolidityTest.ts - include `console.sol` in set of truffle assets to deploy for testing packages/truffle/package.json - add @ganache/console.log dependency packages/truffle/test/scenarios/solidity_console/Printf.sol packages/truffle/test/scenarios/solidity_console/printf.js packages/truffle/test/sources/init/config-disable-migrate-false.js packages/truffle/test/sources/init/config-disable-migrate-true.js packages/truffle/test/sources/init/truffle-config.js - integration tests for consoleLog packages/truffle/test/scenarios/sandbox.js - return tempDirPath when creating a sandbox. Printf tests depend on that. packages/truffle/webpack.config.js - bundle @ganache/console.log/console.sol Links ===== 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#:~:text=There%20is%20no%20way%20to,and%20for...in%20. Co-authored-by: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Co-authored-by: Micaiah Reid --- packages/config/src/configDefaults.ts | 5 + packages/config/test/unit.test.ts | 83 ++++--- packages/core/lib/commands/develop/run.js | 13 +- .../lib/commands/migrate/runMigrations.js | 104 +++++++++ packages/core/lib/commands/test/run.js | 16 +- packages/core/lib/console.js | 85 ++++--- packages/core/lib/debug/compiler.js | 4 +- packages/core/package.json | 1 + packages/environment/chain.js | 20 +- packages/environment/develop.js | 105 ++++++--- packages/environment/package.json | 5 +- .../environment/test/connectOrStartTest.js | 24 +- packages/resolver/.eslintrc.json | 4 + packages/resolver/lib/resolver.ts | 19 +- .../resolver/lib/sources/truffle/index.ts | 44 +++- packages/resolver/package.json | 1 + packages/resolver/test/truffle.js | 7 +- packages/test/src/SolidityTest.ts | 6 +- packages/test/src/Test.ts | 4 +- packages/test/src/TestRunner.ts | 4 +- packages/truffle/package.json | 1 + packages/truffle/test/scenarios/sandbox.js | 3 +- .../scenarios/solidity_console/Printf.sol | 37 +++ .../test/scenarios/solidity_console/printf.js | 210 ++++++++++++++++++ .../init/config-disable-migrate-false.js | 20 ++ .../init/config-disable-migrate-true.js | 20 ++ .../test/sources/init/truffle-config.js | 12 + packages/truffle/webpack.config.js | 7 +- yarn.lock | 41 +++- 29 files changed, 741 insertions(+), 164 deletions(-) create mode 100644 packages/resolver/.eslintrc.json create mode 100644 packages/truffle/test/scenarios/solidity_console/Printf.sol create mode 100644 packages/truffle/test/scenarios/solidity_console/printf.js create mode 100644 packages/truffle/test/sources/init/config-disable-migrate-false.js create mode 100644 packages/truffle/test/sources/init/config-disable-migrate-true.js diff --git a/packages/config/src/configDefaults.ts b/packages/config/src/configDefaults.ts index ff4ea4be9b3..5fd6930086f 100644 --- a/packages/config/src/configDefaults.ts +++ b/packages/config/src/configDefaults.ts @@ -28,6 +28,10 @@ export const getInitialConfig = ({ } }, verboseRpc: false, + solidityLog: { + displayPrefix: "", + preventConsoleLogMigration: false + }, gas: null, gasPrice: null, maxFeePerGas: null, @@ -93,6 +97,7 @@ export const configProps = ({ network() {}, networks() {}, verboseRpc() {}, + solidityLog() {}, build() {}, resolver() {}, artifactor() {}, diff --git a/packages/config/test/unit.test.ts b/packages/config/test/unit.test.ts index 1f49a730510..b2cd9cd1792 100644 --- a/packages/config/test/unit.test.ts +++ b/packages/config/test/unit.test.ts @@ -3,52 +3,65 @@ import TruffleConfig from "../dist"; import { describe, it } from "mocha"; describe("TruffleConfig unit tests", async () => { - describe("with", async () => { - let truffleConfig: TruffleConfig; + let truffleConfig: TruffleConfig; - beforeEach(() => { + describe("Defaults", async () => { + before(() => { truffleConfig = TruffleConfig.default(); }); - it("a simple object", async () => { - const expectedRandom = 42; - const expectedFoo = "bar"; - const obj = { - random: expectedRandom, - foo: expectedFoo + it("solidityLog", () => { + const expectedSolidityLog = { + displayPrefix: "", + preventConsoleLogMigration: false }; - const newConfig = truffleConfig.with(obj); - assert.equal(expectedRandom, newConfig.random); - assert.equal(expectedFoo, newConfig.foo); + assert.deepStrictEqual(truffleConfig.solidityLog, expectedSolidityLog); }); + }), + describe("with", async () => { + beforeEach(() => { + truffleConfig = TruffleConfig.default(); + }); - it("overwrites a known property", () => { - const expectedProvider = { a: "propertyA", b: "propertyB" }; - const newConfig = truffleConfig.with({ provider: expectedProvider }); - assert.deepEqual(expectedProvider, newConfig.provider); - }); + it("a simple object", async () => { + const expectedRandom = 42; + const expectedFoo = "bar"; + const obj = { + random: expectedRandom, + foo: expectedFoo + }; + const newConfig = truffleConfig.with(obj); + assert.strictEqual(expectedRandom, newConfig.random); + assert.strictEqual(expectedFoo, newConfig.foo); + }); - it("ignores properties that throw", () => { - const expectedSurvivor = "BatMan"; - const minefield = { who: expectedSurvivor }; - - const hits = ["boom", "pow", "crash", "zonk"]; - hits.forEach(hit => { - Object.defineProperty(minefield, hit, { - get() { - throw new Error("BOOM!"); - }, - enumerable: true //must be enumerable - }); + it("overwrites a known property", () => { + const expectedProvider = { a: "propertyA", b: "propertyB" }; + const newConfig = truffleConfig.with({ provider: expectedProvider }); + assert.deepStrictEqual(expectedProvider, newConfig.provider); }); - const newConfig = truffleConfig.with(minefield); + it("ignores properties that throw", () => { + const expectedSurvivor = "BatMan"; + const minefield = { who: expectedSurvivor }; - //one survivor - assert.equal(expectedSurvivor, newConfig.who); + const hits = ["boom", "pow", "crash", "zonk"]; + hits.forEach(hit => { + Object.defineProperty(minefield, hit, { + get() { + throw new Error("BOOM!"); + }, + enumerable: true //must be enumerable + }); + }); + + const newConfig = truffleConfig.with(minefield); - //these jokers shouldn't be included - hits.forEach(hit => assert.equal(undefined, newConfig[hit])); + //one survivor + assert.strictEqual(expectedSurvivor, newConfig.who); + + //these jokers shouldn't be included + hits.forEach(hit => assert.strictEqual(undefined, newConfig[hit])); + }); }); - }); }); diff --git a/packages/core/lib/commands/develop/run.js b/packages/core/lib/commands/develop/run.js index 7522bdc77d9..d214f8ab466 100644 --- a/packages/core/lib/commands/develop/run.js +++ b/packages/core/lib/commands/develop/run.js @@ -45,14 +45,23 @@ module.exports = async options => { "This mnemonic was created for you by Truffle. It is not secure.\n" + "Ensure you do not use it on production blockchains, or else you risk losing funds."; - const ipcOptions = { log: options.log }; + const ipcOptions = {}; + + if (options.log) { + ipcOptions.log = options.log; + } + const ganacheOptions = configureManagedGanache( config, customConfig, mnemonic ); - const { started } = await Develop.connectOrStart(ipcOptions, ganacheOptions); + const { started } = await Develop.connectOrStart( + ipcOptions, + ganacheOptions, + config + ); const url = `http://${ganacheOptions.host}:${ganacheOptions.port}/`; if (started) { diff --git a/packages/core/lib/commands/migrate/runMigrations.js b/packages/core/lib/commands/migrate/runMigrations.js index 9f23c526a79..51c309ad6df 100644 --- a/packages/core/lib/commands/migrate/runMigrations.js +++ b/packages/core/lib/commands/migrate/runMigrations.js @@ -1,6 +1,110 @@ +const { createReadStream } = require("fs"); +const { readdir } = require("fs/promises"); +const path = require("path"); +const JSONStream = require("JSONStream"); const Migrate = require("@truffle/migrate").default; +const TruffleError = require("@truffle/error"); +const os = require("os"); +const debug = require("debug")("migrate:run"); + +// Search for Push1 (60) to Push(32) 7F + console.log +// 60 ---- 7F C O N S O L E . L O G +const consoleLogRex = /[67][0-9a-f]636F6e736F6c652e6c6f67/i; + +async function usesConsoleLog(artifactJson) { + const debugLog = debug.extend("test"); + debugLog("Artifact: %o", artifactJson); + + //create a parser to get the value of jsonpath .deployedBytecode + const parser = JSONStream.parse(["deployedBytecode"]); + const stream = createReadStream(artifactJson).pipe(parser); + + return new Promise((resolve, reject) => { + stream.on("data", data => { + //JSONParse will emit the entire string/value + //so initiate stream cleanup here + stream.destroy(); + const usesConsoleLog = consoleLogRex.test(data); + debugLog("usesConsoleLog:", usesConsoleLog); + resolve(usesConsoleLog); + }); + + stream.on("error", err => { + stream.destroy(); + debugLog("onError: %o", err); + reject(err); + }); + }); +} + +async function findArtifactsThatUseConsoleLog(buildDir) { + const debugLog = debug.extend("dirty-files"); + const filenames = await readdir(buildDir); + + const artifacts = []; + await Promise.allSettled( + filenames.map(async filename => { + if (filename.endsWith(".json")) { + try { + const itLogs = await usesConsoleLog(path.join(buildDir, filename)); + if (itLogs) { + artifacts.push(filename); + } + } catch (e) { + debugLog("Promise failure: %o", e.message); + } + } + }) + ); + return artifacts; +} module.exports = async function (config) { + const debugLog = debug.extend("guard"); + // only check if deploying on MAINNET + // NOTE: this includes Ethereum Classic as well as Ethereum as they're only + // distinguishable by checking their chainIds, 2 and 1 respectively. + if (config.network_id === 1) { + debugLog("solidityLog guard for mainnet"); + try { + const buildDir = config.contracts_build_directory; + const loggingArtifacts = await findArtifactsThatUseConsoleLog(buildDir); + + debugLog(`${loggingArtifacts.length} consoleLog artifacts detected`); + debugLog( + "config.solidityLog.preventConsoleLogMigration: " + + config.solidityLog.preventConsoleLogMigration + ); + + if (loggingArtifacts.length) { + console.warn( + `${os.EOL}Solidity console.log detected in the following assets:` + ); + console.warn(loggingArtifacts.join(", ")); + console.warn(); + + if (config.solidityLog.preventConsoleLogMigration) { + throw new TruffleError( + "You are trying to deploy contracts that use console.log." + + os.EOL + + "Please fix, or disable this check by setting solidityLog.preventConsoleLogMigration to false" + + os.EOL + ); + } + } + } catch (err) { + if (err instanceof TruffleError) throw err; + + debugLog("Unexpected error %o:", err); + // Something went wrong while inspecting for console log. + // Log warning and skip the remaining logic in this branch + console.warn(); + console.warn( + "Failed to detect Solidity console.log usage:" + os.EOL + err + ); + } + } + if (config.f) { return await Migrate.runFrom(config.f, config); } else { diff --git a/packages/core/lib/commands/test/run.js b/packages/core/lib/commands/test/run.js index 3faa9682523..c52ff4fe179 100644 --- a/packages/core/lib/commands/test/run.js +++ b/packages/core/lib/commands/test/run.js @@ -38,16 +38,21 @@ module.exports = async function (options) { } // Start managed ganache network - async function startGanacheAndRunTests(ipcOptions, ganacheOptions, config) { + async function startGanacheAndRunTests( + ipcOptions, + ganacheOptions, + truffleConfig + ) { const { disconnect } = await Develop.connectOrStart( ipcOptions, - ganacheOptions + ganacheOptions, + truffleConfig ); const ipcDisconnect = disconnect; - await Environment.develop(config, ganacheOptions); - const { temporaryDirectory } = await copyArtifactsToTempDir(config); + await Environment.develop(truffleConfig, ganacheOptions); + const { temporaryDirectory } = await copyArtifactsToTempDir(truffleConfig); const numberOfFailures = await prepareConfigAndRunTests({ - config, + config: truffleConfig, files, temporaryDirectory }); @@ -103,6 +108,7 @@ module.exports = async function (options) { ); const ipcOptions = { network: "test" }; + numberOfFailures = await startGanacheAndRunTests( ipcOptions, ganacheOptions, diff --git a/packages/core/lib/console.js b/packages/core/lib/console.js index c00b14144a6..d1a3bc60f3e 100644 --- a/packages/core/lib/console.js +++ b/packages/core/lib/console.js @@ -11,7 +11,7 @@ const TruffleError = require("@truffle/error"); const fse = require("fs-extra"); const path = require("path"); const EventEmitter = require("events"); -const spawnSync = require("child_process").spawnSync; +const { spawn } = require("child_process"); const Require = require("@truffle/require"); const debug = require("debug")("console"); const { getCommand } = require("./command-utils"); @@ -241,12 +241,10 @@ class Console extends EventEmitter { } provision() { - let files; + let files = []; + let jsonBlobs = []; try { - const unfilteredFiles = fse.readdirSync( - this.options.contracts_build_directory - ); - files = unfilteredFiles.filter(file => file.endsWith(".json")); + files = fse.readdirSync(this.options.contracts_build_directory); } catch (error) { // Error reading the build directory? Must mean it doesn't exist or we don't have access to it. // Couldn't provision the contracts if we wanted. It's possible we're hiding very rare FS @@ -254,10 +252,10 @@ class Console extends EventEmitter { // doesn't exist" 99.9% of the time. } - let jsonBlobs = []; - files = files || []; - files.forEach(file => { + // filter out non artifacts + if (!file.endsWith(".json")) return; + try { const body = fse.readFileSync( path.join(this.options.contracts_build_directory, file), @@ -314,7 +312,7 @@ class Console extends EventEmitter { }); } - runSpawn(inputStrings, options) { + async runSpawn(inputStrings, options) { let childPath; /* eslint-disable no-undef */ if (typeof BUNDLE_CONSOLE_CHILD_FILENAME !== "undefined") { @@ -323,10 +321,7 @@ class Console extends EventEmitter { childPath = path.join(__dirname, "../lib/console-child.js"); } - // stderr is piped here because we don't need to repeatedly see the parent - // errors/warnings in child process - specifically the error re: having - // multiple config files - const spawnOptions = { stdio: ["inherit", "inherit", "pipe"] }; + const spawnOptions = { stdio: "pipe" }; const settings = ["config", "network", "url"] .filter(setting => options[setting]) .map(setting => `--${setting} ${options[setting]}`) @@ -334,27 +329,47 @@ class Console extends EventEmitter { const spawnInput = `${settings} -- ${inputStrings}`; - const spawnResult = spawnSync( + const spawnedProcess = spawn( "node", ["--no-deprecation", childPath, spawnInput], spawnOptions ); - if (spawnResult.stderr) { - // Theoretically stderr can contain multiple errors. - // So let's just print it instead of throwing through - // the error handling mechanism. Bad call? - debug(spawnResult.stderr.toString()); - } + // Theoretically stderr can contain multiple errors. + // So let's just print it instead of throwing through + // the error handling mechanism. Bad call? Who knows... + // better be safe and buffer stderr so that it doesn't + // interrupt stdout, and present it as a complete + // string at the end of the spawned process. + let bufferedError = ""; + spawnedProcess.stderr.on("data", data => { + bufferedError += data.toString(); + }); - // re-provision to ensure any changes are available in the repl - this.provision(); + spawnedProcess.stdout.on("data", data => { + // remove extra newline in `truffle develop` console + console.log(data.toString().trim()); + }); + + return new Promise((resolve, reject) => { + spawnedProcess.on("close", code => { + // dump bufferedError + debug(bufferedError); + + if (!code) { + // re-provision to ensure any changes are available in the repl + this.provision(); - //display prompt when child repl process is finished - this.repl.displayPrompt(); + //display prompt when child repl process is finished + this.repl.displayPrompt(); + return void resolve(); + } + reject(code); + }); + }); } - interpret(input, context, filename, callback) { + async interpret(input, context, filename, callback) { const processedInput = processInput(input, this.allowedCommands); if ( this.allowedCommands.includes(processedInput.split(" ")[0]) && @@ -365,7 +380,7 @@ class Console extends EventEmitter { }) !== null ) { try { - this.runSpawn(processedInput, this.options); + await this.runSpawn(processedInput, this.options); } catch (error) { // Perform error handling ourselves. if (error instanceof TruffleError) { @@ -393,12 +408,12 @@ class Console extends EventEmitter { /* - allow whitespace before everything else - - optionally capture `var|let|const = ` - - varname only matches if it starts with a-Z or _ or $ + - optionally capture `var| let |const = ` + - varname only matches if it starts with a-Z or _ or $ and if contains only those chars or numbers - - this is overly restrictive but is easier to maintain - - capture `await ` - */ + - this is overly restrictive but is easier to maintain + - capture `await ` + */ let includesAwait = /^\s*((?:(?:var|const|let)\s+)?[a-zA-Z_$][0-9a-zA-Z_$]*\s*=\s*)?(\(?\s*await[\s\S]*)/; @@ -420,11 +435,11 @@ class Console extends EventEmitter { // Wrap the await inside an async function. // Strange indentation keeps column offset correct in stack traces - source = `(async function() { try { ${ + source = `(async function() { try {${ assign ? `global.${RESULT} =` : "return" } ( - ${expression.trim()} - ); } catch(e) { global.ERROR = e; throw e; } }())`; + ${expression.trim()} + ); } catch(e) {global.ERROR = e; throw e; } }())`; assignment = assign ? `${assign.trim()} global.${RESULT}; void delete global.${RESULT};` diff --git a/packages/core/lib/debug/compiler.js b/packages/core/lib/debug/compiler.js index ec94699c3e8..df082250d00 100644 --- a/packages/core/lib/debug/compiler.js +++ b/packages/core/lib/debug/compiler.js @@ -12,9 +12,7 @@ class DebugCompiler { let compileConfig = this.config.with({ quiet: true }); if (withTests) { - const testResolver = new Resolver(this.config, { - includeTruffleSources: true - }); + const testResolver = new Resolver(this.config); const testFiles = glob .sync(`${this.config.test_directory}/**/*.sol`) .map(filePath => path.resolve(filePath)); diff --git a/packages/core/package.json b/packages/core/package.json index 8594d59c166..20db463556b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -54,6 +54,7 @@ "@truffle/spinners": "^0.2.3", "@truffle/test": "^0.1.4", "@truffle/workflow-compile": "^4.0.43", + "JSONStream": "^1.3.5", "address": "^1.1.2", "chai": "^4.2.0", "colors": "1.4.0", diff --git a/packages/environment/chain.js b/packages/environment/chain.js index 587cd5aa9bf..6f38617d189 100644 --- a/packages/environment/chain.js +++ b/packages/environment/chain.js @@ -5,6 +5,7 @@ const IPC = require("node-ipc").IPC; const Ganache = require("ganache"); const path = require("path"); const debug = require("debug"); +const util = require("util"); /* * Loggers @@ -201,8 +202,9 @@ class LifecycleMixin { // constructor - accepts options for Ganache class GanacheMixin { - constructor(options) { + constructor(options, ganacheConsoleLogger) { this.ganache = Ganache.server(options); + this.ganacheConsoleLogger = ganacheConsoleLogger; } // start Ganache and capture promise that resolves when ready @@ -222,6 +224,15 @@ class GanacheMixin { connect(supervisor, socket) { this.ready.then(() => { supervisor.emit(socket, "truffle.ready"); + + // hook into ganache console.log events + this.ganache.provider.on("ganache:vm:tx:console.log", ({ logs }) => { + this.ganacheConsoleLogger.log( + // Format and colorize ganache log data which may or may not + // include a format string as the first argument. + util.formatWithOptions({ colors: true }, ...logs) + ); + }); }); } @@ -271,6 +282,7 @@ process.on("uncaughtException", ({ stack }) => { */ const ipcLogger = new Logger(); const ganacheLogger = new Logger(); +const ganacheConsoleLogger = new Logger(); const supervisor = new Supervisor({ appspace: "truffle.", @@ -282,9 +294,13 @@ const supervisor = new Supervisor({ ipcLogger.subscribe(ipcDebug); options.logger = { log: ganacheLogger.log.bind(ganacheLogger) }; +ganacheConsoleLogger.log = ganacheConsoleLogger.log.bind(ganacheConsoleLogger); +const ganacheMixin = new GanacheMixin(options, ganacheConsoleLogger); supervisor.use(new LifecycleMixin()); -supervisor.use(new GanacheMixin(options)); +supervisor.use(ganacheMixin); supervisor.use(new LoggerMixin(ipcLogger, "truffle.ipc.log")); supervisor.use(new LoggerMixin(ganacheLogger, "truffle.ganache.log")); +supervisor.use(new LoggerMixin(ganacheConsoleLogger, "truffle.solidity.log")); + supervisor.start(); diff --git a/packages/environment/develop.js b/packages/environment/develop.js index aefae1fe6fb..1bf35cc972a 100644 --- a/packages/environment/develop.js +++ b/packages/environment/develop.js @@ -2,9 +2,10 @@ const { IPC } = require("node-ipc"); const path = require("path"); const { spawn } = require("child_process"); const debug = require("debug"); +const chalk = require("chalk"); const Develop = { - start: async function (ipcNetwork, options = {}) { + start: async function (ipcNetwork, ganacheOptions = {}) { let chainPath; // The path to the dev env process depends on whether or not @@ -18,10 +19,11 @@ const Develop = { chainPath = path.join(__dirname, "./", "chain.js"); } - const logger = options.logger || console; - //check that genesis-time config option passed through the truffle-config.js file is a valid time. - if (options.time && isNaN(Date.parse(options.time))) { - options.time = Date.now(); + const logger = ganacheOptions.logger || console; + //check that genesis-time config option passed through the + //truffle-config.js file is a valid time. + if (ganacheOptions.time && isNaN(Date.parse(ganacheOptions.time))) { + ganacheOptions.time = Date.now(); logger.log( "\x1b[31m%s\x1b[0m", "Invalid Date passed to genesis-time, using current Date instead", @@ -29,7 +31,7 @@ const Develop = { ); } - const stringifiedOptions = JSON.stringify(options); + const stringifiedOptions = JSON.stringify(ganacheOptions); const optionsBuffer = Buffer.from(stringifiedOptions); const base64OptionsString = optionsBuffer.toString("base64"); @@ -39,18 +41,20 @@ const Develop = { }); }, - connect: function (options) { + connect: function (ipcOptions, truffleConfig) { const debugServer = debug("develop:ipc:server"); const debugClient = debug("develop:ipc:client"); const debugRPC = debug("develop:ganache"); - // make the `develop:ganache` debug prefix orange - // 215 is Xterm number for "SandyBrown" (#ffaf5f) - debugRPC.color = 215; + const ganacheColor = { + hex: "#ffaf5f", // ganache's color in hex + xterm: 215 // Xterm's number equivalent + }; + debugRPC.color = ganacheColor.xterm; - options.retry = options.retry || false; - options.log = options.log || false; - options.network = options.network || "develop"; - var ipcNetwork = options.network; + ipcOptions.retry = ipcOptions.retry || false; + ipcOptions.log = ipcOptions.log || false; + ipcOptions.network = ipcOptions.network || "develop"; + var ipcNetwork = ipcOptions.network; var ipc = new IPC(); ipc.config.appspace = "truffle."; @@ -59,22 +63,15 @@ const Develop = { var dirname = ipc.config.socketRoot; var basename = `${ipc.config.appspace}${ipcNetwork}`; var connectPath = path.join(dirname, basename); + var loggers = {}; ipc.config.silent = !debugClient.enabled; ipc.config.logger = debugClient; - var loggers = {}; - - if (debugServer.enabled) { - loggers.ipc = debugServer; - } - - if (options.log) { - debugRPC.enabled = true; - - loggers.ganache = function () { + const sanitizeAndCallFn = + fn => + (...args) => { // HACK-y: replace `{}` that is getting logged instead of "" - var args = Array.prototype.slice.call(arguments); if ( args.length === 1 && typeof args[0] === "object" && @@ -82,12 +79,39 @@ const Develop = { ) { args[0] = ""; } - - debugRPC.apply(undefined, args); + fn.apply(undefined, args); }; + + if (debugServer.enabled) { + loggers.ipc = debugServer; + } + + // create a logger to present Ganache's console log messages + const createSolidityLogger = prefix => { + if (prefix == null || typeof prefix !== "string") { + prefix = ""; + } + + return maybeMultipleLines => + maybeMultipleLines.split("\n").forEach( + // decorate each line's prefix. + line => console.log(chalk.hex(ganacheColor.hex)(` ${prefix}`), line) + ); + }; + + // enable output/logger for solidity console.log + loggers.solidity = sanitizeAndCallFn( + createSolidityLogger( + truffleConfig.solidityLog && truffleConfig.solidityLog.displayPrefix + ) + ); + + if (ipcOptions.log) { + debugRPC.enabled = true; + loggers.ganache = sanitizeAndCallFn(debugRPC); } - if (!options.retry) { + if (!ipcOptions.retry) { ipc.config.maxRetries = 0; } @@ -116,20 +140,31 @@ const Develop = { }); }, - connectOrStart: async function (options, ganacheOptions) { - options.retry = false; - - const ipcNetwork = options.network || "develop"; + /** + * Connect to a managed Ganache service. This will connect to an existing + * Ganache service if one exists, or, create a new one to connect to. + * + * @param {Object} ipcOptions + * @param {string} ipcOptions.network the network name. + * @param {Object} ganacheOptions to be used if starting a Ganache service is + * necessary. + * @param {TruffleConfig} truffleConfig the truffle config. + * @returns void + */ + connectOrStart: async function (ipcOptions, ganacheOptions, truffleConfig) { + ipcOptions.retry = false; + + const ipcNetwork = ipcOptions.network || "develop"; let started = false; let disconnect; try { - disconnect = await this.connect(options); + disconnect = await this.connect(ipcOptions, truffleConfig); } catch (_error) { await this.start(ipcNetwork, ganacheOptions); - options.retry = true; - disconnect = await this.connect(options); + ipcOptions.retry = true; + disconnect = await this.connect(ipcOptions, truffleConfig); started = true; } finally { return { diff --git a/packages/environment/package.json b/packages/environment/package.json index 5528a77fbd3..f44ac0ef1ba 100644 --- a/packages/environment/package.json +++ b/packages/environment/package.json @@ -21,18 +21,21 @@ }, "dependencies": { "@truffle/artifactor": "^4.0.177", + "@truffle/config": "^1.3.45", "@truffle/error": "^0.1.1", "@truffle/expect": "^0.1.4", "@truffle/interface-adapter": "^0.5.25", "@truffle/provider": "^0.2.64", "@truffle/resolver": "^9.0.25", "ganache": "7.5.0", + "chalk": "^4.1.2", "node-ipc": "9.2.1", "source-map-support": "^0.5.19", "web3": "1.7.4" }, "devDependencies": { - "debug": "^4.3.1" + "debug": "^4.3.1", + "mocha": "9.2.2" }, "publishConfig": { "access": "public" diff --git a/packages/environment/test/connectOrStartTest.js b/packages/environment/test/connectOrStartTest.js index e8b9d5de678..81c93863859 100644 --- a/packages/environment/test/connectOrStartTest.js +++ b/packages/environment/test/connectOrStartTest.js @@ -1,5 +1,6 @@ const assert = require("chai").assert; const Develop = require("../develop"); +const TruffleConfig = require("@truffle/config"); describe("connectOrStart test network", async function () { const ipcOptions = { network: "test" }; @@ -12,7 +13,11 @@ describe("connectOrStart test network", async function () { it("starts Ganache when no Ganache instance is running", async function () { let connection; try { - connection = await Develop.connectOrStart(ipcOptions, ganacheOptions); + connection = await Develop.connectOrStart( + ipcOptions, + ganacheOptions, + TruffleConfig.default() + ); assert.isTrue(connection.started, "A new Ganache server did not spin up"); assert.isFunction(connection.disconnect, "disconnect is not a function"); } finally { @@ -29,13 +34,20 @@ describe("connectOrStart test network", async function () { try { //Establish IPC Ganache service spawnedGanache = await Develop.start(ipcOptions.network, ganacheOptions); - connectionOneDisconnect = await Develop.connect({ - ...ipcOptions, - retry: true - }); + connectionOneDisconnect = await Develop.connect( + { + ...ipcOptions, + retry: true + }, + TruffleConfig.default() + ); //Test - connectionTwo = await Develop.connectOrStart(ipcOptions, ganacheOptions); + connectionTwo = await Develop.connectOrStart( + ipcOptions, + ganacheOptions, + TruffleConfig.default() + ); //Validate assert.isFalse(connectionTwo.started); diff --git a/packages/resolver/.eslintrc.json b/packages/resolver/.eslintrc.json new file mode 100644 index 00000000000..31fc6ca90a8 --- /dev/null +++ b/packages/resolver/.eslintrc.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../.eslintrc.package.json"] +} + diff --git a/packages/resolver/lib/resolver.ts b/packages/resolver/lib/resolver.ts index 845f9353af5..59fce9ef7c9 100644 --- a/packages/resolver/lib/resolver.ts +++ b/packages/resolver/lib/resolver.ts @@ -1,6 +1,3 @@ -import debugModule from "debug"; -const debug = debugModule("resolver"); - const contract = require("@truffle/contract"); const expect = require("@truffle/expect"); const provision = require("@truffle/provisioner"); @@ -16,7 +13,10 @@ export class Resolver { options: any; sources: ResolverSource[]; - constructor(options: any, resolverOptions: ResolverOptions = {}) { + constructor( + options: any, + resolverOptions: ResolverOptions = { includeTruffleSources: true } + ) { expect.options(options, [ "working_directory", "contracts_build_directory", @@ -53,15 +53,16 @@ export class Resolver { import_path: string, search_path?: string ): ReturnType { - let abstraction; - this.sources.forEach((source: ResolverSource) => { + for (const source of this.sources) { const result = source.require(import_path, search_path); if (result) { - abstraction = contract(result); + const abstraction = contract(result); provision(abstraction, this.options); + return abstraction; } - }); - if (abstraction) return abstraction; + } + + // exhausted sources and could not resolve throw new Error( "Could not find artifacts for " + import_path + " from any sources" ); diff --git a/packages/resolver/lib/sources/truffle/index.ts b/packages/resolver/lib/sources/truffle/index.ts index ac519c82020..389c602d5fc 100644 --- a/packages/resolver/lib/sources/truffle/index.ts +++ b/packages/resolver/lib/sources/truffle/index.ts @@ -4,7 +4,6 @@ import { Deployed } from "./Deployed"; import findContracts from "@truffle/contract-sources"; import type { ResolverSource } from "../../source"; const contract = require("@truffle/contract"); - export class Truffle implements ResolverSource { options: any; @@ -16,11 +15,14 @@ export class Truffle implements ResolverSource { if (importPath === `truffle${path.sep}DeployedAddresses.sol`) { const sourceFiles = await findContracts(this.options.contracts_directory); - const buildDirFiles: string[] = - fse.existsSync(this.options.contracts_build_directory) - ? fse.readdirSync(this.options.contracts_build_directory) - : []; - const abstractionFiles = buildDirFiles.filter(file => file.match(/^.*.json$/)); + const buildDirFiles: string[] = fse.existsSync( + this.options.contracts_build_directory + ) + ? fse.readdirSync(this.options.contracts_build_directory) + : []; + const abstractionFiles = buildDirFiles.filter(file => + file.match(/^.*.json$/) + ); const mapping: { [key: string]: string | false } = {}; @@ -30,7 +32,10 @@ export class Truffle implements ResolverSource { // to prevent any compile errors in tests. sourceFiles.forEach((file: string) => { // we need to account for .json and .abi.json files - const name = path.basename(path.basename(path.basename(file, ".sol"), ".json"), ".abi"); + const name = path.basename( + path.basename(path.basename(file, ".sol"), ".json"), + ".abi" + ); if (blacklist.has(name)) return; mapping[name] = false; }); @@ -70,6 +75,31 @@ export class Truffle implements ResolverSource { return { body: addressSource, filePath: importPath }; } + // Match both Camel and Pascal casing for console.sol + if ( + importPath === `truffle${path.sep}Console.sol` || + importPath === `truffle${path.sep}console.sol` + ) { + // calculating this in webpack env breaks + let unbundledGanacheConsoleSol; + // @ts-ignore + if (typeof BUNDLE_VERSION === "undefined") { + unbundledGanacheConsoleSol = path.resolve( + require.resolve("@ganache/console.log/console.sol") + ); + } + const actualImportPath = + // @ts-ignore + typeof BUNDLE_VERSION !== "undefined" + ? path.resolve(path.join(__dirname, `console.sol`)) + : unbundledGanacheConsoleSol; + const body = fse.readFileSync(actualImportPath, { encoding: "utf8" }); + return { + body, + filePath: importPath + }; + } + const truffleLibraries = [ "Assert", "AssertAddress", diff --git a/packages/resolver/package.json b/packages/resolver/package.json index 037bf9ac9a9..4efd0c76ec4 100644 --- a/packages/resolver/package.json +++ b/packages/resolver/package.json @@ -27,6 +27,7 @@ }, "types": "dist/lib/index.d.ts", "dependencies": { + "@ganache/console.log": "0.2.0", "@truffle/compile-solidity": "^6.0.52", "@truffle/contract": "^4.6.9", "@truffle/contract-sources": "^0.2.0", diff --git a/packages/resolver/test/truffle.js b/packages/resolver/test/truffle.js index 20fc6c7f847..63ce7337708 100644 --- a/packages/resolver/test/truffle.js +++ b/packages/resolver/test/truffle.js @@ -7,7 +7,6 @@ const { Truffle } = require("../dist/lib/sources/truffle"); const resolver = new Truffle({}); describe("truffle resolve", function () { - describe("assertion contracts", () => { [ "Assert", @@ -24,6 +23,8 @@ describe("truffle resolve", function () { "AssertUint", "AssertUintArray", "SafeSend", + "console", + "Console" ].forEach(lib => { it(`resolves truffle/${lib}.sol`, async () => { const dependency = path.join("truffle", `${lib}.sol`); @@ -32,7 +33,7 @@ describe("truffle resolve", function () { result.filePath.includes(dependency), `should have resovled 'truffle${path.sep}${lib}.sol'` ); - }) - }) + }); + }); }); }); diff --git a/packages/test/src/SolidityTest.ts b/packages/test/src/SolidityTest.ts index c6320d37440..1fba9495b2f 100644 --- a/packages/test/src/SolidityTest.ts +++ b/packages/test/src/SolidityTest.ts @@ -138,7 +138,8 @@ export const SolidityTest = { "truffle/AssertUint.sol", "truffle/AssertUintArray.sol", "truffle/DeployedAddresses.sol", - `truffle/SafeSend.sol` + "truffle/SafeSend.sol", + "truffle/Console.sol" ]; const { compilations } = await Compile.sourcesWithDependencies({ @@ -195,7 +196,8 @@ export const SolidityTest = { "AssertString", "AssertUint", "AssertUintArray", - "DeployedAddresses" + "DeployedAddresses", + "Console" ]; const testAbstractions = testLibraries.map(name => diff --git a/packages/test/src/Test.ts b/packages/test/src/Test.ts index f1335c5f2e1..3464a05c5c0 100644 --- a/packages/test/src/Test.ts +++ b/packages/test/src/Test.ts @@ -122,9 +122,7 @@ export const Test = { const accounts = await this.getAccounts(interfaceAdapter); - const testResolver = new Resolver(config, { - includeTruffleSources: true - }); + const testResolver = new Resolver(config); const { compilations } = await this.compileContractsWithTestFilesIfNeeded( solTests, diff --git a/packages/test/src/TestRunner.ts b/packages/test/src/TestRunner.ts index 98683d9131a..ef605f6b564 100644 --- a/packages/test/src/TestRunner.ts +++ b/packages/test/src/TestRunner.ts @@ -66,9 +66,7 @@ export class TestRunner { async initialize() { debug("initializing"); - this.config.resolver = new Resolver(this.config, { - includeTruffleSources: true - }); + this.config.resolver = new Resolver(this.config); if (this.firstSnapshot) { debug("taking first snapshot"); diff --git a/packages/truffle/package.json b/packages/truffle/package.json index 14d7b800b2c..bcfaa501fa1 100644 --- a/packages/truffle/package.json +++ b/packages/truffle/package.json @@ -42,6 +42,7 @@ "original-require": "^1.0.1" }, "devDependencies": { + "@ganache/console.log": "^0.2.0", "@truffle/box": "^2.1.65", "@truffle/config": "^1.3.45", "@truffle/contract": "^4.6.9", diff --git a/packages/truffle/test/scenarios/sandbox.js b/packages/truffle/test/scenarios/sandbox.js index 3f7820dabac..9205460f05e 100644 --- a/packages/truffle/test/scenarios/sandbox.js +++ b/packages/truffle/test/scenarios/sandbox.js @@ -17,7 +17,8 @@ module.exports = { ); return { config, - cleanupSandboxDir: tempDir.removeCallback + cleanupSandboxDir: tempDir.removeCallback, + tempDirPath: path.join(tempDir.name, subPath) }; }, diff --git a/packages/truffle/test/scenarios/solidity_console/Printf.sol b/packages/truffle/test/scenarios/solidity_console/Printf.sol new file mode 100644 index 00000000000..39e38f81778 --- /dev/null +++ b/packages/truffle/test/scenarios/solidity_console/Printf.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.22 <0.9.0; + +import "truffle/Console.sol"; + +contract Printf { + constructor() payable { } + + function log_string() view public { + console.log("String! I am not a twine!"); + } + + function log_unicode() view public { + console.log(unicode"This is unicode: ☮"); + } + + function log_address_o() view public { + console.log("The address is: %o", address(this)); + } + + function log_address_s() view public { + console.log("The address is: %s", address(this)); + } + + function log_multiline_carriageReturn() view public { + console.log("The cr: line 1\r line 2\rline 3"); + } + + function log_multiline_lineFeed() view public { + console.log("The lf: line 1\nline 2\nline 3"); + } + + function log_uint256() view public returns (uint256) { + console.log("The uint256: %d", address(this).balance); + return address(this).balance; + } +} diff --git a/packages/truffle/test/scenarios/solidity_console/printf.js b/packages/truffle/test/scenarios/solidity_console/printf.js new file mode 100644 index 00000000000..165fd660f12 --- /dev/null +++ b/packages/truffle/test/scenarios/solidity_console/printf.js @@ -0,0 +1,210 @@ +const MemoryLogger = require("../MemoryLogger"); +const CommandRunner = require("../commandRunner"); +const fs = require("fs-extra"); +const path = require("path"); +const assert = require("assert"); +const Ganache = require("ganache"); +const sandbox = require("../sandbox"); +const MemDown = require("memdown"); + +//prepare a helpful message to standout in CI log noise +const formatLines = lines => + lines + .split("\n") + .map(line => `\t---truffle develop log---\t${line}`) + .join("\n"); + +const copyFixtures = config => { + fs.copySync( + path.join(__dirname, "Printf.sol"), + path.join(config.contracts_directory, "Printf.sol") + ); +}; + +describe("Solidity console log [ @standalone ]", function () { + this.timeout(30000); + + let config, logger, output, provider; + let cleanupSandboxDir; + let tempDirPath; // to manipulate truffle-config.js on fs. + + before("set up sandbox", async () => { + ({ config, cleanupSandboxDir, tempDirPath } = await sandbox.create( + path.join(__dirname, "../../sources/init") + )); + + provider = Ganache.provider({ + miner: { instamine: "eager" }, + gasLimit: config.gasLimit, + database: { db: new MemDown() } + }); + + logger = new MemoryLogger(); + config.logger = logger; + config.networks = { development: { provider } }; + + copyFixtures(config); + }); + + after(async () => { + await cleanupSandboxDir(); + }); + + describe("Logging", async function () { + before("setup: interact with contract", async function () { + const input = [ + "compile", + "p = await Printf.new({value: 0x11235})", + "`Contract address is: ${p.address}`", + + "await p.log_string()", + "await p.log_unicode()", + "await p.log_address_s()", + "await p.log_address_o()", + "await p.log_multiline_carriageReturn()", + "await p.log_multiline_lineFeed()" + ]; + + await CommandRunner.runInREPL({ + inputCommands: input, + config, + executableCommand: "develop", + displayHost: "develop" + }); + + output = logger.contents().replace(/\x1b\[[0-9;]*m/g, ""); // strip terminal control codes + }); + + after(async () => { + provider && (await provider.disconnect()); + }); + + it("logs string", function () { + let expectedValue = ": String! I am not a twine!"; + + assert( + output.includes(expectedValue), + `Expected: "${expectedValue}" in output:\n${formatLines(output)}` + ); + }); + + it("logs unicode", () => { + let expectedValue = ": This is unicode: ☮"; + assert( + output.includes(expectedValue), + `Expected: "${expectedValue}" in output:\n${formatLines(output)}` + ); + }); + + it("logs address as %o", () => { + let expectedValue = ": The address is: 0x..."; + let rex = /: The address is: '0x[\da-f]{40}'/; + assert( + rex.test(output), + `Expected: "${expectedValue}" in output:\n${formatLines(output)}` + ); + }); + + it("Logs address as %s", () => { + let expectedValue = ": The address is: 0x..."; + let rex = /: The address is: 0x[\da-f]{40}/; + assert( + rex.test(output), + `Expected: "${expectedValue}" in output:\n${formatLines(output)}` + ); + }); + + it("Logs multiline strings with carriage-return", () => { + let rex = /: The cr: line 1. line 2.line 3/is; + assert( + rex.test(output), + `Expected: a string with carriage return in output:\n${formatLines( + output + )}` + ); + }); + + it("Logs multiline strings with linefeed", () => { + let rex1 = /: The lf: line 1/; + let rex2 = /: line 2/; + let rex3 = /: line 3/; + assert( + rex1.test(output) && rex2.test(output) && rex3.test(output), + `Expected: a string with linefeeds in output:\n${formatLines(output)}` + ); + }); + }); + + describe("Migration", async function () { + this.timeout(30000); + let server; + let debugEnv = ""; + + before("setup: interact with contract", async function () { + server = Ganache.server({ + chain: { chainId: 1, networkId: 1 }, + miner: { instamine: "eager" }, + database: { db: new MemDown() }, + logging: { quiet: true } + }); + await server.listen(7545); + + copyFixtures(config); + }); + + after(async () => { + await server.close(); + }); + + it("MAINNET preventConsoleLogMigration: false", async function () { + try { + logger = new MemoryLogger(); + config.logger = logger; + // setup new config file + await fs.copy( + path.join(tempDirPath, "config-disable-migrate-false.js"), + path.join(tempDirPath, "truffle-config.js") + ); + await CommandRunner.run("migrate --network mainnet", config, debugEnv); + assert(logger.includes("Total deployments: 1")); + } catch (error) { + console.log("ERROR: %o", error); + assert.fail("Migration should have succeeded!"); + } + }); + + it("MAINNET preventConsoleLogMigration: true", async function () { + try { + logger = new MemoryLogger(); + config.logger = logger; + // setup new config file + await fs.copy( + path.join(tempDirPath, "config-disable-migrate-true.js"), + path.join(tempDirPath, "truffle-config.js") + ); + await CommandRunner.run( + "migrate --network mainnet --reset --skip-dry-run", + config, + debugEnv + ); + assert.fail("Migration should have failed"); + } catch (error) { + const output = logger.contents(); + const summary = + /Solidity console.log detected in the following assets:.+Printf.json/ms; + assert( + summary.test(output), + "Should list the contract using console.log" + ); + const action1 = + /You are trying to deploy contracts that use console.log./; + + assert(action1.test(output), "Should suggest action"); + + const action2 = + /Please fix, or disable this check by setting.+preventConsoleLogMigration to false/; + assert(action2.test(output), "Should suggest action"); + } + }); + }); +}); diff --git a/packages/truffle/test/sources/init/config-disable-migrate-false.js b/packages/truffle/test/sources/init/config-disable-migrate-false.js new file mode 100644 index 00000000000..defd75b2b39 --- /dev/null +++ b/packages/truffle/test/sources/init/config-disable-migrate-false.js @@ -0,0 +1,20 @@ +module.exports = { + solidityLog: { + displayPrefix: ": ", + preventConsoleLogMigration: false + }, + + networks: { + mainnet: { + network_id: 1, + host: "127.0.0.1", + port: 7545 + } + }, + + compilers: { + solc: { + version: "0.8.13" // Fetch exact version from solc-bin (default: truffle's version) + } + } +}; diff --git a/packages/truffle/test/sources/init/config-disable-migrate-true.js b/packages/truffle/test/sources/init/config-disable-migrate-true.js new file mode 100644 index 00000000000..f72da6a3ab5 --- /dev/null +++ b/packages/truffle/test/sources/init/config-disable-migrate-true.js @@ -0,0 +1,20 @@ +module.exports = { + solidityLog: { + displayPrefix: ": ", + preventConsoleLogMigration: true + }, + + networks: { + mainnet: { + network_id: 1, + host: "127.0.0.1", + port: 7545 + } + }, + + compilers: { + solc: { + version: "0.8.13" // Fetch exact version from solc-bin (default: truffle's version) + } + } +}; diff --git a/packages/truffle/test/sources/init/truffle-config.js b/packages/truffle/test/sources/init/truffle-config.js index 79506525351..79c93fd999e 100644 --- a/packages/truffle/test/sources/init/truffle-config.js +++ b/packages/truffle/test/sources/init/truffle-config.js @@ -1,4 +1,16 @@ module.exports = { + solidityLog: { + displayPrefix: ": " + }, + + networks: { + mainnet: { + network_id: 1, + host: "127.0.0.1", + port: 7545 + } + }, + compilers: { solc: { version: "0.8.13" // Fetch exact version from solc-bin (default: truffle's version) diff --git a/packages/truffle/webpack.config.js b/packages/truffle/webpack.config.js index 3809396c42c..b5fb9e61c33 100644 --- a/packages/truffle/webpack.config.js +++ b/packages/truffle/webpack.config.js @@ -25,6 +25,8 @@ const truffleRequireDistDirectory = path.join( "dist" ); +const ganacheConsoleSol = require.resolve("@ganache/console.log/console.sol"); + const commandsEntries = commands.reduce((a, command) => { a[command] = path.join( __dirname, @@ -175,7 +177,7 @@ module.exports = { "bn.js" ), "original-fs": path.join(__dirname, "./nil.js"), - scrypt: "js-scrypt" + "scrypt": "js-scrypt" } }, @@ -213,6 +215,9 @@ module.exports = { ), to: "initSource" }, + { + from: ganacheConsoleSol + }, { from: path.join(truffleLibraryDirectory, "Assert.sol") }, diff --git a/yarn.lock b/yarn.lock index 8ea33a7c7e8..8c6d88159b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3706,6 +3706,14 @@ "@floating-ui/dom" "^0.5.3" use-isomorphic-layout-effect "^1.1.1" +"@ganache/console.log@0.2.0", "@ganache/console.log@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@ganache/console.log/-/console.log-0.2.0.tgz#32ea0df806ed735d61bd0537d7b7fc350e511479" + integrity sha512-+SNBUZzrbe4DE4F0jdl9SU8w3ek5k4cUE73ttUFweo8FaKEDQsMbFjZ3ZU0LM6QM/zCMqE7euSq0s/IlsYxf7A== + dependencies: + "@ganache/utils" "0.3.0" + ethereumjs-util "7.1.5" + "@ganache/ethereum-address@0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@ganache/ethereum-address/-/ethereum-address-0.1.5.tgz#4b4994cf3b49b2f79bcd9fa9923f8217e956b99c" @@ -3854,6 +3862,17 @@ optionalDependencies: "@trufflesuite/bigint-buffer" "1.1.9" +"@ganache/utils@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@ganache/utils/-/utils-0.3.0.tgz#f95d7a4746d4e062febf3ce59f65f6ca1336be8a" + integrity sha512-cxoG8KQxkYPl71BPdKZihjVKqN2AE7WLXjU65BVOQ5jEYrUH3CWSxA9v7CCUJj4e0HoXFpVFIZ+1HRkiBKKiKg== + dependencies: + emittery "0.10.0" + keccak "3.0.1" + seedrandom "3.0.5" + optionalDependencies: + "@trufflesuite/bigint-buffer" "1.1.9" + "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -13086,6 +13105,17 @@ ethereumjs-util@7.1.3: ethereum-cryptography "^0.1.3" rlp "^2.2.4" +ethereumjs-util@7.1.5, ethereumjs-util@^7.1.5: + version "7.1.5" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" + integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + rlp "^2.2.4" + ethereumjs-util@^4.3.0: version "4.5.1" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz#f4bf9b3b515a484e3cc8781d61d9d980f7c83bd0" @@ -13171,17 +13201,6 @@ ethereumjs-util@^7.1.1, ethereumjs-util@^7.1.3, ethereumjs-util@^7.1.4: ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethereumjs-util@^7.1.5: - version "7.1.5" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" - integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg== - dependencies: - "@types/bn.js" "^5.1.0" - bn.js "^5.1.2" - create-hash "^1.1.2" - ethereum-cryptography "^0.1.3" - rlp "^2.2.4" - ethereumjs-vm@^2.3.4: version "2.6.0" resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6"