From ce6a524ffc02994d4fc813550f4726ce49e74755 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Mon, 9 Sep 2019 23:35:29 -0700 Subject: [PATCH] Attach to non-fork ganache vm (#391) --- dist/truffle.plugin.js | 5 +-- lib/app.js | 69 +++++++++++++++++++++++++++++++++++++++--- lib/ui.js | 3 ++ test/units/app.js | 4 ++- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/dist/truffle.plugin.js b/dist/truffle.plugin.js index d7b762b..85d8bb0 100644 --- a/dist/truffle.plugin.js +++ b/dist/truffle.plugin.js @@ -32,7 +32,6 @@ const path = require('path'); const dir = require('node-dir'); const Web3 = require('web3'); const util = require('util'); -const ganache = require('ganache-core-sc'); const globby = require('globby'); async function plugin(truffleConfig){ @@ -70,7 +69,7 @@ async function plugin(truffleConfig){ death(app.cleanUp); // Launch in-process provider - const provider = await app.provider(ganache); + const provider = await app.provider(truffle.ganache); const web3 = new Web3(provider); const accounts = await web3.eth.getAccounts(); const nodeInfo = await web3.eth.getNodeInfo(); @@ -153,6 +152,8 @@ function loadTruffleLibrary(){ try { return require("./truffle.library")} catch(err) {}; // TO DO: throw error? This point should never be reached. + // Validate that truffle.ganache exists? Have checked that + // a non-existent prop defaults to the ganache-core-sc fallback FWIW. } /** diff --git a/lib/app.js b/lib/app.js index 986282d..57dff5d 100644 --- a/lib/app.js +++ b/lib/app.js @@ -3,6 +3,7 @@ const fs = require('fs'); const path = require('path'); const istanbul = require('istanbul'); const util = require('util'); +const assert = require('assert'); const Instrumenter = require('./instrumenter'); const Coverage = require('./coverage'); @@ -113,15 +114,31 @@ class App { * * TODO: generalize provider options setting for non-ganache clients.. */ - provider(client){ - if(!this.client) this.client = client; + async provider(client){ + let retry = false; + + if(!this.client) this.client = client; // Prefer client from options + this.collector = new DataCollector(this.instrumenter.instrumentationData); this.providerOptions.gasLimit = this.gasLimitString; this.providerOptions.allowUnlimitedContractSize = true; - this.providerOptions.logger = { log: this.collector.step.bind(this.collector) }; - this.provider = this.client.provider(this.providerOptions); + // Try to launch provider and attach to vm step of + // either plugin's ganache or a provider passed via options + try { + this.provider = await this.attachToVM(); + } catch(err){ + retry = true; + this.ui.report('vm-fail', []) + } + + // Fallback to ganache-core-sc (eq: ganache-core 2.7.0) + if (retry){ + this.providerOptions.logger = { log: this.collector.step.bind(this.collector) }; + this.client = require('ganache-core-sc'); + this.provider = this.client.provider(this.providerOptions); + } return this.provider; } @@ -176,6 +193,50 @@ class App { } // ------------------------------------------ Utils ---------------------------------------------- + // ======== + // Provider + // ======== + async attachToVM(){ + const self = this; + const provider = this.client.provider(this.providerOptions); + + this.assertHasBlockchain(provider); + + await this.vmIsResolved(provider); + + const blockchain = provider.engine.manager.state.blockchain; + const createVM = blockchain.createVMFromStateTrie; + + // Attach to VM which ganache has already instantiated + // and which it uses to execute eth_send + blockchain.vm.on('step', self.collector.step.bind(self.collector)); + + // Attach/hijack createVM method which ganache uses to run eth_calls + blockchain.createVMFromStateTrie = function(state, activatePrecompiles) { + const vm = createVM.apply(blockchain, arguments); + vm.on('step', self.collector.step.bind(self.collector)); + return vm; + } + + return provider; + } + + assertHasBlockchain(provider){ + assert(provider.engine.manager.state.blockchain !== undefined); + assert(provider.engine.manager.state.blockchain.createVMFromStateTrie !== undefined); + } + + async vmIsResolved(provider){ + return new Promise(resolve => { + const interval = setInterval(() => { + if (provider.engine.manager.state.blockchain.vm !== undefined){ + clearInterval(interval); + resolve(); + } + }); + }) + } + // ======== // File I/O // ======== diff --git a/lib/ui.js b/lib/ui.js index c396808..4ec4142 100644 --- a/lib/ui.js +++ b/lib/ui.js @@ -22,6 +22,9 @@ class UI { const ds = c.bold.yellow('>'); const kinds = { + 'vm-fail': `:warning: ${c.red('There was a problem attaching to the ganache-core VM.')} `+ + `${c.red('Check the provider option syntax in solidity-coverage docs.')}\n`+ + `:warning: ${c.red('Using ganache-core-sc (eq. core v2.7.0) instead.')}\n`, 'truffle-help': `Usage: truffle run coverage [options]\n\n` + `Options:\n` + diff --git a/test/units/app.js b/test/units/app.js index 3ebaaa3..1323806 100644 --- a/test/units/app.js +++ b/test/units/app.js @@ -100,7 +100,9 @@ describe('app', function() { await plugin(truffleConfig); }); - it('project uses multiple migrations', async function() { + // This project has three contract suites and uses .deployed() instances which + // depend on truffle's migratons and the inter-test evm_revert / evm_snapshot mechanism. + it('project evm_reverts repeatedly', async function() { assertCleanInitialState(); mock.installFullProject('multiple-migrations'); await plugin(truffleConfig);