diff --git a/remix-debug/index.js b/remix-debug/index.js index a5e3ac4f80..9f7b93d27d 100644 --- a/remix-debug/index.js +++ b/remix-debug/index.js @@ -30,6 +30,7 @@ module.exports = { storage: { StorageViewer: StorageViewer, StorageResolver: StorageResolver - } + }, + SolidityDecoder: SolidityDecoder } diff --git a/remix-simulator/README.md b/remix-simulator/README.md new file mode 100644 index 0000000000..ea0ecbddb0 --- /dev/null +++ b/remix-simulator/README.md @@ -0,0 +1,2 @@ +# `remix-simulator` + diff --git a/remix-simulator/bin/ethsim b/remix-simulator/bin/ethsim new file mode 100755 index 0000000000..e136cc361f --- /dev/null +++ b/remix-simulator/bin/ethsim @@ -0,0 +1,4 @@ +#!/usr/bin/env node + +require('../src/server'); + diff --git a/remix-simulator/index.js b/remix-simulator/index.js new file mode 100644 index 0000000000..4b17d07532 --- /dev/null +++ b/remix-simulator/index.js @@ -0,0 +1,5 @@ +var Provider = require('./src/provider') + +module.exports = { + Provider: Provider +} diff --git a/remix-simulator/package.json b/remix-simulator/package.json new file mode 100644 index 0000000000..e3d2116695 --- /dev/null +++ b/remix-simulator/package.json @@ -0,0 +1,86 @@ +{ + "name": "remix-simulator", + "version": "0.0.3", + "description": "Ethereum IDE and tools for the web", + "contributors": [ + { + "name": "Iuri Matias", + "email": "iuri@ethereum.org" + }, + { + "name": "Yann Levreau", + "email": "yann@ethdev.com" + } + ], + "main": "./index.js", + "dependencies": { + "ansi-gray": "^0.1.1", + "babel-plugin-transform-object-assign": "^6.22.0", + "babel-preset-es2017": "^6.24.1", + "babelify": "^7.3.0", + "body-parser": "^1.18.2", + "color-support": "^1.1.3", + "express": "^4.16.3", + "fast-async": "^6.3.7", + "merge": "^1.2.0", + "remix-lib": "^0.2.5", + "standard": "^10.0.3", + "time-stamp": "^2.0.0", + "web3": "1.0.0-beta.27" + }, + "scripts": { + "test": "standard" + }, + "bin": { + "ethsim": "./bin/ethsim", + "remix-simulator": "./bin/ethsim" + }, + "standard": { + "ignore": [ + "node_modules/*" + ], + "parser": "babel-eslint" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ethereum/remix.git" + }, + "author": "remix team", + "license": "MIT", + "bugs": { + "url": "https://github.com/ethereum/remix/issues" + }, + "homepage": "https://github.com/ethereum/remix#readme", + "browserify": { + "transform": [ + [ + "babelify", + { + "plugins": [ + [ + "fast-async", + { + "runtimePatten": null, + "compiler": { + "promises": true, + "es7": true, + "noRuntime": true, + "wrapAwait": true + } + } + ], + "transform-object-assign" + ] + } + ], + [ + "babelify", + { + "presets": [ + "es2017" + ] + } + ] + ] + } +} diff --git a/remix-simulator/src/methods/accounts.js b/remix-simulator/src/methods/accounts.js new file mode 100644 index 0000000000..c85493aade --- /dev/null +++ b/remix-simulator/src/methods/accounts.js @@ -0,0 +1,22 @@ +var Web3 = require('web3') + +var Accounts = function () { + this.web3 = new Web3() + // TODO: make it random and/or use remix-libs + this.accounts = [this.web3.eth.accounts.create(['abcd'])] + + this.accounts[this.accounts[0].address.toLowerCase()] = this.accounts[0] + this.accounts[this.accounts[0].address.toLowerCase()].privateKey = Buffer.from(this.accounts[this.accounts[0].address.toLowerCase()].privateKey.slice(2), 'hex') +} + +Accounts.prototype.methods = function () { + return { + eth_accounts: this.eth_accounts.bind(this) + } +} + +Accounts.prototype.eth_accounts = function (payload, cb) { + return cb(null, this.accounts.map((x) => x.address)) +} + +module.exports = Accounts diff --git a/remix-simulator/src/methods/blocks.js b/remix-simulator/src/methods/blocks.js new file mode 100644 index 0000000000..718070f42c --- /dev/null +++ b/remix-simulator/src/methods/blocks.js @@ -0,0 +1,42 @@ + +var Blocks = function () { +} + +Blocks.prototype.methods = function () { + return { + eth_getBlockByNumber: this.eth_getBlockByNumber.bind(this), + eth_gasPrice: this.eth_gasPrice.bind(this) + } +} + +Blocks.prototype.eth_getBlockByNumber = function (payload, cb) { + let b = { + 'difficulty': '0x0', + 'extraData': '0x', + 'gasLimit': '0x7a1200', + 'gasUsed': '0x0', + 'hash': '0xdb731f3622ef37b4da8db36903de029220dba74c41185f8429f916058b86559f', + 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + 'miner': '0x3333333333333333333333333333333333333333', + 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000', + 'nonce': '0x0000000000000042', + 'number': '0x0', + 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000', + 'receiptsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', + 'size': '0x1f8', + 'stateRoot': '0xb7917653f92e62394d2207d0f39a1320ff1cb93d1cee80d3c492627e00b219ff', + 'timestamp': '0x0', + 'totalDifficulty': '0x0', + 'transactions': [], + 'transactionsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', + 'uncles': [] + } + cb(null, b) +} + +Blocks.prototype.eth_gasPrice = function (payload, cb) { + cb(null, 1) +} + +module.exports = Blocks diff --git a/remix-simulator/src/methods/misc.js b/remix-simulator/src/methods/misc.js new file mode 100644 index 0000000000..a2fd6db044 --- /dev/null +++ b/remix-simulator/src/methods/misc.js @@ -0,0 +1,16 @@ +var version = require('../../package.json').version + +var Misc = function () { +} + +Misc.prototype.methods = function () { + return { + web3_clientVersion: this.web3_clientVersion.bind(this) + } +} + +Misc.prototype.web3_clientVersion = function (payload, cb) { + cb(null, 'Remix Simulator/' + version) +} + +module.exports = Misc diff --git a/remix-simulator/src/methods/transactions.js b/remix-simulator/src/methods/transactions.js new file mode 100644 index 0000000000..53834ea7dd --- /dev/null +++ b/remix-simulator/src/methods/transactions.js @@ -0,0 +1,63 @@ +var RemixLib = require('remix-lib') +var executionContext = RemixLib.execution.executionContext +var processTx = require('./txProcess.js') + +var Transactions = function (accounts) { + this.accounts = accounts + // TODO: fix me; this is a temporary and very hackish thing just to get the getCode working for now + this.deployedContracts = {} +} + +Transactions.prototype.methods = function () { + return { + eth_sendTransaction: this.eth_sendTransaction.bind(this), + eth_getTransactionReceipt: this.eth_getTransactionReceipt.bind(this), + eth_getCode: this.eth_getCode.bind(this), + eth_call: this.eth_call.bind(this), + eth_estimateGas: this.eth_estimateGas.bind(this) + } +} + +Transactions.prototype.eth_sendTransaction = function (payload, cb) { + processTx(this.accounts, payload, false, cb) +} + +Transactions.prototype.eth_getTransactionReceipt = function (payload, cb) { + const self = this + executionContext.web3().eth.getTransactionReceipt(payload.params[0], (error, receipt) => { + if (error) { + return cb(error) + } + self.deployedContracts[receipt.contractAddress] = receipt.data + + var r = { + 'transactionHash': receipt.hash, + 'transactionIndex': '0x00', + 'blockHash': '0x766d18646a06cf74faeabf38597314f84a82c3851859d9da9d94fc8d037269e5', + 'blockNumber': '0x06', + 'gasUsed': '0x06345f', + 'cumulativeGasUsed': '0x06345f', + 'contractAddress': receipt.contractAddress, + 'logs': receipt.logs, + 'status': 1 + } + + cb(null, r) + }) +} + +Transactions.prototype.eth_estimateGas = function (payload, cb) { + cb(null, 3000000) +} + +Transactions.prototype.eth_getCode = function (payload, cb) { + let address = payload.params[0] + + cb(null, this.deployedContracts[address] || '0x') +} + +Transactions.prototype.eth_call = function (payload, cb) { + processTx(this.accounts, payload, true, cb) +} + +module.exports = Transactions diff --git a/remix-simulator/src/methods/txProcess.js b/remix-simulator/src/methods/txProcess.js new file mode 100644 index 0000000000..fad01e5e78 --- /dev/null +++ b/remix-simulator/src/methods/txProcess.js @@ -0,0 +1,96 @@ +var RemixLib = require('remix-lib') +var TxExecution = RemixLib.execution.txExecution +var TxRunner = RemixLib.execution.txRunner +var executionContext = RemixLib.execution.executionContext + +function runCall (payload, from, to, data, value, gasLimit, txRunner, callbacks, callback) { + let finalCallback = function (err, result) { + if (err) { + return callback(err) + } + + let toReturn = '0x' + result.result.vm.return.toString('hex') + if (toReturn === '0x') { + toReturn = '0x0' + } + return callback(null, toReturn) + } + + TxExecution.callFunction(from, to, data, value, gasLimit, null, txRunner, callbacks, finalCallback, true) +} + +function runTx (payload, from, to, data, value, gasLimit, txRunner, callbacks, callback) { + let finalCallback = function (err, result) { + if (err) { + return callback(err) + } + callback(null, result.transactionHash) + } + + TxExecution.callFunction(from, to, data, value, gasLimit, null, txRunner, callbacks, finalCallback, false) +} + +function createContract (payload, from, data, value, gasLimit, txRunner, callbacks, callback) { + let finalCallback = function (err, result) { + if (err) { + return callback(err) + } + callback(null, result.transactionHash) + } + + TxExecution.createContract(from, data, value, gasLimit, txRunner, callbacks, finalCallback) +} + +function processTx (accounts, payload, isCall, callback) { + let api = { + logMessage: (msg) => { + }, + logHtmlMessage: (msg) => { + }, + config: { + getUnpersistedProperty: (key) => { + return true + }, + get: () => { + return true + } + }, + detectNetwork: (cb) => { + cb() + }, + personalMode: () => { + return false + } + } + + executionContext.init(api.config) + + let txRunner = new TxRunner(accounts, api) + let { from, to, data, value, gas } = payload.params[0] + gas = gas || 3000000 + + let callbacks = { + confirmationCb: (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + continueTxExecution(null) + }, + gasEstimationForceSend: (error, continueTxExecution, cancelCb) => { + if (error) { + continueTxExecution(error) + } + continueTxExecution() + }, + promptCb: (okCb, cancelCb) => { + okCb() + } + } + + if (isCall) { + runCall(payload, from, to, data, value, gas, txRunner, callbacks, callback) + } else if (to) { + runTx(payload, from, to, data, value, gas, txRunner, callbacks, callback) + } else { + createContract(payload, from, data, value, gas, txRunner, callbacks, callback) + } +} + +module.exports = processTx diff --git a/remix-simulator/src/methods/whisper.js b/remix-simulator/src/methods/whisper.js new file mode 100644 index 0000000000..bfd88f43e0 --- /dev/null +++ b/remix-simulator/src/methods/whisper.js @@ -0,0 +1,15 @@ + +var Whisper = function () { +} + +Whisper.prototype.methods = function () { + return { + shh_version: this.shh_version.bind(this) + } +} + +Whisper.prototype.shh_version = function (payload, cb) { + cb(null, 5) +} + +module.exports = Whisper diff --git a/remix-simulator/src/provider.js b/remix-simulator/src/provider.js new file mode 100644 index 0000000000..6a4327f7b5 --- /dev/null +++ b/remix-simulator/src/provider.js @@ -0,0 +1,41 @@ +const log = require('./utils/logs.js') +const merge = require('merge') + +const Accounts = require('./methods/accounts.js') +const Blocks = require('./methods/blocks.js') +const Misc = require('./methods/misc.js') +const Transactions = require('./methods/transactions.js') +const Whisper = require('./methods/whisper.js') + +var Provider = function () { + this.Accounts = new Accounts() + + this.methods = {} + this.methods = merge(this.methods, this.Accounts.methods()) + this.methods = merge(this.methods, (new Blocks()).methods()) + this.methods = merge(this.methods, (new Misc()).methods()) + this.methods = merge(this.methods, (new Transactions(this.Accounts.accounts)).methods()) + this.methods = merge(this.methods, (new Whisper()).methods()) +} + +Provider.prototype.sendAsync = function (payload, callback) { + log.info('payload method is ', payload.method) + + let method = this.methods[payload.method] + if (method) { + return method.call(method, payload, (err, result) => { + if (err) { + return callback(err) + } + let response = {'id': payload.id, 'jsonrpc': '2.0', 'result': result} + callback(null, response) + }) + } + callback(new Error('unknown method ' + payload.method)) +} + +Provider.prototype.isConnected = function () { + return true +} + +module.exports = Provider diff --git a/remix-simulator/src/server.js b/remix-simulator/src/server.js new file mode 100644 index 0000000000..1171b8826d --- /dev/null +++ b/remix-simulator/src/server.js @@ -0,0 +1,25 @@ +const express = require('express') +const bodyParser = require('body-parser') +const app = express() +const Provider = require('./provider') +const log = require('./utils/logs.js') + +var provider = new Provider() + +app.use(bodyParser.urlencoded({extended: true})) +app.use(bodyParser.json()) + +app.get('/', (req, res) => { + res.send('Welcome to remix-simulator') +}) + +app.use(function (req, res) { + provider.sendAsync(req.body, (err, jsonResponse) => { + if (err) { + res.send({error: err}) + } + res.send(jsonResponse) + }) +}) + +app.listen(8545, () => log('Remix Simulator listening on port 8545!')) diff --git a/remix-simulator/src/utils/logs.js b/remix-simulator/src/utils/logs.js new file mode 100644 index 0000000000..d117ea9fcb --- /dev/null +++ b/remix-simulator/src/utils/logs.js @@ -0,0 +1,81 @@ +'use strict'; + +var gray = require('ansi-gray'); +var timestamp = require('time-stamp'); +var supportsColor = require('color-support'); + +function hasFlag(flag) { + return ((typeof(process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1)); +} + +function addColor(str) { + if (hasFlag('no-color')) { + return str; + } + + if (hasFlag('color')) { + return gray(str); + } + + if (supportsColor()) { + return gray(str); + } + + return str; +} + +let logger = { + stdout: function(arg) { + if (typeof(process) === 'undefined' || !process.stdout) return; + process.stdout.write(arg); + }, + stderr: function(arg) { + if (typeof(process) === 'undefined' || process.stderr) return; + process.stderr.write(arg); + }, +}; + +function getTimestamp(){ + return '['+addColor(timestamp('HH:mm:ss'))+']'; +} + +function log(){ + var time = getTimestamp(); + logger.stdout(time + ' '); + console.log.apply(console, arguments); + return this; +} + +function info(){ + var time = getTimestamp(); + logger.stdout(time + ' '); + console.info.apply(console, arguments); + return this; +} + +function dir(){ + var time = getTimestamp(); + logger.stdout(time + ' '); + console.dir.apply(console, arguments); + return this; +} + +function warn(){ + var time = getTimestamp(); + logger.stderr(time + ' '); + console.warn.apply(console, arguments); + return this; +} + +function error(){ + var time = getTimestamp(); + logger.stderr(time + ' '); + console.error.apply(console, arguments); + return this; +} + +module.exports = log; +module.exports.info = info; +module.exports.dir = dir; +module.exports.warn = warn; +module.exports.error = error;