Code coverage for Solidity smart-contracts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
solidity-coverage/test/util/vm.js

141 lines
4.6 KiB

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));
};