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: |
||||
pre: |
||||
- 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'); |
||||
var getInstrumentedVersion = require('./../instrumentSolidity.js'); |
||||
var util = require('./util/util.js') |
||||
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'); |
||||
|
||||
/** |
||||
* NB: passing '1' to solc as an option activates the optimiser |
||||
*/ |
||||
describe('if, else, and else if statements', function(){ |
||||
|
||||
it('should compile after instrumenting else statements with brackets',function(){ |
||||
var contract = util.getCode('if/else-with-brackets.sol'); |
||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
||||
var output = solc.compile(info.contract, 1);
|
||||
util.report(output.errors); |
||||
const fileName = 'test.sol'; |
||||
const filePath = path.resolve('./test.sol'); |
||||
const pathPrefix = './'; |
||||
|
||||
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(){ |
||||
var contract = util.getCode('if/else-without-brackets.sol'); |
||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
||||
var output = solc.compile(info.contract, 1);
|
||||
util.report(output.errors); |
||||
it('should cover an if statement with multiline bracketed consequent', (done) => { |
||||
const contract = util.getCode('if/if-with-brackets-multiline.sol'); |
||||
const info = getInstrumentedVersion(contract, fileName, true); |
||||
const coverage = new CoverageMap(); |
||||
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(){ |
||||
var contract = util.getCode('if/if-no-brackets.sol'); |
||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
||||
var output = solc.compile(info.contract, 1);
|
||||
util.report(output.errors); |
||||
it('should cover a simple if statement with a failing condition', (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(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(){ |
||||
var contract = util.getCode('if/if-with-brackets.sol'); |
||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
||||
var output = solc.compile(info.contract, 1);
|
||||
util.report(output.errors); |
||||
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 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(){ |
||||
var contract = util.getCode('if/nested-if-missing-else.sol'); |
||||
var info = getInstrumentedVersion(contract, "test.sol", true); |
||||
var output = solc.compile(info.contract, 1);
|
||||
util.report(output.errors); |
||||
vm.execute(info.contract, 'a', [2, 3, 3]).then(events => { |
||||
const mapping = coverage.generate(events, pathPrefix); |
||||
assert.deepEqual(mapping[filePath].l, {5: 1, 7: 1}); |
||||
assert.deepEqual(mapping[filePath].b, { '1': [ 0, 1 ], '2': [ 1, 0 ], '3': [ 1, 0 ] }); |
||||
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