Skip to content

Commit ce6a524

Browse files
committed
Attach to non-fork ganache vm (#391)
1 parent 07ca4d3 commit ce6a524

File tree

4 files changed

+74
-7
lines changed

4 files changed

+74
-7
lines changed

dist/truffle.plugin.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ const path = require('path');
3232
const dir = require('node-dir');
3333
const Web3 = require('web3');
3434
const util = require('util');
35-
const ganache = require('ganache-core-sc');
3635
const globby = require('globby');
3736

3837
async function plugin(truffleConfig){
@@ -70,7 +69,7 @@ async function plugin(truffleConfig){
7069
death(app.cleanUp);
7170

7271
// Launch in-process provider
73-
const provider = await app.provider(ganache);
72+
const provider = await app.provider(truffle.ganache);
7473
const web3 = new Web3(provider);
7574
const accounts = await web3.eth.getAccounts();
7675
const nodeInfo = await web3.eth.getNodeInfo();
@@ -153,6 +152,8 @@ function loadTruffleLibrary(){
153152
try { return require("./truffle.library")} catch(err) {};
154153

155154
// TO DO: throw error? This point should never be reached.
155+
// Validate that truffle.ganache exists? Have checked that
156+
// a non-existent prop defaults to the ganache-core-sc fallback FWIW.
156157
}
157158

158159
/**

lib/app.js

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const fs = require('fs');
33
const path = require('path');
44
const istanbul = require('istanbul');
55
const util = require('util');
6+
const assert = require('assert');
67

78
const Instrumenter = require('./instrumenter');
89
const Coverage = require('./coverage');
@@ -113,15 +114,31 @@ class App {
113114
*
114115
* TODO: generalize provider options setting for non-ganache clients..
115116
*/
116-
provider(client){
117-
if(!this.client) this.client = client;
117+
async provider(client){
118+
let retry = false;
119+
120+
if(!this.client) this.client = client; // Prefer client from options
121+
118122
this.collector = new DataCollector(this.instrumenter.instrumentationData);
119123

120124
this.providerOptions.gasLimit = this.gasLimitString;
121125
this.providerOptions.allowUnlimitedContractSize = true;
122-
this.providerOptions.logger = { log: this.collector.step.bind(this.collector) };
123126

124-
this.provider = this.client.provider(this.providerOptions);
127+
// Try to launch provider and attach to vm step of
128+
// either plugin's ganache or a provider passed via options
129+
try {
130+
this.provider = await this.attachToVM();
131+
} catch(err){
132+
retry = true;
133+
this.ui.report('vm-fail', [])
134+
}
135+
136+
// Fallback to ganache-core-sc (eq: ganache-core 2.7.0)
137+
if (retry){
138+
this.providerOptions.logger = { log: this.collector.step.bind(this.collector) };
139+
this.client = require('ganache-core-sc');
140+
this.provider = this.client.provider(this.providerOptions);
141+
}
125142

126143
return this.provider;
127144
}
@@ -176,6 +193,50 @@ class App {
176193
}
177194
// ------------------------------------------ Utils ----------------------------------------------
178195

196+
// ========
197+
// Provider
198+
// ========
199+
async attachToVM(){
200+
const self = this;
201+
const provider = this.client.provider(this.providerOptions);
202+
203+
this.assertHasBlockchain(provider);
204+
205+
await this.vmIsResolved(provider);
206+
207+
const blockchain = provider.engine.manager.state.blockchain;
208+
const createVM = blockchain.createVMFromStateTrie;
209+
210+
// Attach to VM which ganache has already instantiated
211+
// and which it uses to execute eth_send
212+
blockchain.vm.on('step', self.collector.step.bind(self.collector));
213+
214+
// Attach/hijack createVM method which ganache uses to run eth_calls
215+
blockchain.createVMFromStateTrie = function(state, activatePrecompiles) {
216+
const vm = createVM.apply(blockchain, arguments);
217+
vm.on('step', self.collector.step.bind(self.collector));
218+
return vm;
219+
}
220+
221+
return provider;
222+
}
223+
224+
assertHasBlockchain(provider){
225+
assert(provider.engine.manager.state.blockchain !== undefined);
226+
assert(provider.engine.manager.state.blockchain.createVMFromStateTrie !== undefined);
227+
}
228+
229+
async vmIsResolved(provider){
230+
return new Promise(resolve => {
231+
const interval = setInterval(() => {
232+
if (provider.engine.manager.state.blockchain.vm !== undefined){
233+
clearInterval(interval);
234+
resolve();
235+
}
236+
});
237+
})
238+
}
239+
179240
// ========
180241
// File I/O
181242
// ========

lib/ui.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class UI {
2222
const ds = c.bold.yellow('>');
2323

2424
const kinds = {
25+
'vm-fail': `:warning: ${c.red('There was a problem attaching to the ganache-core VM.')} `+
26+
`${c.red('Check the provider option syntax in solidity-coverage docs.')}\n`+
27+
`:warning: ${c.red('Using ganache-core-sc (eq. core v2.7.0) instead.')}\n`,
2528

2629
'truffle-help': `Usage: truffle run coverage [options]\n\n` +
2730
`Options:\n` +

test/units/app.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ describe('app', function() {
100100
await plugin(truffleConfig);
101101
});
102102

103-
it('project uses multiple migrations', async function() {
103+
// This project has three contract suites and uses .deployed() instances which
104+
// depend on truffle's migratons and the inter-test evm_revert / evm_snapshot mechanism.
105+
it('project evm_reverts repeatedly', async function() {
104106
assertCleanInitialState();
105107
mock.installFullProject('multiple-migrations');
106108
await plugin(truffleConfig);

0 commit comments

Comments
 (0)