const solc = require('solc'); 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 ? (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)\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, }, (err, results) => { if (err) { reject(err); } else { 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', }; const tx = new Transaction(options); tx.sign(new Buffer(secretKey, 'hex')); return new Promise(resolve => { vm.runTx({ tx, }, (err, results) => { const seenEvents = []; results.vm.runState.logs.forEach(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)); };