Merge pull request #31 from JoinColony/PR30-redux
Start to add proper tests i.e. making sure coverage is as expected. Closes #26 Closes #27 Has a known issue regarding highlighting (see #32).uport
commit
e810429ddb
@ -1,3 +1,8 @@ |
|||||||
|
machine: |
||||||
|
node: |
||||||
|
version: 6.9.1 |
||||||
dependencies: |
dependencies: |
||||||
pre: |
pre: |
||||||
- npm install -g truffle |
- npm install -g truffle |
||||||
|
- rm -rf node_modules/ |
||||||
|
|
@ -0,0 +1,92 @@ |
|||||||
|
|
||||||
|
/** |
||||||
|
* This file contains methods that produce a coverage map to pass to instanbul |
||||||
|
* from data generated by `instrumentSolidity.js` |
||||||
|
*/ |
||||||
|
const SolidityCoder = require('web3/lib/solidity/coder.js'); |
||||||
|
const path = require('path'); |
||||||
|
|
||||||
|
const lineTopic = 'b8995a65f405d9756b41a334f38d8ff0c93c4934e170d3c1429c3e7ca101014d'; |
||||||
|
const functionTopic = 'd4ce765fd23c5cc3660249353d61ecd18ca60549dd62cb9ca350a4244de7b87f'; |
||||||
|
const branchTopic = 'd4cf56ed5ba572684f02f889f12ac42d9583c8e3097802060e949bfbb3c1bff5'; |
||||||
|
const statementTopic = 'b51abbff580b3a34bbc725f2dc6f736e9d4b45a41293fd0084ad865a31fde0c8'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts solcover event data into an object that can be |
||||||
|
* be passed to instanbul to produce coverage reports. |
||||||
|
* @type {CoverageMap} |
||||||
|
*/ |
||||||
|
module.exports = class CoverageMap { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.coverage = {}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes a coverage map object for contract instrumented per `info` and located |
||||||
|
* at `canonicalContractPath` |
||||||
|
* @param {Object} info `info = getIntrumentedVersion(contract, fileName, true)` |
||||||
|
* @param {String} canonicalContractPath target file location |
||||||
|
* @return {Object} coverage map with all values set to zero |
||||||
|
*/ |
||||||
|
addContract(info, canonicalContractPath) { |
||||||
|
this.coverage[canonicalContractPath] = { |
||||||
|
l: {}, |
||||||
|
path: canonicalContractPath, |
||||||
|
s: {}, |
||||||
|
b: {}, |
||||||
|
f: {}, |
||||||
|
fnMap: {}, |
||||||
|
statementMap: {}, |
||||||
|
branchMap: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
info.runnableLines.forEach((item, idx) => { |
||||||
|
this.coverage[canonicalContractPath].l[info.runnableLines[idx]] = 0; |
||||||
|
}); |
||||||
|
this.coverage[canonicalContractPath].fnMap = info.fnMap; |
||||||
|
for (let x = 1; x <= Object.keys(info.fnMap).length; x++) { |
||||||
|
this.coverage[canonicalContractPath].f[x] = 0; |
||||||
|
} |
||||||
|
this.coverage[canonicalContractPath].branchMap = info.branchMap; |
||||||
|
for (let x = 1; x <= Object.keys(info.branchMap).length; x++) { |
||||||
|
this.coverage[canonicalContractPath].b[x] = [0, 0]; |
||||||
|
} |
||||||
|
this.coverage[canonicalContractPath].statementMap = info.statementMap; |
||||||
|
for (let x = 1; x <= Object.keys(info.statementMap).length; x++) { |
||||||
|
this.coverage[canonicalContractPath].s[x] = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Populates an empty coverage map with values derived from an array of events |
||||||
|
* fired by instrumented contracts as they are tested |
||||||
|
* @param {Array} events |
||||||
|
* @param {String} relative path to host contracts eg: './../contracts' |
||||||
|
* @return {Object} coverage map. |
||||||
|
*/ |
||||||
|
generate(events, pathPrefix) { |
||||||
|
for (let idx = 0; idx < events.length; idx++) { |
||||||
|
const event = JSON.parse(events[idx]); |
||||||
|
if (event.topics.indexOf(lineTopic) >= 0) { |
||||||
|
const data = SolidityCoder.decodeParams(['string', 'uint256'], event.data.replace('0x', '')); |
||||||
|
const canonicalContractPath = path.resolve(pathPrefix + path.basename(data[0])); |
||||||
|
this.coverage[canonicalContractPath].l[data[1].toNumber()] += 1; |
||||||
|
} else if (event.topics.indexOf(functionTopic) >= 0) { |
||||||
|
const data = SolidityCoder.decodeParams(['string', 'uint256'], event.data.replace('0x', '')); |
||||||
|
const canonicalContractPath = path.resolve(pathPrefix + path.basename(data[0])); |
||||||
|
this.coverage[canonicalContractPath].f[data[1].toNumber()] += 1; |
||||||
|
} else if (event.topics.indexOf(branchTopic) >= 0) { |
||||||
|
const data = SolidityCoder.decodeParams(['string', 'uint256', 'uint256'], event.data.replace('0x', '')); |
||||||
|
const canonicalContractPath = path.resolve(pathPrefix + path.basename(data[0])); |
||||||
|
this.coverage[canonicalContractPath].b[data[1].toNumber()][data[2].toNumber()] += 1; |
||||||
|
} else if (event.topics.indexOf(statementTopic) >= 0) { |
||||||
|
const data = SolidityCoder.decodeParams(['string', 'uint256'], event.data.replace('0x', '')); |
||||||
|
const canonicalContractPath = path.resolve(pathPrefix + path.basename(data[0])); |
||||||
|
this.coverage[canonicalContractPath].s[data[1].toNumber()] += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return Object.assign({}, this.coverage); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
@ -0,0 +1,56 @@ |
|||||||
|
const SolExplore = require('sol-explore'); |
||||||
|
const SolidityParser = require('solidity-parser'); |
||||||
|
|
||||||
|
/** |
||||||
|
* Splices enclosing brackets into `contract` around `expression`; |
||||||
|
* @param {String} contract solidity code |
||||||
|
* @param {Object} node AST node to bracket |
||||||
|
* @return {String} contract |
||||||
|
*/ |
||||||
|
function blockWrap(contract, expression) { |
||||||
|
return contract.slice(0, expression.start) + '{' + contract.slice(expression.start, expression.end) + '}' + contract.slice(expression.end); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Locates unbracketed singleton statements attached to if, else, for and while statements |
||||||
|
* and brackets them. Instrumenter needs to inject events at these locations and having |
||||||
|
* them pre-bracketed simplifies the process. Each time a modification is made the contract |
||||||
|
* is passed back to the parser and re-walked because all the starts and ends get shifted. |
||||||
|
* @param {String} contract solidity code |
||||||
|
* @return {String} contract |
||||||
|
*/ |
||||||
|
module.exports.run = function r(contract) { |
||||||
|
let keepRunning = true; |
||||||
|
|
||||||
|
while (keepRunning) { |
||||||
|
const ast = SolidityParser.parse(contract); |
||||||
|
keepRunning = false; |
||||||
|
SolExplore.traverse(ast, { |
||||||
|
enter(node, parent) { |
||||||
|
// If consequents
|
||||||
|
if (node.type === 'IfStatement' && node.consequent.type !== 'BlockStatement') { |
||||||
|
contract = blockWrap(contract, node.consequent); |
||||||
|
keepRunning = true; |
||||||
|
this.stopTraversal(); |
||||||
|
// If alternates
|
||||||
|
} else if ( |
||||||
|
node.type === 'IfStatement' && |
||||||
|
node.alternate && |
||||||
|
node.alternate.type !== 'IfStatement' && |
||||||
|
node.alternate.type !== 'BlockStatement') { |
||||||
|
contract = blockWrap(contract, node.alternate); |
||||||
|
keepRunning = true; |
||||||
|
this.stopTraversal(); |
||||||
|
// Loops
|
||||||
|
} else if ( |
||||||
|
(node.type === 'ForStatement' || node.type === 'WhileStatement') && |
||||||
|
node.body.type !== 'BlockStatement') { |
||||||
|
contract = blockWrap(contract, node.body); |
||||||
|
keepRunning = true; |
||||||
|
this.stopTraversal(); |
||||||
|
} |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
|
return contract; |
||||||
|
}; |
@ -1,44 +1,149 @@ |
|||||||
var solc = require('solc'); |
const solc = require('solc'); |
||||||
var getInstrumentedVersion = require('./../instrumentSolidity.js'); |
const path = require('path'); |
||||||
var util = require('./util/util.js') |
const getInstrumentedVersion = require('./../instrumentSolidity.js'); |
||||||
|
const util = require('./util/util.js'); |
||||||
|
const CoverageMap = require('./../coverageMap'); |
||||||
|
const vm = require('./util/vm'); |
||||||
|
const assert = require('assert'); |
||||||
|
|
||||||
/** |
|
||||||
* NB: passing '1' to solc as an option activates the optimiser |
|
||||||
*/ |
|
||||||
describe('if, else, and else if statements', function(){ |
describe('if, else, and else if statements', function(){ |
||||||
|
|
||||||
it('should compile after instrumenting else statements with brackets',function(){ |
const fileName = 'test.sol'; |
||||||
var contract = util.getCode('if/else-with-brackets.sol'); |
const filePath = path.resolve('./test.sol'); |
||||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
const pathPrefix = './'; |
||||||
var output = solc.compile(info.contract, 1);
|
|
||||||
util.report(output.errors); |
it('should cover an if statement with a bracketed consequent', (done) => { |
||||||
|
const contract = util.getCode('if/if-with-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a(1) => if (x == 1) { x = 3; }
|
||||||
|
vm.execute(info.contract, 'a', [1]).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {1: [1, 0]}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// Runs: a(1) => if (x == 1) x = 2;
|
||||||
|
it('should cover an unbracketed if consequent (single line)',function(done){ |
||||||
|
const contract = util.getCode('if/if-no-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Same results as previous test
|
||||||
|
vm.execute(info.contract, 'a', [1]).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {1: [1, 0]}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}).catch(err => {console.log(err); done() }) |
||||||
}) |
}) |
||||||
|
|
||||||
it('should compile after instrumenting else statements without brackets',function(){ |
it('should cover an if statement with multiline bracketed consequent', (done) => { |
||||||
var contract = util.getCode('if/else-without-brackets.sol'); |
const contract = util.getCode('if/if-with-brackets-multiline.sol'); |
||||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
var output = solc.compile(info.contract, 1);
|
const coverage = new CoverageMap(); |
||||||
util.report(output.errors); |
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a(1) => if (x == 1){\n x = 3; }
|
||||||
|
vm.execute(info.contract, 'a', [1]).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {1: [1, 0]}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// Runs: a(1) => if (x == 1)\n x = 3;
|
||||||
|
it('should cover an unbracketed if consequent (multi-line)', function(done){ |
||||||
|
const contract = util.getCode('if/if-no-brackets-multiline.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
// Same results as previous test
|
||||||
|
vm.execute(info.contract, 'a', [1]).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {1: [1, 0]}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}) |
||||||
}) |
}) |
||||||
|
|
||||||
it('should compile after instrumenting if statements with no brackets',function(){ |
it('should cover a simple if statement with a failing condition', (done) => { |
||||||
var contract = util.getCode('if/if-no-brackets.sol'); |
const contract = util.getCode('if/if-with-brackets.sol'); |
||||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
var output = solc.compile(info.contract, 1);
|
const coverage = new CoverageMap(); |
||||||
util.report(output.errors); |
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a(2) => if (x == 1) { x = 3; }
|
||||||
|
vm.execute(info.contract, 'a', [2]).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {1: [0, 1]}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 0}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}).catch(err => {console.log(err); done() }) |
||||||
|
}); |
||||||
|
|
||||||
|
// Runs: a(2) => if (x == 1){\n throw;\n }else{\n x = 5; \n}
|
||||||
|
it('should cover an if statement with a bracketed alternate', (done) => { |
||||||
|
const contract = util.getCode('if/else-with-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
vm.execute(info.contract, 'a', [2]).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 0, 8: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {1: [0, 1]}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 0, 3: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
}) |
}) |
||||||
|
}); |
||||||
|
|
||||||
|
it('should cover an if statement with an unbracketed alternate',function(done){ |
||||||
|
const contract = util.getCode('if/else-without-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, "test.sol", true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
it('should compile after instrumenting if statements with brackets',function(){ |
vm.execute(info.contract, 'a', [2]).then(events => { |
||||||
var contract = util.getCode('if/if-with-brackets.sol'); |
const mapping = coverage.generate(events, pathPrefix); |
||||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 0, 8: 1}); |
||||||
var output = solc.compile(info.contract, 1);
|
assert.deepEqual(mapping[filePath].b, {1: [0, 1]}); |
||||||
util.report(output.errors); |
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 0, 3: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
}) |
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should cover nested if statements with missing else statements',function(done){ |
||||||
|
const contract = util.getCode('if/nested-if-missing-else.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
it('should compile after instrumenting nested if statements with missing else statements',function(){ |
vm.execute(info.contract, 'a', [2, 3, 3]).then(events => { |
||||||
var contract = util.getCode('if/nested-if-missing-else.sol'); |
const mapping = coverage.generate(events, pathPrefix); |
||||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
assert.deepEqual(mapping[filePath].l, {5: 1, 7: 1}); |
||||||
var output = solc.compile(info.contract, 1);
|
assert.deepEqual(mapping[filePath].b, { '1': [ 0, 1 ], '2': [ 1, 0 ], '3': [ 1, 0 ] }); |
||||||
util.report(output.errors); |
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}) |
||||||
}) |
}) |
||||||
}) |
}) |
@ -0,0 +1,82 @@ |
|||||||
|
const solc = require('solc'); |
||||||
|
const path = require('path'); |
||||||
|
const getInstrumentedVersion = require('./../instrumentSolidity.js'); |
||||||
|
const util = require('./util/util.js'); |
||||||
|
const CoverageMap = require('./../coverageMap'); |
||||||
|
const vm = require('./util/vm'); |
||||||
|
const assert = require('assert'); |
||||||
|
|
||||||
|
describe('for and while statements', function(){ |
||||||
|
|
||||||
|
const fileName = 'test.sol'; |
||||||
|
const filePath = path.resolve('./test.sol'); |
||||||
|
const pathPrefix = './'; |
||||||
|
|
||||||
|
it('should cover a for statement with a bracketed body (multiline)', (done) => { |
||||||
|
const contract = util.getCode('loops/for-with-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a() => for(var x = 1; x < 10; x++){\n sha3(x);\n }
|
||||||
|
vm.execute(info.contract, 'a', []).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 10}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 10}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
it('should cover a for statement with an unbracketed body', (done) => { |
||||||
|
const contract = util.getCode('loops/for-no-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a() => for(var x = 1; x < 10; x++)\n sha3(x);\n
|
||||||
|
vm.execute(info.contract, 'a', []).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 10}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 10}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
it('should cover a while statement with an bracketed body (multiline)', (done) => { |
||||||
|
const contract = util.getCode('loops/while-with-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a() => var t = true;\n while(t){\n t = false;\n }
|
||||||
|
vm.execute(info.contract, 'a', []).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 1, 7: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1, 3: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}) |
||||||
|
}); |
||||||
|
|
||||||
|
it('should cover a while statement with an unbracketed body (multiline)', (done) => { |
||||||
|
const contract = util.getCode('loops/while-no-brackets.sol'); |
||||||
|
const info = getInstrumentedVersion(contract, fileName, true); |
||||||
|
const coverage = new CoverageMap(); |
||||||
|
coverage.addContract(info, filePath); |
||||||
|
|
||||||
|
// Runs: a() => var t = true;\n while(t)\n t = false;\n
|
||||||
|
vm.execute(info.contract, 'a', []).then(events => { |
||||||
|
const mapping = coverage.generate(events, pathPrefix); |
||||||
|
assert.deepEqual(mapping[filePath].l, {5: 1, 6: 1, 7: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].b, {}); |
||||||
|
assert.deepEqual(mapping[filePath].s, {1: 1, 2: 1, 3: 1}); |
||||||
|
assert.deepEqual(mapping[filePath].f, {1: 1}); |
||||||
|
done(); |
||||||
|
}) |
||||||
|
}); |
||||||
|
}) |
@ -0,0 +1,9 @@ |
|||||||
|
pragma solidity ^0.4.3; |
||||||
|
|
||||||
|
contract Test { |
||||||
|
function a(uint x) { |
||||||
|
if (x == 1) { |
||||||
|
x = 3; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
pragma solidity ^0.4.3; |
||||||
|
|
||||||
|
contract Test { |
||||||
|
function a() { |
||||||
|
for(var x = 0; x < 10; x++) |
||||||
|
sha3(x); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
pragma solidity ^0.4.3; |
||||||
|
|
||||||
|
contract Test { |
||||||
|
function a() { |
||||||
|
for(var x = 0; x < 10; x++){ |
||||||
|
sha3(x); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
pragma solidity ^0.4.3; |
||||||
|
|
||||||
|
contract Test { |
||||||
|
function a() { |
||||||
|
var t = true; |
||||||
|
while(t) |
||||||
|
t = false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
pragma solidity ^0.4.3; |
||||||
|
|
||||||
|
contract Test { |
||||||
|
function a() { |
||||||
|
var t = true; |
||||||
|
while(t){ |
||||||
|
t = false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,140 @@ |
|||||||
|
const solc = require('solc'); |
||||||
|
const path = require('path'); |
||||||
|
const VM = require('ethereumjs-vm'); |
||||||
|
const Account = require('ethereumjs-account'); |
||||||
|
const Transaction = require('ethereumjs-tx'); |
||||||
|
const utils = require('ethereumjs-util'); |
||||||
|
const CryptoJS = require('crypto-js'); |
||||||
|
const Trie = require('merkle-patricia-tree'); |
||||||
|
const coder = require('web3/lib/solidity/coder.js'); |
||||||
|
|
||||||
|
// Don't use this address for anything, obviously!
|
||||||
|
const secretKey = 'e81cb653c260ee12c72ec8750e6bfd8a4dc2c3d7e3ede68dd2f150d0a67263d8'; |
||||||
|
const accountAddress = new Buffer('7caf6f9bc8b3ba5c7824f934c826bd6dc38c8467', 'hex'); |
||||||
|
|
||||||
|
/** |
||||||
|
* Encodes function data |
||||||
|
* Source: consensys/eth-lightwallet/lib/txutils.js (line 18) |
||||||
|
*/ |
||||||
|
function encodeFunctionTxData(functionName, types, args) { |
||||||
|
const fullName = functionName + '(' + types.join() + ')'; |
||||||
|
const signature = CryptoJS.SHA3(fullName, {outputLength: 256}).toString(CryptoJS.enc.Hex).slice(0, 8); |
||||||
|
const dataHex = signature + coder.encodeParams(types, args); |
||||||
|
return '0x' + dataHex; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extracts types from abi |
||||||
|
* Source: consensys/eth-lightwallet/lib/txutils.js (line 27) |
||||||
|
*/ |
||||||
|
function getTypesFromAbi(abi, functionName) { |
||||||
|
function matchesFunctionName(json) { |
||||||
|
return (json.name === functionName && json.type === 'function'); |
||||||
|
} |
||||||
|
function getTypes(json) { |
||||||
|
return json.type; |
||||||
|
} |
||||||
|
const funcJson = abi.filter(matchesFunctionName)[0]; |
||||||
|
return (funcJson.inputs).map(getTypes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves abi for contract
|
||||||
|
* Source: raineorshine/eth-new-contract/src/index.js (line 8) |
||||||
|
* @param {String} source solidity contract |
||||||
|
* @param {Object} compilation compiled `source` |
||||||
|
* @return {Object} abi |
||||||
|
*/ |
||||||
|
function getAbi(source, compilation){ |
||||||
|
const contractNameMatch = source.match(/(?:contract|library)\s([^\s]*)\s*{/) |
||||||
|
if(!contractNameMatch) { |
||||||
|
throw new Error('Could not parse contract name from source.') |
||||||
|
} |
||||||
|
const contractName = contractNameMatch[1] |
||||||
|
return JSON.parse(compilation.contracts[contractName].interface) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates, funds and publishes account to Trie |
||||||
|
*/ |
||||||
|
function createAccount(trie) { |
||||||
|
const account = new Account(); |
||||||
|
account.balance = 'f00000000000000000'; |
||||||
|
trie.put(accountAddress, account.serialize()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Deploys contract represented by `code` |
||||||
|
* @param {String} code contract bytecode |
||||||
|
*/ |
||||||
|
|
||||||
|
function deploy(vm, code) { |
||||||
|
const tx = new Transaction({gasPrice: '1', gasLimit: 'ffffff', data: code,}); |
||||||
|
tx.sign(new Buffer(secretKey, 'hex')); |
||||||
|
|
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
vm.runTx({tx: tx}, (err, results) => { |
||||||
|
(err) |
||||||
|
? reject(err) |
||||||
|
: resolve(results.createdAddress); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Invokes `functionName` with `args` on contract at `address`. Tx construction logic |
||||||
|
* is poached from consensys/eth-lightwallet/lib/txutils:functionTx |
||||||
|
* @param {Array} abi contract abi |
||||||
|
* @param {String} address deployed contract to invoke method on |
||||||
|
* @param {String} functionName method to invoke |
||||||
|
* @param {Array} args functionName's arguments |
||||||
|
* @return {Promise} resolves array of logged events |
||||||
|
*/ |
||||||
|
function callMethod(vm, abi, address, functionName, args) { |
||||||
|
|
||||||
|
const types = getTypesFromAbi(abi, functionName); |
||||||
|
const txData = encodeFunctionTxData(functionName, types, args); |
||||||
|
const options = { |
||||||
|
gasPrice: '0x1', |
||||||
|
gasLimit: '0xffffff', |
||||||
|
to: utils.bufferToHex(address), |
||||||
|
data: txData, |
||||||
|
nonce: '0x1', |
||||||
|
}; |
||||||
|
|
||||||
|
let tx = new Transaction(options); |
||||||
|
tx.sign(new Buffer(secretKey, 'hex')); |
||||||
|
|
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
vm.runTx({tx: tx}, (err, results) => { |
||||||
|
let seenEvents = []; |
||||||
|
results.vm.runState.logs.map(log => { |
||||||
|
const toWrite = {}; |
||||||
|
toWrite.address = log[0].toString('hex'); |
||||||
|
toWrite.topics = log[1].map(x => x.toString('hex')); |
||||||
|
toWrite.data = log[2].toString('hex'); |
||||||
|
seenEvents.push(JSON.stringify(toWrite)); |
||||||
|
}); |
||||||
|
resolve(seenEvents); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Runs method `functionName` with parameters `args` on contract. Resolves a |
||||||
|
* CR delimited list of logged events. |
||||||
|
* @param {String} contract solidity to compile |
||||||
|
* @param {String} functionName method to invoke on contract |
||||||
|
* @param {Array} args parameter values to pass to method |
||||||
|
* @return {Promise} resolves array of logged events. |
||||||
|
*/ |
||||||
|
module.exports.execute = function ex(contract, functionName, args) { |
||||||
|
const output = solc.compile(contract, 1); |
||||||
|
const code = new Buffer(output.contracts.Test.bytecode, 'hex'); |
||||||
|
const abi = getAbi(contract, output);
|
||||||
|
const stateTrie = new Trie(); |
||||||
|
const vm = new VM({ state: stateTrie }); |
||||||
|
|
||||||
|
createAccount(stateTrie); |
||||||
|
return deploy(vm, code).then(address => callMethod(vm, abi, address, functionName, args)); |
||||||
|
}; |
Loading…
Reference in new issue