From ef504cf157db585c40a5b998986f8d49db95fca0 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 26 Mar 2018 12:35:40 +0200 Subject: [PATCH 1/2] add debug tests --- remix-debug/package.json | 6 +- remix-debug/test/tests.js | 177 +++++++++++++++++++++++++++++++++++++ remix-debug/test/vmCall.js | 64 ++++++++++++++ 3 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 remix-debug/test/tests.js create mode 100644 remix-debug/test/vmCall.js diff --git a/remix-debug/package.json b/remix-debug/package.json index bd13d5fe13..1710d742f0 100644 --- a/remix-debug/package.json +++ b/remix-debug/package.json @@ -22,6 +22,8 @@ "babel-preset-es2015": "^6.24.0", "babel-preset-stage-0": "^6.24.1", "babelify": "^7.3.0", + "ethereumjs-util": "^4.5.0", + "ethereumjs-vm": "2.3.1", "notify-error": "^1.2.0", "npm-run-all": "^4.1.2", "remix-core": "latest", @@ -32,7 +34,9 @@ }, "scripts": { "build": "mkdirp build; browserify index.js > build/app.js", - "lint": "standard | notify-error" + "lint": "standard | notify-error", + "downloadsolc": "cd node_modules/solc && (test -e soljson.js || wget --no-check-certificate https://solc-bin.ethereum.org/soljson.js) && cd ..", + "test": "standard && npm run downloadsolc && tape ./test/tests.js" }, "repository": { "type": "git", diff --git a/remix-debug/test/tests.js b/remix-debug/test/tests.js new file mode 100644 index 0000000000..b54254b406 --- /dev/null +++ b/remix-debug/test/tests.js @@ -0,0 +1,177 @@ +'use strict' +var tape = require('tape') +var remixLib = require('remix-lib') +var remixCore = require('remix-core') +var compilerInput = remixLib.helpers.compiler.compilerInput +var vmCall = require('./vmCall') +var Debugger = require('../src/Ethdebugger') +var compiler = require('solc') + +tape('debug contract', function (t) { + var privateKey = new Buffer('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', 'hex') + var vm = vmCall.initVM(t, privateKey) + var output = compiler.compileStandardWrapper(compilerInput(ballot)) + output = JSON.parse(output) + var web3VM = new remixLib.vm.Web3VMProvider() + web3VM.setVM(vm) + vmCall.sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, output.contracts['test.sol']['Ballot'].evm.bytecode.object, (error, txHash) => { + if (error) { + t.end(error) + } else { + web3VM.eth.getTransaction(txHash, (error, tx) => { + if (error) { + t.end(error) + } else { + var debugManager = new Debugger({ + compilationResult: function () { + return output + } + }) + + debugManager.addProvider('web3vmprovider', web3VM) + debugManager.switchProvider('web3vmprovider') + + debugManager.callTree.event.register('callTreeReady', () => { + testDebugging(t, debugManager) + }) + + debugManager.debug(tx) + } + }) + } + }) +}) + + +function testDebugging (t, debugManager) { + // stack + debugManager.traceManager.getStackAt(4, (error, callstack) => { + if (error) return t.end(error) + t.equal(JSON.stringify(callstack), JSON.stringify([ '0x0000000000000000000000000000000000000000000000000000000000000000' ])) + }) + + debugManager.traceManager.getStackAt(41, (error, callstack) => { + if (error) return t.end(error) + + /* + t.equal(JSON.stringify(callstack), JSON.stringify(['0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000004b0897b0513fdc7c541b6d9d7e929c4e5364d2db', '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000001', '0x000000000000000000000000000000000000000000000000000000000000002d'])) + */ + }) + + // storage + debugManager.traceManager.getCurrentCalledAddressAt(38, (error, address) => { + if (error) return t.end(error) + var storageView = debugManager.storageViewAt(38, address) + storageView.storageRange((error, storage) => { + if (error) return t.end(error) + t.equal(JSON.stringify(storage), JSON.stringify({ '0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563': { key: '0x0000000000000000000000000000000000000000000000000000000000000000', value: '0x0000000000000000000000004b0897b0513fdc7c541b6d9d7e929c4e5364d2db' } })) + }) + }) + + debugManager.extractStateAt(138, (error, state) => { + if (error) return t.end(error) + debugManager.decodeStateAt(138, state, (error, decodedState) => { + if (error) return t.end(error) + t.equal(decodedState['chairperson'].value, '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB') + t.equal(decodedState['chairperson'].type, 'address') + t.equal(decodedState['proposals'].value[0].value.voteCount.value, '0') + t.equal(decodedState['proposals'].value[0].value.voteCount.type, 'uint256') + t.equal(decodedState['proposals'].value[0].type, 'struct Ballot.Proposal') + t.equal(decodedState['proposals'].length, '0x1') + t.equal(decodedState['proposals'].type, 'struct Ballot.Proposal[]') + }) + }) + + debugManager.traceManager.getCurrentCalledAddressAt(138, (error, address) => { + if (error) return t.end(error) + debugManager.sourceLocationFromVMTraceIndex(address, 138, (error, location) => { + if (error) return t.end(error) + debugManager.decodeLocalsAt(138, location, (error, decodedlocals) => { + if (error) return t.end(error) + t.equal(JSON.stringify(decodedlocals), JSON.stringify({'p': {'value': '45', 'type': 'uint256'}, 'addressLocal': {'value': '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB', 'type': 'address'}, 'proposalsLocals': {'value': [{'value': {'voteCount': {'value': '0', 'type': 'uint256'}}, 'type': 'struct Ballot.Proposal'}], 'length': '0x1', 'type': 'struct Ballot.Proposal[]'}})) + }) + }) + }) + + var sourceMappingDecoder = new remixLib.SourceMappingDecoder() + var breakPointManager = new remixCore.code.BreakpointManager(debugManager, (rawLocation) => { + return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, ballot) + }) + + breakPointManager.add({fileName: 'test.sol', row: 24}) + + breakPointManager.event.register('breakpointHit', function (sourceLocation, step) { + t.equal(sourceLocation, '') + t.equal(step, 67) + }) + breakPointManager.jumpNextBreakpoint(true) +} + +var ballot = `pragma solidity ^0.4.0; +contract Ballot { + + struct Voter { + uint weight; + bool voted; + uint8 vote; + address delegate; + } + struct Proposal { + uint voteCount; + } + + address chairperson; + mapping(address => Voter) voters; + Proposal[] proposals; + + /// Create a new ballot with $(_numProposals) different proposals. + function Ballot() public { + uint p = 45; + chairperson = msg.sender; + address addressLocal = msg.sender; // copy of state variable + voters[chairperson].weight = 1; + proposals.length = 1; + Proposal[] proposalsLocals = proposals; // copy of state variable + } + + /// Give $(toVoter) the right to vote on this ballot. + /// May only be called by $(chairperson). + function giveRightToVote(address toVoter) public { + if (msg.sender != chairperson || voters[toVoter].voted) return; + voters[toVoter].weight = 1; + } + + /// Delegate your vote to the voter $(to). + function delegate(address to) public { + Voter storage sender = voters[msg.sender]; // assigns reference + if (sender.voted) return; + while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender) + to = voters[to].delegate; + if (to == msg.sender) return; + sender.voted = true; + sender.delegate = to; + Voter storage delegateTo = voters[to]; + if (delegateTo.voted) + proposals[delegateTo.vote].voteCount += sender.weight; + else + delegateTo.weight += sender.weight; + } + + /// Give a single vote to proposal $(toProposal). + function vote(uint8 toProposal) public { + Voter storage sender = voters[msg.sender]; + if (sender.voted || toProposal >= proposals.length) return; + sender.voted = true; + sender.vote = toProposal; + proposals[toProposal].voteCount += sender.weight; + } + + function winningProposal() public constant returns (uint8 _winningProposal) { + uint256 winningVoteCount = 0; + for (uint8 prop = 0; prop < proposals.length; prop++) + if (proposals[prop].voteCount > winningVoteCount) { + winningVoteCount = proposals[prop].voteCount; + _winningProposal = prop; + } + } +}` diff --git a/remix-debug/test/vmCall.js b/remix-debug/test/vmCall.js new file mode 100644 index 0000000000..11abc857a0 --- /dev/null +++ b/remix-debug/test/vmCall.js @@ -0,0 +1,64 @@ +'use strict' +var utileth = require('ethereumjs-util') +var Tx = require('ethereumjs-tx') +var Block = require('ethereumjs-block') +var BN = require('ethereumjs-util').BN +var remixLib = require('remix-lib') + +function sendTx (vm, from, to, value, data, cb) { + var tx = new Tx({ + nonce: new BN(from.nonce++), + gasPrice: new BN(1), + gasLimit: new BN(3000000, 10), + to: to, + value: new BN(value, 10), + data: new Buffer(data, 'hex') + }) + tx.sign(from.privateKey) + var block = new Block({ + header: { + timestamp: new Date().getTime() / 1000 | 0, + number: 0 + }, + transactions: [], + uncleHeaders: [] + }) + vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (error, result) { + setTimeout(() => { + cb(error, utileth.bufferToHex(tx.hash())) + }, 500) + }) +} + +/* + Init VM / Send Transaction +*/ +function initVM (st, privateKey) { + var utileth = require('ethereumjs-util') + var VM = require('ethereumjs-vm') + var Web3Providers = remixLib.vm.Web3Providers + var address = utileth.privateToAddress(privateKey) + var vm = new VM({ + enableHomestead: true, + activatePrecompiles: true + }) + vm.stateManager.putAccountBalance(address, 'f00000000000000001', function cb () {}) + var web3Providers = new Web3Providers() + web3Providers.addVM('VM', vm) + web3Providers.get('VM', function (error, obj) { + if (error) { + var mes = 'provider TEST not defined' + console.log(mes) + st.fail(mes) + } else { + remixLib.global.web3 = obj + remixLib.global.web3Debug = obj + } + }) + return vm +} + +module.exports = { + sendTx: sendTx, + initVM: initVM +} From 1ed4c7d3547f3e831b12b2a7fec9a5b91e286175 Mon Sep 17 00:00:00 2001 From: yann300 Date: Mon, 26 Mar 2018 13:04:48 +0200 Subject: [PATCH 2/2] activate test --- .circleci/config.yml | 10 ++++++++++ remix-debug/test/tests.js | 17 ++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ad0a4e36e0..b8190142d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -30,6 +30,15 @@ jobs: steps: - checkout - run: cd remix-solidity && npm install && npm test + + remix-debug: + docker: + - image: circleci/node:7.10 + environment: + working_directory: ~/repo + steps: + - checkout + - run: cd remix-debug && npm install && npm test workflows: version: 2 @@ -38,4 +47,5 @@ workflows: - remix-lib - remix-core - remix-solidity + - remix-debug diff --git a/remix-debug/test/tests.js b/remix-debug/test/tests.js index b54254b406..23e33ca556 100644 --- a/remix-debug/test/tests.js +++ b/remix-debug/test/tests.js @@ -8,6 +8,7 @@ var Debugger = require('../src/Ethdebugger') var compiler = require('solc') tape('debug contract', function (t) { + t.plan(12) var privateKey = new Buffer('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', 'hex') var vm = vmCall.initVM(t, privateKey) var output = compiler.compileStandardWrapper(compilerInput(ballot)) @@ -95,16 +96,22 @@ function testDebugging (t, debugManager) { var sourceMappingDecoder = new remixLib.SourceMappingDecoder() var breakPointManager = new remixCore.code.BreakpointManager(debugManager, (rawLocation) => { - return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, ballot) + return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, sourceMappingDecoder.getLinebreakPositions(ballot)) }) - breakPointManager.add({fileName: 'test.sol', row: 24}) + breakPointManager.add({fileName: 'test.sol', row: 23}) breakPointManager.event.register('breakpointHit', function (sourceLocation, step) { - t.equal(sourceLocation, '') - t.equal(step, 67) + console.log('breakpointHit') + t.equal(JSON.stringify(sourceLocation), JSON.stringify({ start: 591, length: 1, file: 0, jump: '-' })) + t.equal(step, 73) }) - breakPointManager.jumpNextBreakpoint(true) + + breakPointManager.event.register('noBreakpointHit', function () { + t.end('noBreakpointHit') + console.log('noBreakpointHit') + }) + breakPointManager.jumpNextBreakpoint(0, true) } var ballot = `pragma solidity ^0.4.0;