Merge branch 'master' into highlightsCleaner

highlightsCleaner
Liana Husikyan 4 years ago committed by GitHub
commit 2358699337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      .circleci/config.yml
  2. 1
      .env
  3. 1
      apps/remix-ide-e2e/seleniumConfig.js
  4. 2
      apps/remix-ide-e2e/src/commands/checkVariableDebug.ts
  5. 2
      apps/remix-ide-e2e/src/commands/clickFunction.ts
  6. 3
      apps/remix-ide-e2e/src/commands/goToVMTraceStep.ts
  7. 12
      apps/remix-ide-e2e/src/examples/example-contracts.ts
  8. 202
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  9. 2
      apps/remix-ide-e2e/src/tests/fileManager_api.test.ts
  10. 6
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  11. 8
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  12. 2
      apps/remix-ide-e2e/src/tests/signingMessage.test.ts
  13. 17
      apps/remix-ide-e2e/src/tests/solidityImport.test.ts
  14. 30
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  15. 5
      apps/remix-ide-e2e/src/tests/staticAnalysis.test.ts
  16. 12
      apps/remix-ide/ci/browser_tests_chrome.sh
  17. 12
      apps/remix-ide/ci/browser_tests_firefox.sh
  18. 10
      apps/remix-ide/ci/browser_tests_run_deploy.sh
  19. 5
      apps/remix-ide/ci/build_and_publish_docker_images.sh
  20. 7
      apps/remix-ide/ci/copy_resources.sh
  21. 24
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  22. 2
      apps/remix-ide/ci/makeMockCompiler.js
  23. 12
      apps/remix-ide/src/app/editor/example-contracts.js
  24. 2
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  25. 61
      apps/remix-ide/src/app/tabs/debugger/debuggerUI.js
  26. 4
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/VmDebugger.js
  27. 28
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/SolidityLocals.js
  28. 2
      apps/remix-ide/src/app/tabs/debugger/debuggerUI/vmDebugger/utils/SolidityTypeFormatter.js
  29. 2
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  30. 53
      apps/remix-ide/src/app/tabs/runTab/model/recorder.js
  31. 48
      apps/remix-ide/src/app/tabs/runTab/recorder.js
  32. 2
      apps/remix-ide/src/app/tabs/testTab/testTab.js
  33. 25
      apps/remix-ide/src/app/udapp/make-udapp.js
  34. 4
      apps/remix-ide/src/app/udapp/run-tab.js
  35. 7
      apps/remix-ide/src/app/ui/TreeView.js
  36. 4
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  37. 47
      apps/remix-ide/src/app/ui/modal-dialog-custom.js
  38. 2
      apps/remix-ide/src/app/ui/modaldialog.js
  39. 38
      apps/remix-ide/src/app/ui/renderer.js
  40. 2
      apps/remix-ide/src/app/ui/styles/modaldialog-styles.js
  41. 2
      apps/remix-ide/src/blockchain/providers/injected.js
  42. 23
      apps/remix-ide/src/lib/transactionReceiptResolver.js
  43. 2
      apps/remix-ide/src/remixAppManager.js
  44. 2
      libs/README.md
  45. 3
      libs/remix-astwalker/.eslintrc
  46. 2
      libs/remix-astwalker/src/@types/remix-lib/index.d.ts
  47. 12
      libs/remix-astwalker/src/astWalker.ts
  48. 4
      libs/remix-astwalker/src/sourceMappings.ts
  49. 26
      libs/remix-debug/src/Ethdebugger.js
  50. 1
      libs/remix-debug/src/cmdline/index.js
  51. 71
      libs/remix-debug/src/code/breakpointManager.js
  52. 11
      libs/remix-debug/src/code/codeManager.js
  53. 24
      libs/remix-debug/src/code/codeResolver.js
  54. 4
      libs/remix-debug/src/debugger/VmDebugger.js
  55. 38
      libs/remix-debug/src/debugger/debugger.js
  56. 20
      libs/remix-debug/src/debugger/solidityLocals.js
  57. 4
      libs/remix-debug/src/solidity-decoder/decodeInfo.js
  58. 7
      libs/remix-debug/src/solidity-decoder/index.js
  59. 82
      libs/remix-debug/src/solidity-decoder/internalCallTree.js
  60. 8
      libs/remix-debug/src/solidity-decoder/localDecoder.js
  61. 42
      libs/remix-debug/src/solidity-decoder/solidityProxy.js
  62. 10
      libs/remix-debug/src/solidity-decoder/stateDecoder.js
  63. 3
      libs/remix-debug/src/solidity-decoder/types/Address.js
  64. 24
      libs/remix-debug/src/solidity-decoder/types/ArrayType.js
  65. 33
      libs/remix-debug/src/solidity-decoder/types/DynamicByteArray.js
  66. 11
      libs/remix-debug/src/solidity-decoder/types/Mapping.js
  67. 19
      libs/remix-debug/src/solidity-decoder/types/RefType.js
  68. 6
      libs/remix-debug/src/solidity-decoder/types/StringType.js
  69. 10
      libs/remix-debug/src/solidity-decoder/types/Struct.js
  70. 20
      libs/remix-debug/src/solidity-decoder/types/ValueType.js
  71. 26
      libs/remix-debug/src/solidity-decoder/types/util.js
  72. 54
      libs/remix-debug/src/source/sourceLocationTracker.js
  73. 19
      libs/remix-debug/src/source/sourceMappingDecoder.js
  74. 5
      libs/remix-debug/src/storage/mappingPreimages.js
  75. 61
      libs/remix-debug/test/astwalker.js
  76. 14
      libs/remix-debug/test/debugger.js
  77. 9
      libs/remix-debug/test/decoder/localsTests/helper.js
  78. 17
      libs/remix-debug/test/decoder/localsTests/int.js
  79. 16
      libs/remix-debug/test/decoder/localsTests/misc.js
  80. 16
      libs/remix-debug/test/decoder/localsTests/misc2.js
  81. 16
      libs/remix-debug/test/decoder/localsTests/structArray.js
  82. 4
      libs/remix-debug/test/resources/testWeb3.js
  83. 8
      libs/remix-debug/test/resources/testWeb3.json
  84. 1
      libs/remix-debug/test/resources/traceWithABIEncoder.json
  85. 113
      libs/remix-debug/test/sourceLocationTracker.js
  86. 6
      libs/remix-debug/test/sourceMappingDecoder.js
  87. 1
      libs/remix-debug/test/tests.js
  88. 5
      libs/remix-lib/src/eventManager.js
  89. 6
      libs/remix-lib/src/execution/eventsDecoder.js
  90. 20
      libs/remix-lib/src/execution/txListener.js
  91. 22
      libs/remix-lib/src/execution/txRunner.js
  92. 7
      libs/remix-lib/src/storage.js
  93. 49
      libs/remix-lib/src/universalDapp.js
  94. 5
      libs/remix-lib/src/web3Provider/web3Providers.js
  95. 12
      libs/remix-lib/src/web3Provider/web3VmProvider.js
  96. 2
      libs/remix-simulator/src/provider.js
  97. 2
      libs/remix-tests/sol/tests.sol.ts
  98. 1
      libs/remixd
  99. 1
      libs/remixd/.eslintrc
  100. 49
      libs/remixd/README.md
  101. Some files were not shown because too many files have changed in this diff Show More

@ -212,8 +212,9 @@ jobs:
- run: npm run build:libs - run: npm run build:libs
- run: npm run downloadsolc_root - run: npm run downloadsolc_root
- run: npm run build - run: npm run build
- run: ./apps/remix-ide/ci/build_and_publish_docker_images.sh - run: ./apps/remix-ide/ci/copy_resources.sh
- run: ./apps/remix-ide/ci/publishIpfs - run: ./apps/remix-ide/ci/publishIpfs
- run: ./apps/remix-ide/ci/build_and_publish_docker_images.sh
deploy-remix-alpha: deploy-remix-alpha:
docker: docker:
@ -245,6 +246,36 @@ jobs:
./apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh; ./apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh;
fi fi
deploy-remix-beta:
docker:
# specify the version you desire here
- image: circleci/node:10.18.0-browsers
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
resource_class: xlarge
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/assets dist/apps/remix-ide/index.html dist/apps/remix-ide/main.js dist/apps/remix-ide/polyfills.js dist/apps/remix-ide/runtime.js dist/apps/remix-ide/vendor.js dist/apps/remix-ide/favicon.ico"
working_directory: ~/remix-project
steps:
- checkout
- run: npm install
- run: npm run lint
- run: npm run build:libs
- run: npm run downloadsolc_root
- run: npm run build
- run:
name: Deploy
command: |
if [ "${CIRCLE_BRANCH}" == "remix_beta" ]; then
./apps/remix-ide/ci/deploy_from_travis_remix-beta.sh;
fi
workflows: workflows:
version: 2 version: 2
build_all: build_all:
@ -280,3 +311,11 @@ workflows:
filters: filters:
branches: branches:
only: master only: master
- deploy-remix-beta:
requires:
- remix-ide-chrome
- remix-ide-firefox
- remix-ide-run-deploy
filters:
branches:
only: remix_beta

@ -1,3 +1,4 @@
gist_token=<token> gist_token=<token>
account_passphrase=<passphrase> account_passphrase=<passphrase>
account_password=<password> account_password=<password>
NODE_OPTIONS=--max-old-space-size=2048

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports = { module.exports = {
version: '3.8.1', version: '3.8.1',
baseURL: 'https://selenium-release.storage.googleapis.com', baseURL: 'https://selenium-release.storage.googleapis.com',

@ -33,7 +33,7 @@ function checkDebug (browser: NightwatchBrowser, id: string, debugValue: Nightwa
} }
const equal = deepequal(debugValue, value) const equal = deepequal(debugValue, value)
if (!equal) { if (!equal) {
browser.assert.fail('checkDebug on ' + id, 'info about error\n ' + JSON.stringify(debugValue) + '\n ' + JSON.stringify(value), '') browser.assert.fail(JSON.stringify(value), 'info about error\n ' + JSON.stringify(debugValue) + '\n ' + JSON.stringify(value), '')
} }
done() done()
}) })

@ -14,7 +14,7 @@ class ClickFunction extends EventEmitter {
done() done()
}) })
}) })
.click('.instance button[title="' + fnFullName + '"]') .scrollAndClick('.instance button[title="' + fnFullName + '"]')
.pause(2000) .pause(2000)
.perform(() => { .perform(() => {
this.emit('complete') this.emit('complete')

@ -2,12 +2,9 @@ import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from "events" import EventEmitter from "events"
class GoToVmTraceStep extends EventEmitter { class GoToVmTraceStep extends EventEmitter {
command (this: NightwatchBrowser, step: number, incr?: number): NightwatchBrowser { command (this: NightwatchBrowser, step: number, incr?: number): NightwatchBrowser {
this.api.perform((done) => {
goToVMtraceStep(this.api, step, incr, () => { goToVMtraceStep(this.api, step, incr, () => {
done()
this.emit('complete') this.emit('complete')
}) })
})
return this return this
} }
} }

@ -1,6 +1,6 @@
'use strict' 'use strict'
const storage = `pragma solidity >=0.4.22 <0.7.0; const storage = `pragma solidity >=0.7.0 <0.8.0;
/** /**
* @title Storage * @title Storage
@ -27,7 +27,7 @@ contract Storage {
} }
}` }`
const owner = `pragma solidity >=0.4.22 <0.7.0; const owner = `pragma solidity >=0.7.0 <0.8.0;
/** /**
* @title Owner * @title Owner
@ -54,7 +54,7 @@ contract Owner {
/** /**
* @dev Set contract deployer as owner * @dev Set contract deployer as owner
*/ */
constructor() public { constructor() {
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
emit OwnerSet(address(0), owner); emit OwnerSet(address(0), owner);
} }
@ -77,7 +77,7 @@ contract Owner {
} }
}` }`
const ballot = `pragma solidity >=0.4.22 <0.7.0; const ballot = `pragma solidity >=0.7.0 <0.8.0;
/** /**
* @title Ballot * @title Ballot
@ -109,7 +109,7 @@ contract Ballot {
* @dev Create a new ballot to choose one of 'proposalNames'. * @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals * @param proposalNames names of proposals
*/ */
constructor(bytes32[] memory proposalNames) public { constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender; chairperson = msg.sender;
voters[chairperson].weight = 1; voters[chairperson].weight = 1;
@ -281,7 +281,7 @@ contract Ballot {
} }
}` }`
const ballotTest = `pragma solidity >=0.4.22 <0.7.0; const ballotTest = `pragma solidity >=0.4.22 <0.8.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
import "../3_Ballot.sol"; import "../3_Ballot.sol";

@ -16,7 +16,7 @@ module.exports = {
'Should launch debugger': function (browser: NightwatchBrowser) { 'Should launch debugger': function (browser: NightwatchBrowser) {
browser.addFile('blah.sol', sources[0]['browser/blah.sol']) browser.addFile('blah.sol', sources[0]['browser/blah.sol'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]') .waitForElementPresent('*[title="Deploy - transact (not payable)"]', 45000)
.click('*[title="Deploy - transact (not payable)"]') .click('*[title="Deploy - transact (not payable)"]')
.debugTransaction(0) .debugTransaction(0)
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER') .assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER')
@ -42,7 +42,6 @@ module.exports = {
.waitForElementVisible('*[data-id="slider"]') .waitForElementVisible('*[data-id="slider"]')
.click('*[data-id="slider"]') .click('*[data-id="slider"]')
.setValue('*[data-id="slider"]', '50') .setValue('*[data-id="slider"]', '50')
.pause(2000)
.assert.containsText('*[data-id="solidityLocals"]', 'no locals') .assert.containsText('*[data-id="solidityLocals"]', 'no locals')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92') .assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92')
}, },
@ -79,8 +78,10 @@ module.exports = {
browser browser
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js') .setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js')
.pause(2000)
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.testContracts('externalImport.sol', sources[1]['browser/externalImport.sol'], ['ERC20']) .testContracts('externalImport.sol', sources[1]['browser/externalImport.sol'], ['ERC20'])
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000)
.selectContract('ERC20') .selectContract('ERC20')
.createContract('"tokenName", "symbol"') .createContract('"tokenName", "symbol"')
.debugTransaction(2) .debugTransaction(2)
@ -94,6 +95,88 @@ module.exports = {
}`) != -1, }`) != -1,
'current displayed content is not from the ERC20 source code') 'current displayed content is not from the ERC20 source code')
}) })
},
'Should display correct source highlighting while debugging a contract which has ABIEncoderV2': function (browser: NightwatchBrowser) {
/*
localVariable_step266_ABIEncoder and localVariable_step717_ABIEncoder
still contains unwanted values (related to decoding calldata types)
This is still an issue @todo(https://github.com/ethereum/remix-project/issues/481), so this test will fail when this issue is fixed
*/
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js')
.clickLaunchIcon('udapp')
.testContracts('withABIEncoderV2.sol', sources[2]['browser/withABIEncoderV2.sol'], ['test'])
.selectContract('test')
.createContract('')
.clickInstance(2)
.clickFunction('test1 - transact (not payable)', {types: 'bytes userData', values: '0x000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4'})
.debugTransaction(4)
.pause(2000)
.goToVMTraceStep(261)
.pause(1000)
/*
for the test below:
source highlight should remain line `bytes32 idAsk = abi.decode(userData[:33], (bytes32));`
At this vmtrace index, the sourcemap has file = -1 because the execution is in the generated sources (ABIEncoderV2)
the atIndex of SourceLocationTracker was buggy and return an incorrect value, this is fixed
But the debugger uses now validSourcelocation, which means file is not -1.
In that case the source highlight at 261 should be the same as for step 262
*/
.waitForElementPresent('.highlightLine7')
.goToVMTraceStep(266)
.pause(1000)
.checkVariableDebug('soliditylocals', localVariable_step266_ABIEncoder) // locals should not be initiated at this point, only idAsk should
.goToVMTraceStep(717)
.pause(5000)
.checkVariableDebug('soliditylocals', localVariable_step717_ABIEncoder) // all locals should be initiaed
.clickLaunchIcon('udapp')
.clickInstance(2)
},
'Should load more solidity locals array': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.testContracts('locals.sol', sources[3]['browser/locals.sol'], ['testLocals'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 40000)
.createContract('')
.pause(2000)
.clickInstance(3)
.clickFunction('t - transact (not payable)')
.pause(2000)
.debugTransaction(6)
.waitForElementVisible('*[data-id="slider"]')
.click('*[data-id="slider"]')
.setValue('*[data-id="slider"]', '5000')
.waitForElementPresent('*[data-id="treeViewTogglearray"]')
.click('*[data-id="treeViewTogglearray"]')
.waitForElementPresent('*[data-id="treeViewLoadMore"]')
.click('*[data-id="treeViewLoadMore"]')
.assert.containsText('*[data-id="solidityLocals"]', '149: 0 uint256')
.notContainsText('*[data-id="solidityLocals"]', '150: 0 uint256')
},
'Should debug using generated sources': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.7.2+commit.51b20bc0.js')
.clickLaunchIcon('udapp')
.pause(2000)
.testContracts('withGeneratedSources.sol', sources[4]['browser/withGeneratedSources.sol'], ['A'])
.createContract('')
.clickInstance(4)
.clickFunction('f - transact (not payable)', {types: 'uint256[] ', values: '[]'})
.debugTransaction(8)
.pause(2000)
.click('*[data-id="debuggerTransactionStartButton"]') // stop debugging
.click('*[data-id="debugGeneratedSourcesLabel"]') // select debug with generated sources
.click('*[data-id="debuggerTransactionStartButton"]') // start debugging
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32) { revert(0, 0) }') != -1, 'current displayed content is not a generated source')
})
.end() .end()
}, },
@ -104,7 +187,7 @@ const sources = [
{ {
'browser/blah.sol': { 'browser/blah.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.6.0; pragma solidity >=0.7.0 <0.8.0;
contract Kickstarter { contract Kickstarter {
@ -119,7 +202,7 @@ const sources = [
Project[] public projects; Project[] public projects;
constructor() public { constructor() {
} }
@ -136,7 +219,118 @@ const sources = [
}, },
{ {
'browser/externalImport.sol': {content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; contract test7 {}'} 'browser/externalImport.sol': {content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; contract test7 {}'}
},
{
'browser/withABIEncoderV2.sol': {content: `
pragma experimental ABIEncoderV2;
contract test {
// 000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4
// 0000000000000000000000000000000000000000000000000000000000000002
function test1 (bytes calldata userData) external returns (bytes memory, bytes32, bytes32, uint) {
bytes32 idAsk = abi.decode(userData[:33], (bytes32));
bytes32 idOffer = abi.decode(userData[32:64], (bytes32));
bytes memory ro = abi.encodePacked(msg.sender, msg.sender, idAsk, idOffer);
return (ro, idAsk, idOffer, userData.length);
}
function testgp (bytes calldata userData) external returns (bytes4) {
return abi.decode(userData[:4], (bytes4));
}
}
`}
},
{
'browser/locals.sol': {
content: `
pragma solidity ^0.7.0;
contract testLocals {
function t () public {
uint[] memory array = new uint[](150);
for (uint k = 0; k < 150; k++) {
array[k] = k;
}
}
}
`
}
},
{
'browser/withGeneratedSources.sol': {
content: `
// SPDX-License-Identifier: GPL-3.0
pragma experimental ABIEncoderV2;
contract A {
function f(uint[] memory) public returns (uint256) { }
}
`
}
} }
] ]
const localVariable_step266_ABIEncoder = {
"<1>": {
"length": "0xNaN",
"type": "bytes",
"value": "0x"
},
"<2>": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
"<3>": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000000"
},
"<4>": {
"type": "uint256",
"value": "0"
},
"idAsk": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000002"
},
"userData": {
"error": "<decoding failed - no decoder for calldata>",
"type": "bytes"
}
}
const localVariable_step717_ABIEncoder = {
"<1>": {
"length": "0xd0",
"type": "bytes",
"value": "0x5b38da6a701c568545dcfcb03fcb875f56beddc45b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001"
},
"<2>": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000002"
},
"<3>": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000001"
},
"<4>": {
"type": "uint256",
"value": "84"
},
"idAsk": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000002"
},
"idOffer": {
"type": "bytes32",
"value": "0x0000000000000000000000000000000000000000000000000000000000000001"
},
"ro": {
"length": "0xd0",
"type": "bytes",
"value": "0x5b38da6a701c568545dcfcb03fcb875f56beddc45b38da6a701c568545dcfcb03fcb875f56beddc400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001"
},
"userData": {
"error": "<decoding failed - no decoder for calldata>",
"type": "bytes"
}
}

@ -55,7 +55,7 @@ module.exports = {
.addFile('copyFile.js', { content: executeCopyFile }) .addFile('copyFile.js', { content: executeCopyFile })
.executeScript(`remix.exeCurrent()`) .executeScript(`remix.exeCurrent()`)
.pause(2000) .pause(2000)
.journalLastChildIncludes('pragma solidity >=0.4.22 <0.7.0;') .journalLastChildIncludes('pragma solidity >=0.7.0 <0.8.0;')
}, },
'Should execute `rename` api from file manager external api': function (browser: NightwatchBrowser) { 'Should execute `rename` api from file manager external api': function (browser: NightwatchBrowser) {

@ -178,8 +178,8 @@ const remixIdeThemes = {
primary: '#2A9FD6', primary: '#2A9FD6',
secondary: '#555', secondary: '#555',
success: '#77B300', success: '#77B300',
info: '#9933CC', info: '#93C',
warning: '#FF8800', warning: '#F80',
danger: '#CC0000' danger: '#C00'
} }
} }

@ -44,12 +44,12 @@ module.exports = {
.addFile('Greet.sol', sources[0]['browser/Greet.sol']) .addFile('Greet.sol', sources[0]['browser/Greet.sol'])
.clickLaunchIcon('udapp') .clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') .selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c')
.waitForElementPresent('*[data-id="Deploy - transact (not payable)"]') .waitForElementPresent('*[data-id="Deploy - transact (not payable)"]', 45000)
.click('*[data-id="Deploy - transact (not payable)"]') .click('*[data-id="Deploy - transact (not payable)"]')
.pause(5000) .pause(5000)
.testFunction('0xc39ee005c1e1368c84f02e458de4b41dbb966631a8714d15ef8362dada249ede', { .testFunction('0x82f6c88a909b49d6cc003fb302a6e0184c3f08e942b62e1c95dec326d4c6020b', {
status: 'true Transaction mined and execution succeed', status: 'true Transaction mined and execution succeed',
'transaction hash': '0xc39ee005c1e1368c84f02e458de4b41dbb966631a8714d15ef8362dada249ede' 'transaction hash': '0x82f6c88a909b49d6cc003fb302a6e0184c3f08e942b62e1c95dec326d4c6020b'
}) })
}, },
@ -191,7 +191,7 @@ const sources = [
'browser/Greet.sol': { 'browser/Greet.sol': {
content: content:
` `
pragma solidity ^0.6.0; pragma solidity ^0.7.0;
contract helloWorld { contract helloWorld {
string public message; string public message;

@ -55,7 +55,7 @@ module.exports = {
const sources = [ const sources = [
{ {
'browser/signMassage.sol': {content: ` 'browser/signMassage.sol': {content: `
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.8.0;
contract SignMassageTest { contract SignMassageTest {
function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) public pure returns (address) { function testRecovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) public pure returns (address) {
return ecrecover(h, v, r, s); return ecrecover(h, v, r, s);

@ -58,6 +58,20 @@ module.exports = {
.addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol']) .addFile('Untitled7.sol', sources[6]['browser/Untitled7.sol'])
.clickLaunchIcon('fileExplorers') .clickLaunchIcon('fileExplorers')
.verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000}) .verifyContracts(['test11', 'ERC20', 'SafeMath'], {wait: 10000})
},
'Test switch to a github import from a solidity warning': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.7.4+commit.3f05b770.js')
.addFile('Untitled8.sol', sources[7]['browser/Untitled8.sol'])
.clickLaunchIcon('fileExplorers')
.clickLaunchIcon('solidity')
.waitForElementPresent('[data-id="compiledErrors"] div:nth-child(3)')
.click('[data-id="compiledErrors"] div:nth-child(3)') // select the second warning which point to ERC20 code
.getEditorValue((content) => {
browser.assert.ok(content.indexOf(`contract ERC20 is Context, IERC20`) != -1,
'current displayed content should be from the ERC20 source code')
})
.end() .end()
}, },
tearDown: sauce tearDown: sauce
@ -85,5 +99,8 @@ const sources = [
}, },
{ {
'browser/Untitled7.sol': {content: 'import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"; contract test11 {}'} 'browser/Untitled7.sol': {content: 'import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"; contract test11 {}'}
},
{
'browser/Untitled8.sol': {content: 'import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol"; contract test12 {}'}
} }
] ]

@ -90,7 +90,7 @@ module.exports = {
.scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]') .scrollAndClick('*[data-id="testTabRunTestsTabRunAction"]')
.pause(5000) .pause(5000)
.click('*[data-id="testTabRunTestsTabStopAction"]') .click('*[data-id="testTabRunTestsTabStopAction"]')
.pause(1000) // .pause(1000)
.assert.containsText('*[data-id="testTabRunTestsTabStopAction"]', 'Stopping') .assert.containsText('*[data-id="testTabRunTestsTabStopAction"]', 'Stopping')
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000) .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000)
.assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/tests/ks2b_test.sol') .assert.containsText('*[data-id="testTabSolidityUnitTestsOutput"]', 'browser/tests/ks2b_test.sol')
@ -172,7 +172,7 @@ function runTests (browser: NightwatchBrowser) {
.pause(500) .pause(500)
.scrollAndClick('#runTestsTabRunAction') .scrollAndClick('#runTestsTabRunAction')
.waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000) .waitForElementPresent('*[data-id="testTabSolidityUnitTestsOutputheader"]', 40000)
.waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 7000) .waitForElementPresent('#solidityUnittestsOutput div[class^="testPass"]', 10000)
.assert.containsText('#solidityUnittestsOutput', 'browser/tests/4_Ballot_test.sol') .assert.containsText('#solidityUnittestsOutput', 'browser/tests/4_Ballot_test.sol')
.assert.containsText('#solidityUnittestsOutput', '✓ Check winning proposal') .assert.containsText('#solidityUnittestsOutput', '✓ Check winning proposal')
.assert.containsText('#solidityUnittestsOutput', '✓ Check winnin proposal with return value') .assert.containsText('#solidityUnittestsOutput', '✓ Check winnin proposal with return value')
@ -183,12 +183,12 @@ const sources = [
{ {
'browser/simple_storage.sol': { 'browser/simple_storage.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.8.0;
contract SimpleStorage { contract SimpleStorage {
uint public storedData; uint public storedData;
constructor() public { constructor() {
storedData = 100; storedData = 100;
} }
@ -204,7 +204,7 @@ const sources = [
}, },
'browser/tests/simple_storage_test.sol': { 'browser/tests/simple_storage_test.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.8.0;
import "remix_tests.sol"; import "remix_tests.sol";
import "../simple_storage.sol"; import "../simple_storage.sol";
@ -233,7 +233,7 @@ const sources = [
}, },
'browser/ks2a.sol': { 'browser/ks2a.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.6.0; pragma solidity >=0.4.22 <0.8.0;
contract Kickstarter { contract Kickstarter {
enum State { Started, Completed } enum State { Started, Completed }
@ -246,14 +246,14 @@ const sources = [
State state; State state;
mapping(address => uint) funders; // added mapping(address => uint) funders; // added
} }
uint numProjects;
Project[] public projects; Project[] public projects;
constructor() public { constructor() {
} }
function createProject(string memory name, uint goal) public { function createProject(string memory name, uint goal) public {
projects.length++; // new line projects.push(); // new line
Project storage project = projects[projects.length - 1]; Project storage project = projects[projects.length - 1];
project.name = name; project.name = name;
project.goal = goal; project.goal = goal;
@ -287,7 +287,7 @@ const sources = [
}, },
'browser/tests/ks2b_test.sol': { 'browser/tests/ks2b_test.sol': {
content: ` content: `
pragma solidity >=0.4.22 <0.6.0; pragma solidity >=0.4.22 <0.8.0;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
@ -336,7 +336,7 @@ const sources = [
} }
function checkProjectIsFundable () public { function checkProjectIsFundable () public {
kickstarter.fundProject.value(120000)(0); kickstarter.fundProject{value:120000}(0);
(address owner, string memory name, uint goal, uint fundsAvailable, uint amountContributed, Kickstarter.State state) = kickstarter.projects(0); (address owner, string memory name, uint goal, uint fundsAvailable, uint amountContributed, Kickstarter.State state) = kickstarter.projects(0);
Assert.equal(amountContributed, 120000, "contributed amount is incorrect"); Assert.equal(amountContributed, 120000, "contributed amount is incorrect");
} }
@ -346,7 +346,7 @@ const sources = [
}, },
'browser/compilationError_test.sol': { 'browser/compilationError_test.sol': {
content: ` content: `
pragma solidity ^0.6.1; pragma solidity ^0.7.0;
contract failOnCompilation { contract failOnCompilation {
fallback() { fallback() {
@ -357,10 +357,10 @@ const sources = [
}, },
'browser/tests/deployError_test.sol': { 'browser/tests/deployError_test.sol': {
content: ` content: `
pragma solidity ^0.6.0; pragma solidity ^0.7.0;
contract failingDeploy { contract failingDeploy {
constructor() public { constructor() {
revert('Deploy Failed'); revert('Deploy Failed');
} }
} }
@ -368,7 +368,7 @@ const sources = [
}, },
'browser/tests/methodFailure_test.sol': { 'browser/tests/methodFailure_test.sol': {
content: ` content: `
pragma solidity ^0.6.0; pragma solidity ^0.7.0;
contract methodfailure { contract methodfailure {
function add(uint a, uint b) public { function add(uint a, uint b) public {

@ -6,12 +6,12 @@ import sauce from './sauce'
const sources = [ const sources = [
{ {
'browser/Untitled.sol': {content: ` 'browser/Untitled.sol': {content: `
pragma solidity >=0.4.22 <0.6.0; pragma solidity >=0.6.0 <0.8.0;
contract test1 { address test = tx.origin; } contract test1 { address test = tx.origin; }
contract test2 {} contract test2 {}
contract TooMuchGas { contract TooMuchGas {
uint x; uint x;
function() external { fallback() external {
x++; x++;
uint test; uint test;
uint test1; uint test1;
@ -36,6 +36,7 @@ function runTests (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('#icon-panel', 10000) .waitForElementVisible('#icon-panel', 10000)
.clickLaunchIcon('solidity') .clickLaunchIcon('solidity')
.pause(10000)
.testContracts('Untitled.sol', sources[0]['browser/Untitled.sol'], ['TooMuchGas', 'test1', 'test2']) .testContracts('Untitled.sol', sources[0]['browser/Untitled.sol'], ['TooMuchGas', 'test1', 'test2'])
.clickLaunchIcon('solidityStaticAnalysis') .clickLaunchIcon('solidityStaticAnalysis')
.click('#staticanalysisView button') .click('#staticanalysisView button')

@ -2,22 +2,14 @@
set -e set -e
setupRemixd () {
mkdir remixdSharedfolder
cd apps/remix-ide/contracts
echo 'sharing folder: '
echo $PWD
../../../node_modules/.bin/remixd -s $PWD --remix-ide http://127.0.0.1:8080 &
cd ../../..
}
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}} BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & npm run ganache-cli &
npm run serve & npm run serve &
setupRemixd echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &
npm run remixd &
sleep 5 sleep 5

@ -2,22 +2,14 @@
set -e set -e
setupRemixd () {
mkdir remixdSharedfolder
cd apps/remix-ide/contracts
echo 'sharing folder: '
echo $PWD
../../../node_modules/.bin/remixd -s $PWD --remix-ide http://127.0.0.1:8080 &
cd ../../..
}
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}} BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & npm run ganache-cli &
npm run serve & npm run serve &
setupRemixd echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &
npm run remixd &
sleep 5 sleep 5

@ -2,22 +2,12 @@
set -e set -e
setupRemixd () {
mkdir remixdSharedfolder
cd apps/remix-ide/contracts
echo 'sharing folder: '
echo $PWD
../../../node_modules/.bin/remixd -s $PWD --remix-ide http://127.0.0.1:8080 &
cd ../../..
}
BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}} BUILD_ID=${CIRCLE_BUILD_NUM:-${TRAVIS_JOB_NUMBER}}
echo "$BUILD_ID" echo "$BUILD_ID"
TEST_EXITCODE=0 TEST_EXITCODE=0
npm run ganache-cli & npm run ganache-cli &
npm run serve & npm run serve &
setupRemixd
sleep 5 sleep 5

@ -7,11 +7,6 @@ if [ "$CIRCLE_BRANCH" == "master" ]; then
export TAG="latest"; export TAG="latest";
fi fi
rm -rf temp_publish_docker
mkdir temp_publish_docker
cp -r $FILES_TO_PACKAGE temp_publish_docker
ls
docker login --username $DOCKER_USER --password $DOCKER_PASS docker login --username $DOCKER_USER --password $DOCKER_PASS
docker-compose -f docker-compose.yaml -f build.yaml build docker-compose -f docker-compose.yaml -f build.yaml build
docker push remixproject/remix-ide:$TAG docker push remixproject/remix-ide:$TAG

@ -0,0 +1,7 @@
#!/bin/bash
set -e
rm -rf temp_publish_docker
mkdir temp_publish_docker
cp -r $FILES_TO_PACKAGE temp_publish_docker
ls

@ -0,0 +1,24 @@
#!/bin/bash
set -e
SHA=`git rev-parse --short --verify HEAD`
git config user.name "$COMMIT_AUTHOR"
git config user.email "$COMMIT_AUTHOR_EMAIL"
git checkout --orphan gh-pages
git rm --cached -r -f .
echo "# Automatic build" > README.md
echo "Built website from \`$SHA\`. See https://github.com/ethereum/remix-ide/ for details." >> README.md
echo "To use an offline copy, download \`remix-$SHA.zip\`." >> README.md
cp -r $FILES_TO_PACKAGE "./"
rm -rf dist
ls
FILES_TO_DEPLOY="assets index.html main.js polyfills.js runtime.js vendor.js favicon.ico"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore
git add -f $FILES_TO_DEPLOY remix-$SHA.zip
git commit -m "Built website from {$SHA}."
git push -f git@github.com:ethereum/remix-live-beta.git gh-pages

@ -3,7 +3,7 @@
var fs = require('fs') var fs = require('fs')
var compiler = require('solc') var compiler = require('solc')
var compilerInput = require('@remix-project/remix-solidity').CompilerInput var compilerInput = require('@remix-project/remix-solidity').CompilerInput
var defaultVersion = 'v0.6.6+commit.6c089d02' var defaultVersion = 'v0.7.4+commit.3f05b770'
const path = require('path') const path = require('path')
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => { compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {

@ -1,6 +1,6 @@
'use strict' 'use strict'
const storage = `pragma solidity >=0.4.22 <0.7.0; const storage = `pragma solidity >=0.7.0 <0.8.0;
/** /**
* @title Storage * @title Storage
@ -27,7 +27,7 @@ contract Storage {
} }
}` }`
const owner = `pragma solidity >=0.4.22 <0.7.0; const owner = `pragma solidity >=0.7.0 <0.8.0;
/** /**
* @title Owner * @title Owner
@ -54,7 +54,7 @@ contract Owner {
/** /**
* @dev Set contract deployer as owner * @dev Set contract deployer as owner
*/ */
constructor() public { constructor() {
owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
emit OwnerSet(address(0), owner); emit OwnerSet(address(0), owner);
} }
@ -77,7 +77,7 @@ contract Owner {
} }
}` }`
const ballot = `pragma solidity >=0.4.22 <0.7.0; const ballot = `pragma solidity >=0.7.0 <0.8.0;
/** /**
* @title Ballot * @title Ballot
@ -109,7 +109,7 @@ contract Ballot {
* @dev Create a new ballot to choose one of 'proposalNames'. * @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals * @param proposalNames names of proposals
*/ */
constructor(bytes32[] memory proposalNames) public { constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender; chairperson = msg.sender;
voters[chairperson].weight = 1; voters[chairperson].weight = 1;
@ -215,7 +215,7 @@ contract Ballot {
} }
` `
var ballotTest = `pragma solidity >=0.4.22 <0.7.0; var ballotTest = `pragma solidity >=0.7.0 <0.8.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
import "../3_Ballot.sol"; import "../3_Ballot.sol";

@ -23,7 +23,7 @@ class CompilerContainer {
timeout: 300, timeout: 300,
allversions: null, allversions: null,
selectedVersion: null, selectedVersion: null,
defaultVersion: 'soljson-v0.6.6+commit.6c089d02.js' // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler defaultVersion: 'soljson-v0.7.4+commit.3f05b770.js' // this default version is defined: in makeMockCompiler (for browser test) and in package.json (downloadsolc_root) for the builtin compiler
} }
} }

@ -22,6 +22,26 @@ var css = csjs`
.statusMessage { .statusMessage {
margin-left: 15px; margin-left: 15px;
} }
.debuggerConfig {
display: flex;
align-items: center;
}
.debuggerConfig label {
margin: 0;
}
.debuggerSection {
padding: 12px 24px 16px;
}
.debuggerLabel {
margin-bottom: 2px;
font-size: 11px;
line-height: 12px;
text-transform: uppercase;
}
` `
class DebuggerUI { class DebuggerUI {
@ -32,7 +52,9 @@ class DebuggerUI {
this.event = new EventManager() this.event = new EventManager()
this.isActive = false this.isActive = false
this.opt = {
debugWithGeneratedSources: false
}
this.sourceHighlighter = new SourceHighlighter() this.sourceHighlighter = new SourceHighlighter()
this.startTxBrowser() this.startTxBrowser()
@ -72,12 +94,31 @@ class DebuggerUI {
this.isActive = isActive this.isActive = isActive
}) })
this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation) => { this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources) => {
if (!lineColumnPos) return
const contracts = await this.fetchContractAndCompile( const contracts = await this.fetchContractAndCompile(
this.currentReceipt.contractAddress || this.currentReceipt.to, this.currentReceipt.contractAddress || this.currentReceipt.to,
this.currentReceipt) this.currentReceipt)
if (contracts) { if (contracts) {
const path = contracts.getSourceName(rawLocation.file) let path = contracts.getSourceName(rawLocation.file)
if (!path) {
// check in generated sources
for (const source of generatedSources) {
if (source.id === rawLocation.file) {
path = `browser/.debugger/generated-sources/${source.name}`
let content
try {
content = await this.debuggerModule.call('fileManager', 'getFile', path, source.contents)
} catch (e) {
console.log('unable to fetch generated sources, the file probably doesn\'t exist yet', e)
}
if (content !== source.contents) {
await this.debuggerModule.call('fileManager', 'setFile', path, source.contents)
}
break
}
}
}
if (path) { if (path) {
await this.debuggerModule.call('editor', 'discardHighlight') await this.debuggerModule.call('editor', 'discardHighlight')
await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path) await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path)
@ -137,7 +178,8 @@ class DebuggerUI {
console.error(e) console.error(e)
} }
return null return null
} },
debugWithGeneratedSources: this.opt.debugWithGeneratedSources
}) })
this.listenToEvents() this.listenToEvents()
@ -167,7 +209,8 @@ class DebuggerUI {
console.error(e) console.error(e)
} }
return null return null
} },
debugWithGeneratedSources: false
}) })
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => { debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error) if (error) return reject(error)
@ -188,10 +231,16 @@ class DebuggerUI {
var view = yo` var view = yo`
<div> <div>
<div class="px-2"> <div class="px-2">
<div class="mt-3">
<p class="mt-2 ${css.debuggerLabel}">Debugger Configuration</p>
<div class="mt-2 ${css.debuggerConfig} custom-control custom-checkbox">
<input class="custom-control-input" id="debugGeneratedSourcesInput" onchange=${(event) => { this.opt.debugWithGeneratedSources = event.target.checked }} type="checkbox" title="Debug with generated sources">
<label data-id="debugGeneratedSourcesLabel" class="form-check-label custom-control-label" for="debugGeneratedSourcesInput">Debug generated sources if available (from Solidity v0.7.2)</label>
</div>
</div>
${this.txBrowser.render()} ${this.txBrowser.render()}
${this.stepManagerView} ${this.stepManagerView}
${this.debuggerHeadPanelsView} ${this.debuggerHeadPanelsView}
</div>
<div class="${css.statusMessage}">${this.statusMessage}</div> <div class="${css.statusMessage}">${this.statusMessage}</div>
${this.debuggerPanelsView} ${this.debuggerPanelsView}
</div> </div>

@ -83,9 +83,13 @@ function VmDebugger (vmDebuggerLogic) {
this.vmDebuggerLogic.event.register('solidityStateUpdating', this.solidityState.setUpdating.bind(this.solidityState)) this.vmDebuggerLogic.event.register('solidityStateUpdating', this.solidityState.setUpdating.bind(this.solidityState))
this.solidityLocals = new SolidityLocals() this.solidityLocals = new SolidityLocals()
this.solidityLocals.event.register('solidityLocalsLoadMore', (cursor) => {
this.vmDebuggerLogic.event.trigger('solidityLocalsLoadMore', [cursor])
})
this.vmDebuggerLogic.event.register('solidityLocals', this.solidityLocals.update.bind(this.solidityLocals)) this.vmDebuggerLogic.event.register('solidityLocals', this.solidityLocals.update.bind(this.solidityLocals))
this.vmDebuggerLogic.event.register('solidityLocalsMessage', this.solidityLocals.setMessage.bind(this.solidityLocals)) this.vmDebuggerLogic.event.register('solidityLocalsMessage', this.solidityLocals.setMessage.bind(this.solidityLocals))
this.vmDebuggerLogic.event.register('solidityLocalsUpdating', this.solidityLocals.setUpdating.bind(this.solidityLocals)) this.vmDebuggerLogic.event.register('solidityLocalsUpdating', this.solidityLocals.setUpdating.bind(this.solidityLocals))
this.vmDebuggerLogic.event.register('solidityLocalsLoadMoreCompleted', this.solidityLocals.loadMore.bind(this.solidityLocals))
this.returnValuesPanel = new DropdownPanel('Return Value', {json: true}) this.returnValuesPanel = new DropdownPanel('Return Value', {json: true})
this.returnValuesPanel.data = {} this.returnValuesPanel.data = {}

@ -6,18 +6,28 @@ var yo = require('yo-yo')
class SolidityLocals { class SolidityLocals {
constructor (_parent, _traceManager, _internalTreeCall) { constructor () {
this.event = new EventManager() this.event = new EventManager()
this.basicPanel = new DropdownPanel('Solidity Locals', { this.basicPanel = new DropdownPanel('Solidity Locals', {
json: true, json: true,
formatSelf: solidityTypeFormatter.formatSelf, formatSelf: solidityTypeFormatter.formatSelf,
extractData: solidityTypeFormatter.extractData extractData: solidityTypeFormatter.extractData,
loadMore: (cursor) => {
this.event.trigger('solidityLocalsLoadMore', [cursor])
}
}) })
this.view this.view
this._data = null
} }
update (data) { update (data) {
this.basicPanel.update(data) this._data = data
this.basicPanel.update(this._data)
}
loadMore (data) {
this._data = this.mergeLocals(data, this._data)
this.basicPanel.update(this._data)
} }
setMessage (message) { setMessage (message) {
@ -28,6 +38,18 @@ class SolidityLocals {
this.basicPanel.setUpdating() this.basicPanel.setUpdating()
} }
mergeLocals (locals1, locals2) {
Object.keys(locals2).map(item => {
if (locals2[item].cursor && (parseInt(locals2[item].cursor) < parseInt(locals1[item].cursor))) {
locals2[item] = {
...locals1[item],
value: [...locals2[item].value, ...locals1[item].value]
}
}
})
return locals2
}
render () { render () {
this.view = yo`<div id='soliditylocals' data-id="solidityLocals">${this.basicPanel.render()}</div>` this.view = yo`<div id='soliditylocals' data-id="solidityLocals">${this.basicPanel.render()}</div>`
return this.view return this.view

@ -34,6 +34,8 @@ function extractData (item, parent, key) {
}) })
ret.isArray = true ret.isArray = true
ret.self = parent.isArray ? '' : item.type ret.self = parent.isArray ? '' : item.type
ret.cursor = item.cursor
ret.hasNext = item.hasNext
} else if (item.type.indexOf('struct') === 0) { } else if (item.type.indexOf('struct') === 0) {
ret.children = Object.keys((item.value || {})).map(function (key) { ret.children = Object.keys((item.value || {})).map(function (key) {
return {key: key, value: item.value[key]} return {key: key, value: item.value[key]}

@ -31,7 +31,7 @@ class ContractDropdownUI {
if (success) { if (success) {
this.selectContractNames.removeAttribute('disabled') this.selectContractNames.removeAttribute('disabled')
this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => { this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => {
contractNames.appendChild(yo`<option value="${contract.name}" compiler="${compilerFullName}">${contract.name} - ${file}</option>`) contractNames.appendChild(yo`<option value="${contract.name}" compiler="${compilerFullName}">${contract.name} - ${contract.file}</option>`)
}) })
} else { } else {
this.selectContractNames.setAttribute('disabled', true) this.selectContractNames.setAttribute('disabled', true)

@ -4,7 +4,6 @@ var remixLib = require('@remix-project/remix-lib')
var EventManager = remixLib.EventManager var EventManager = remixLib.EventManager
var format = remixLib.execution.txFormat var format = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper var txHelper = remixLib.execution.txHelper
var helper = require('../../../../lib/helper.js')
/** /**
* Record transaction as long as the user create them. * Record transaction as long as the user create them.
@ -12,13 +11,11 @@ var helper = require('../../../../lib/helper.js')
* *
*/ */
class Recorder { class Recorder {
constructor (blockchain, fileManager, config) { constructor (blockchain) {
var self = this var self = this
self.event = new EventManager() self.event = new EventManager()
self.blockchain = blockchain self.blockchain = blockchain
self.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} } self.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} }
this.fileManager = fileManager
this.config = config
this.blockchain.event.register('initiatingTransaction', (timestamp, tx, payLoad) => { this.blockchain.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
if (tx.useCall) return if (tx.useCall) return
@ -29,7 +26,7 @@ class Recorder {
var record = { value, parameters: payLoad.funArgs } var record = { value, parameters: payLoad.funArgs }
if (!to) { if (!to) {
var abi = payLoad.contractABI var abi = payLoad.contractABI
var keccak = ethutil.bufferToHex(ethutil.keccak(abi)) var keccak = ethutil.bufferToHex(ethutil.keccak(JSON.stringify(abi)))
record.abi = keccak record.abi = keccak
record.contractName = payLoad.contractName record.contractName = payLoad.contractName
record.bytecode = payLoad.contractBytecode record.bytecode = payLoad.contractBytecode
@ -279,24 +276,26 @@ class Recorder {
return address return address
} }
runScenario (continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) { runScenario (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
var currentFile = this.config.get('currentFile') if (!json) {
this.fileManager.fileProviderOf(currentFile).get(currentFile, (error, json) => { return cb('a json content must be provided')
if (error) { }
return cb('Invalid Scenario File ' + error) if (typeof json === 'string') {
try {
json = JSON.parse(json)
} catch (e) {
return cb('A scenario file is required. It must be json formatted')
} }
if (!currentFile.match('.json$')) {
return cb('A scenario file is required. Please make sure a scenario file is currently displayed in the editor. The file must be of type JSON. Use the "Save Transactions" Button to generate a new Scenario File.')
} }
try { try {
var obj = JSON.parse(json) var txArray = json.transactions || []
var txArray = obj.transactions || [] var accounts = json.accounts || []
var accounts = obj.accounts || [] var options = json.options || {}
var options = obj.options || {} var abis = json.abis || {}
var abis = obj.abis || {} var linkReferences = json.linkReferences || {}
var linkReferences = obj.linkReferences || {}
} catch (e) { } catch (e) {
return cb('Invalid Scenario File, please try again') return cb('Invalid Scenario File. Please try again')
} }
if (!txArray.length) { if (!txArray.length) {
@ -306,24 +305,8 @@ class Recorder {
this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, (abi, address, contractName) => { this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, (abi, address, contractName) => {
cb(null, abi, address, contractName) cb(null, abi, address, contractName)
}) })
})
}
saveScenario (promptCb, cb) {
var txJSON = JSON.stringify(this.getAll(), null, 2)
var path = this.fileManager.currentPath()
promptCb(path, input => {
var fileProvider = this.fileManager.fileProviderOf(path)
if (!fileProvider) return
var newFile = path + '/' + input
helper.createNonClashingName(newFile, fileProvider, (error, newFile) => {
if (error) return cb('Failed to create file. ' + newFile + ' ' + error)
if (!fileProvider.set(newFile, txJSON)) return cb('Failed to create file ' + newFile)
this.fileManager.open(newFile)
})
})
} }
} }
module.exports = Recorder module.exports = Recorder

@ -1,16 +1,29 @@
var yo = require('yo-yo') var yo = require('yo-yo')
var remixLib = require('@remix-project/remix-lib') var remixLib = require('@remix-project/remix-lib')
var EventManager = remixLib.EventManager var EventManager = remixLib.EventManager
import { Plugin } from '@remixproject/engine'
var csjs = require('csjs-inject') var csjs = require('csjs-inject')
var css = require('../styles/run-tab-styles') var css = require('../styles/run-tab-styles')
import * as packageJson from '../../../../package.json'
var modalDialogCustom = require('../../ui/modal-dialog-custom') var modalDialogCustom = require('../../ui/modal-dialog-custom')
var modalDialog = require('../../ui/modaldialog') var modalDialog = require('../../ui/modaldialog')
var confirmDialog = require('../../ui/confirmDialog') var confirmDialog = require('../../ui/confirmDialog')
class RecorderUI { var helper = require('../../../lib/helper.js')
const profile = {
name: 'recorder',
methods: ['runScenario'],
version: packageJson.version
}
constructor (blockchain, recorder, logCallBack, config) { class RecorderUI extends Plugin {
constructor (blockchain, fileManager, recorder, logCallBack, config) {
super(profile)
this.fileManager = fileManager
this.blockchain = blockchain this.blockchain = blockchain
this.recorder = recorder this.recorder = recorder
this.logCallBack = logCallBack this.logCallBack = logCallBack
@ -31,10 +44,16 @@ class RecorderUI {
onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true"> onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true">
</i>` </i>`
this.runButton.onclick = this.runScenario.bind(this) this.runButton.onclick = () => {
const file = this.config.get('currentFile')
if (!file) return modalDialogCustom.alert('A scenario file has to be selected')
this.runScenario(file)
}
} }
runScenario () {
runScenario (file) {
if (!file) return modalDialogCustom.alert('Unable to run scenerio, no specified scenario file')
var continueCb = (error, continueTxExecution, cancelCb) => { var continueCb = (error, continueTxExecution, cancelCb) => {
if (error) { if (error) {
var msg = typeof error !== 'string' ? error.message : error var msg = typeof error !== 'string' ? error.message : error
@ -67,14 +86,16 @@ class RecorderUI {
const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog) const confirmationCb = this.getConfirmationCb(modalDialog, confirmDialog)
this.fileManager.readFile(file).then((json) => {
// TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
this.recorder.runScenario(continueCb, promptCb, alertCb, confirmationCb, this.logCallBack, (error, abi, address, contractName) => { this.recorder.runScenario(json, continueCb, promptCb, alertCb, confirmationCb, this.logCallBack, (error, abi, address, contractName) => {
if (error) { if (error) {
return modalDialogCustom.alert(error) return modalDialogCustom.alert(error)
} }
this.event.trigger('newScenario', [abi, address, contractName]) this.event.trigger('newScenario', [abi, address, contractName])
}) })
}).catch((error) => modalDialogCustom.alert(error))
} }
getConfirmationCb (modalDialog, confirmDialog) { getConfirmationCb (modalDialog, confirmDialog) {
@ -110,7 +131,7 @@ class RecorderUI {
} }
triggerRecordButton () { triggerRecordButton () {
this.recorder.saveScenario( this.saveScenario(
(path, cb) => { (path, cb) => {
modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb) modalDialogCustom.prompt('Save transactions as scenario', 'Transactions will be saved in a file under ' + path, 'scenario.json', cb)
}, },
@ -120,6 +141,21 @@ class RecorderUI {
) )
} }
saveScenario (promptCb, cb) {
var txJSON = JSON.stringify(this.recorder.getAll(), null, 2)
var path = this.fileManager.currentPath()
promptCb(path, input => {
var fileProvider = this.fileManager.fileProviderOf(path)
if (!fileProvider) return
var newFile = path + '/' + input
helper.createNonClashingName(newFile, fileProvider, (error, newFile) => {
if (error) return cb('Failed to create file. ' + newFile + ' ' + error)
if (!fileProvider.set(newFile, txJSON)) return cb('Failed to create file ' + newFile)
this.fileManager.open(newFile)
})
})
}
} }
module.exports = RecorderUI module.exports = RecorderUI

@ -63,7 +63,7 @@ class TestTabLogic {
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport)) let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport))
if (relative === '') relative = '.' if (relative === '') relative = '.'
const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// Import here the file to test.' const comment = hasCurrent ? `import "${relative}/${remixPath.basename(fileToImport)}";` : '// Import here the file to test.'
return `pragma solidity >=0.4.22 <0.7.0; return `pragma solidity >=0.4.22 <0.8.0;
import "remix_tests.sol"; // this import is automatically injected by Remix. import "remix_tests.sol"; // this import is automatically injected by Remix.
${comment} ${comment}

@ -2,7 +2,6 @@ var registry = require('../../global/registry')
var remixLib = require('@remix-project/remix-lib') var remixLib = require('@remix-project/remix-lib')
var yo = require('yo-yo') var yo = require('yo-yo')
var EventsDecoder = remixLib.execution.EventsDecoder var EventsDecoder = remixLib.execution.EventsDecoder
var TransactionReceiptResolver = require('../../lib/transactionReceiptResolver')
const transactionDetailsLinks = { const transactionDetailsLinks = {
'Main': 'https://www.etherscan.io/tx/', 'Main': 'https://www.etherscan.io/tx/',
@ -27,7 +26,19 @@ export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
}) })
// ----------------- Tx listener ----------------- // ----------------- Tx listener -----------------
const transactionReceiptResolver = new TransactionReceiptResolver(blockchain) let _transactionReceipts = {}
const transactionReceiptResolver = (tx, cb) => {
if (_transactionReceipts[tx.hash]) {
return cb(null, _transactionReceipts[tx.hash])
}
blockchain.web3().eth.getTransactionReceipt(tx.hash, (error, receipt) => {
if (error) {
return cb(error)
}
_transactionReceipts[tx.hash] = receipt
cb(null, receipt)
})
}
const txlistener = blockchain.getTxListener({ const txlistener = blockchain.getTxListener({
api: { api: {
@ -35,9 +46,7 @@ export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
if (compilersArtefacts['__last']) return compilersArtefacts.getAllContractDatas() if (compilersArtefacts['__last']) return compilersArtefacts.getAllContractDatas()
return null return null
}, },
resolveReceipt: function (tx, cb) { resolveReceipt: transactionReceiptResolver
transactionReceiptResolver.resolve(tx, cb)
}
} }
}) })
@ -45,11 +54,7 @@ export function makeUdapp (blockchain, compilersArtefacts, logHtmlCallback) {
blockchain.startListening(txlistener) blockchain.startListening(txlistener)
const eventsDecoder = new EventsDecoder({ const eventsDecoder = new EventsDecoder({
api: { resolveReceipt: transactionReceiptResolver
resolveReceipt: function (tx, cb) {
transactionReceiptResolver.resolve(tx, cb)
}
}
}) })
txlistener.startListening() txlistener.startListening()
registry.put({api: eventsDecoder, name: 'eventsDecoder'}) registry.put({api: eventsDecoder, name: 'eventsDecoder'})

@ -118,13 +118,13 @@ export class RunTab extends LibraryPlugin {
renderRecorder (udappUI, fileManager, config, logCallback) { renderRecorder (udappUI, fileManager, config, logCallback) {
this.recorderCount = yo`<span>0</span>` this.recorderCount = yo`<span>0</span>`
const recorder = new Recorder(this.blockchain, fileManager, config) const recorder = new Recorder(this.blockchain)
recorder.event.register('recorderCountChange', (count) => { recorder.event.register('recorderCountChange', (count) => {
this.recorderCount.innerText = count this.recorderCount.innerText = count
}) })
this.event.register('clearInstance', recorder.clearAll.bind(recorder)) this.event.register('clearInstance', recorder.clearAll.bind(recorder))
this.recorderInterface = new RecorderUI(this.blockchain, recorder, logCallback, config) this.recorderInterface = new RecorderUI(this.blockchain, fileManager, recorder, logCallback, config)
this.recorderInterface.event.register('newScenario', (abi, address, contractName) => { this.recorderInterface.event.register('newScenario', (abi, address, contractName) => {
var noInstancesText = this.noInstancesText var noInstancesText = this.noInstancesText

@ -34,6 +34,9 @@ var css = csjs`
.label_value { .label_value {
min-width: 10%; min-width: 10%;
} }
.cursor_pointer {
cursor: pointer;
}
` `
var EventManager = require('../../lib/events') var EventManager = require('../../lib/events')
@ -49,6 +52,7 @@ class TreeView {
this.event = new EventManager() this.event = new EventManager()
this.extractData = opts.extractData || this.extractDataDefault this.extractData = opts.extractData || this.extractDataDefault
this.formatSelf = opts.formatSelf || this.formatSelfDefault this.formatSelf = opts.formatSelf || this.formatSelfDefault
this.loadMore = opts.loadMore
this.view = null this.view = null
this.expandPath = [] this.expandPath = []
} }
@ -111,6 +115,9 @@ class TreeView {
self.event.trigger('nodeRightClick', [keyPath, data, label, event]) self.event.trigger('nodeRightClick', [keyPath, data, label, event])
} }
li.appendChild(list) li.appendChild(list)
if (data.hasNext) {
list.appendChild(yo`<li><span class="w-100 text-primary ${css.cursor_pointer}" data-id="treeViewLoadMore" onclick="${() => self.loadMore(data.cursor)}">Load more</span></li>`)
}
} else { } else {
caret.style.visibility = 'hidden' caret.style.visibility = 'hidden'
label.oncontextmenu = function (event) { label.oncontextmenu = function (event) {

@ -164,7 +164,6 @@ export class LandingPage extends ViewPlugin {
` `
this.adjustMediaPanel() this.adjustMediaPanel()
globalRegistry.get('themeModule').api.events.on('themeChanged', (theme) => { globalRegistry.get('themeModule').api.events.on('themeChanged', (theme) => {
console.log("theme is ", theme.quality)
this.onThemeChanged(theme.quality) this.onThemeChanged(theme.quality)
}) })
} }
@ -177,6 +176,7 @@ export class LandingPage extends ViewPlugin {
hideMediaPanel (e) { hideMediaPanel (e) {
const mediaPanelsTitle = document.getElementById('remixIDEMediaPanelsTitle') const mediaPanelsTitle = document.getElementById('remixIDEMediaPanelsTitle')
const mediaPanels = document.getElementById('remixIDEMediaPanels') const mediaPanels = document.getElementById('remixIDEMediaPanels')
if (!mediaPanelsTitle || !mediaPanels) return
if (!mediaPanelsTitle.contains(e.target) && !mediaPanels.contains(e.target)) { if (!mediaPanelsTitle.contains(e.target) && !mediaPanels.contains(e.target)) {
this.mediumPanel.classList.remove('d-block') this.mediumPanel.classList.remove('d-block')
this.mediumPanel.classList.add('d-none') this.mediumPanel.classList.add('d-none')
@ -186,7 +186,6 @@ export class LandingPage extends ViewPlugin {
} }
onThemeChanged (themeQuality) { onThemeChanged (themeQuality) {
console.log("themes in listener is", themeQuality)
let twitterFrame = yo` let twitterFrame = yo`
<div class="px-2 ${css.media}"> <div class="px-2 ${css.media}">
<a class="twitter-timeline" <a class="twitter-timeline"
@ -204,7 +203,6 @@ export class LandingPage extends ViewPlugin {
} }
showMediaPanel (e) { showMediaPanel (e) {
console.log(e)
if (e.target.id === 'remixIDEHomeTwitterbtn') { if (e.target.id === 'remixIDEHomeTwitterbtn') {
this.mediumPanel.classList.remove('d-block') this.mediumPanel.classList.remove('d-block')
this.mediumPanel.classList.add('d-none') this.mediumPanel.classList.add('d-none')

@ -15,12 +15,14 @@ module.exports = {
}, },
promptPassphraseCreation: function (ok, cancel) { promptPassphraseCreation: function (ok, cancel) {
var text = 'Please provide a Passphrase for the account creation' var text = 'Please provide a Passphrase for the account creation'
var input = yo`<div> var input = yo`
<input id="prompt1" type="password" name='prompt_text' class="${css['prompt_text']}" > <div>
<input id="prompt1" type="password" name='prompt_text' class="${css['prompt_text']}" oninput="${(e) => validateInput(e)}">
<br> <br>
<br> <br>
<input id="prompt2" type="password" name='prompt_text' class="${css['prompt_text']}" > <input id="prompt2" type="password" name='prompt_text' class="${css['prompt_text']}" oninput="${(e) => validateInput(e)}">
</div>` </div>
`
return modal(null, yo`<div>${text}<div>${input}</div></div>`, return modal(null, yo`<div>${text}<div>${input}</div></div>`,
{ {
fn: () => { fn: () => {
@ -42,7 +44,16 @@ module.exports = {
}, },
promptMulti: function ({ title, text, inputValue }, ok, cancel) { promptMulti: function ({ title, text, inputValue }, ok, cancel) {
if (!inputValue) inputValue = '' if (!inputValue) inputValue = ''
var input = yo`<textarea id="prompt_text" data-id="modalDialogCustomPromptText" class=${css.prompt_text} rows="4" cols="50"></textarea>` const input = yo`
<textarea
id="prompt_text"
data-id="modalDialogCustomPromptText"
class=${css.prompt_text}
rows="4"
cols="50"
oninput="${(e) => validateInput(e)}"
></textarea>
`
return modal(title, yo`<div>${text}<div>${input}</div></div>`, return modal(title, yo`<div>${text}<div>${input}</div></div>`,
{ {
fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) } fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) }
@ -64,10 +75,34 @@ module.exports = {
} }
} }
const validateInput = (e) => {
console.log('validation from ', e)
if (!document.getElementById('modal-footer-ok')) return
if (e.target.value === '') {
document.getElementById('modal-footer-ok').classList.add('disabled')
document.getElementById('modal-footer-ok').style.pointerEvents = 'none'
} else {
document.getElementById('modal-footer-ok').classList.remove('disabled')
document.getElementById('modal-footer-ok').style.pointerEvents = 'auto'
}
}
function prompt (title, text, hidden, inputValue, ok, cancel, focus) { function prompt (title, text, hidden, inputValue, ok, cancel, focus) {
if (!inputValue) inputValue = '' if (!inputValue) inputValue = ''
var type = hidden ? 'password' : 'text' var type = hidden ? 'password' : 'text'
var input = yo`<input type=${type} name='prompt_text' id='prompt_text' class="${css['prompt_text']} form-control" value='${inputValue}' data-id="modalDialogCustomPromptText">` var input = yo`
<input
type=${type}
name='prompt_text'
id='prompt_text'
class="${css['prompt_text']} form-control"
value='${inputValue}'
data-id="modalDialogCustomPromptText"
oninput="${(e) => validateInput(e)}"
>
`
modal(title, yo`<div>${text}<div>${input}</div></div>`, modal(title, yo`<div>${text}<div>${input}</div></div>`,
{ {
fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) } fn: () => { if (typeof ok === 'function') ok(document.getElementById('prompt_text').value) }

@ -130,7 +130,7 @@ module.exports = (title, content, ok, cancel, focusSelector, opts) => {
function html (opts) { function html (opts) {
return yo` return yo`
<div id="modal-dialog" data-id="modalDialogContainer" class="modal" tabindex="-1" role="dialog"> <div id="modal-dialog" data-id="modalDialogContainer" data-backdrop="static" data-keyboard="false" class="modal" tabindex="-1" role="dialog">
<div id="modal-background" class="modal-dialog" role="document"> <div id="modal-background" class="modal-dialog" role="document">
<div class="modal-content ${css.modalContent} ${opts.class}"> <div class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header"> <div class="modal-header">

@ -81,18 +81,24 @@ Renderer.prototype.error = function (message, container, opt) {
text = message.innerText text = message.innerText
} }
var errLocation = text.match(/^([^:]*):([0-9]*):(([0-9]*):)? /) // ^ e.g:
if ((!opt.errFile || !opt.errCol || !opt.errLine) && errLocation) { // browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12
errLocation = parseRegExError(errLocation) // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0/contracts/introspection/IERC1820Registry.sol:3:1: ParserError: Source file requires different compiler version (current compiler is 0.7.4+commit.3f05b770.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
opt.errFile = errLocation.errFile
opt.errLine = errLocation.errLine // extract line / column
opt.errCol = errLocation.errCol let position = text.match(/^(.*?):([0-9]*?):([0-9]*?)?/)
} opt.errLine = position ? parseInt(position[2]) - 1 : -1
opt.errCol = position ? parseInt(position[3]) : -1
// extract file
position = text.match(/^(https:.*?|http:.*?|.*?):/)
opt.errFile = position ? position[1] : ''
if (!opt.noAnnotations && errLocation) { if (!opt.noAnnotations && opt.errFile) {
this._error(errLocation.errFile, { this._error(opt.errFile, {
row: errLocation.errLine, row: opt.errLine,
column: errLocation.errCol, column: opt.errCol,
text: text, text: text,
type: opt.type type: opt.type
}) })
@ -107,7 +113,7 @@ Renderer.prototype.error = function (message, container, opt) {
$error.click((ev) => { $error.click((ev) => {
if (opt.click) { if (opt.click) {
opt.click(message) opt.click(message)
} else if (opt.errFile && opt.errLine && opt.errCol) { } else if (opt.errFile !== undefined && opt.errLine !== undefined && opt.errCol !== undefined) {
this._errorClick(opt.errFile, opt.errLine, opt.errCol) this._errorClick(opt.errFile, opt.errLine, opt.errCol)
} }
}) })
@ -119,12 +125,4 @@ Renderer.prototype.error = function (message, container, opt) {
}) })
} }
function parseRegExError (err) {
return {
errFile: err[1],
errLine: parseInt(err[2], 10) - 1,
errCol: err[4] ? parseInt(err[4], 10) : 0
}
}
module.exports = Renderer module.exports = Renderer

@ -5,7 +5,7 @@ var css = csjs`
.modalFooter { .modalFooter {
} }
.modalContent { .modalContent {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); box-shadow: 0 0 8px 1000px rgba(0,0,0,0.6),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop; -webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s; -webkit-animation-duration: 0.4s;
animation-name: animatetop; animation-name: animatetop;

@ -37,7 +37,7 @@ class InjectedProvider {
signMessage (message, account, _passphrase, cb) { signMessage (message, account, _passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message)) const messageHash = hashPersonalMessage(Buffer.from(message))
try { try {
this.executionContext.web3().eth.sign(message, account, (error, signedData) => { this.executionContext.web3().eth.personal.sign(message, account, (error, signedData) => {
cb(error, '0x' + messageHash.toString('hex'), signedData) cb(error, '0x' + messageHash.toString('hex'), signedData)
}) })
} catch (e) { } catch (e) {

@ -1,23 +0,0 @@
'use strict'
module.exports = class TransactionReceiptResolver {
constructor (blockchain) {
this._transactionReceipts = {}
this.blockchain = blockchain
}
resolve (tx, cb) {
if (this._transactionReceipts[tx.hash]) {
return cb(null, this._transactionReceipts[tx.hash])
}
this.blockchain.web3().eth.getTransactionReceipt(tx.hash, (error, receipt) => {
if (!error) {
this._transactionReceipts[tx.hash] = receipt
cb(null, receipt)
} else {
cb(error)
}
})
}
}

@ -10,7 +10,7 @@ const requiredModules = [ // services + layout views + system views
'terminal', 'settings', 'pluginManager'] 'terminal', 'settings', 'pluginManager']
export function isNative (name) { export function isNative (name) {
const nativePlugins = ['vyper', 'workshops'] const nativePlugins = ['vyper', 'workshops', 'debugger']
return nativePlugins.includes(name) || requiredModules.includes(name) return nativePlugins.includes(name) || requiredModules.includes(name)
} }

@ -22,7 +22,7 @@ Remix is built out of several different modules. Here is the brief description.
+ [`remix-lib`](remix-lib/README.md): Common place for libraries being used across multiple modules + [`remix-lib`](remix-lib/README.md): Common place for libraries being used across multiple modules
+ [`remix-tests`](remix-tests/README.md): Unit test Solidity smart contracts. It works as a plugin & as CLI both + [`remix-tests`](remix-tests/README.md): Unit test Solidity smart contracts. It works as a plugin & as CLI both
+ [`remix-url-resolver`](remix-url-resolver/README.md): Provide helpers for resolving the content from external URL ( including github, swarm, ipfs etc.). + [`remix-url-resolver`](remix-url-resolver/README.md): Provide helpers for resolving the content from external URL ( including github, swarm, ipfs etc.).
+ [`remixd`](https://github.com/ethereum/remixd/tree/master): Allow accessing local filesystem from Remix IDE by running a daemon + [`remixd`](remixd/README.md): Allow accessing local filesystem from Remix IDE by running a daemon
Each module generally has their own npm package and test suite, as well as basic documentation in their respective `README`s. Usage of modules as plugin is well documented **[here](https://remix-ide.readthedocs.io/en/latest/index.html)**. Each module generally has their own npm package and test suite, as well as basic documentation in their respective `README`s. Usage of modules as plugin is well documented **[here](https://remix-ide.readthedocs.io/en/latest/index.html)**.

@ -1,7 +1,8 @@
{ {
"extends": "../../.eslintrc", "extends": "../../.eslintrc",
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "off" "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/prefer-namespace-keyword": "off"
}, },
"ignorePatterns": ["!**/*"] "ignorePatterns": ["!**/*"]
} }

@ -1,4 +1,4 @@
// Type definitiosn for the things we need from remix-lib // Type definitions for the things we need from remix-lib
declare module "remix-lib" { declare module "remix-lib" {
export module util { export module util {

@ -18,6 +18,14 @@ export function isAstNode(node: Record<string, unknown>): boolean {
) )
} }
export function isYulAstNode(node: Record<string, unknown>): boolean {
return (
isObject(node) &&
'nodeType' in node &&
'src' in node
)
}
/** /**
* Crawl the given AST through the function walk(ast, callback) * Crawl the given AST through the function walk(ast, callback)
@ -200,7 +208,7 @@ export class AstWalker extends EventEmitter {
} }
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
walkFullInternal(ast: AstNode, callback: Function) { walkFullInternal(ast: AstNode, callback: Function) {
if (isAstNode(ast)) { if (isAstNode(ast) || isYulAstNode(ast)) {
// console.log(`XXX id ${ast.id}, nodeType: ${ast.nodeType}, src: ${ast.src}`); // console.log(`XXX id ${ast.id}, nodeType: ${ast.nodeType}, src: ${ast.src}`);
callback(ast); callback(ast);
for (const k of Object.keys(ast)) { for (const k of Object.keys(ast)) {
@ -223,7 +231,7 @@ export class AstWalker extends EventEmitter {
// Normalizes parameter callback and calls walkFullInternal // Normalizes parameter callback and calls walkFullInternal
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
walkFull(ast: AstNode, callback: any) { walkFull(ast: AstNode, callback: any) {
if (isAstNode(ast)) return this.walkFullInternal(ast, callback); if (isAstNode(ast) || isYulAstNode(ast)) return this.walkFullInternal(ast, callback);
} }
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types

@ -1,4 +1,4 @@
import { isAstNode, AstWalker } from './astWalker'; import { isAstNode, isYulAstNode, AstWalker } from './astWalker';
import { AstNode, LineColPosition, LineColRange, Location } from "./types"; import { AstNode, LineColPosition, LineColRange, Location } from "./types";
import { util } from "@remix-project/remix-lib"; import { util } from "@remix-project/remix-lib";
@ -31,7 +31,7 @@ export function lineColPositionFromOffset(offset: number, lineBreaks: Array<numb
* @param astNode The object to convert. * @param astNode The object to convert.
*/ */
export function sourceLocationFromAstNode(astNode: AstNode): Location | null { export function sourceLocationFromAstNode(astNode: AstNode): Location | null {
if (isAstNode(astNode) && astNode.src) { if (isAstNode(astNode) && isYulAstNode(astNode) && astNode.src) {
return sourceLocationFromSrc(astNode.src) return sourceLocationFromSrc(astNode.src)
} }
return null; return null;

@ -26,6 +26,7 @@ const {SolidityProxy, stateDecoder, localDecoder, InternalCallTree} = require('.
function Ethdebugger (opts) { function Ethdebugger (opts) {
this.compilationResult = opts.compilationResult || function (contractAddress) { return null } this.compilationResult = opts.compilationResult || function (contractAddress) { return null }
this.web3 = opts.web3 this.web3 = opts.web3
this.opts = opts
this.event = new EventManager() this.event = new EventManager()
@ -33,19 +34,30 @@ function Ethdebugger (opts) {
this.traceManager = new TraceManager({web3: this.web3}) this.traceManager = new TraceManager({web3: this.web3})
this.codeManager = new CodeManager(this.traceManager) this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy(this.traceManager, this.codeManager) this.solidityProxy = new SolidityProxy({getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager)})
this.storageResolver = null this.storageResolver = null
this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true }) const includeLocalVariables = true
this.callTree = new InternalCallTree(this.event,
this.traceManager,
this.solidityProxy,
this.codeManager,
{ ...opts, includeLocalVariables})
} }
Ethdebugger.prototype.setManagers = function () { Ethdebugger.prototype.setManagers = function () {
this.traceManager = new TraceManager({web3: this.web3}) this.traceManager = new TraceManager({web3: this.web3})
this.codeManager = new CodeManager(this.traceManager) this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy(this.traceManager, this.codeManager) this.solidityProxy = new SolidityProxy({getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager)})
this.storageResolver = null this.storageResolver = null
const includeLocalVariables = true
this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true })
this.callTree = new InternalCallTree(this.event,
this.traceManager,
this.solidityProxy,
this.codeManager,
{ ...this.opts, includeLocalVariables})
this.event.trigger('managersChanged')
} }
Ethdebugger.prototype.resolveStep = function (index) { Ethdebugger.prototype.resolveStep = function (index) {
@ -60,6 +72,10 @@ Ethdebugger.prototype.sourceLocationFromVMTraceIndex = async function (address,
return this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts) return this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts)
} }
Ethdebugger.prototype.getValidSourceLocationFromVMTraceIndex = async function (address, stepIndex) {
return this.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts)
}
Ethdebugger.prototype.sourceLocationFromInstructionIndex = async function (address, instIndex, callback) { Ethdebugger.prototype.sourceLocationFromInstructionIndex = async function (address, instIndex, callback) {
return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts) return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts)
} }

@ -79,6 +79,7 @@ class CmdLine {
this.txHash = txNumber this.txHash = txNumber
this.debugger.debug(null, txNumber, null, () => { this.debugger.debug(null, txNumber, null, () => {
this.debugger.event.register('newSourceLocation', (lineColumnPos, rawLocation) => { this.debugger.event.register('newSourceLocation', (lineColumnPos, rawLocation) => {
if (!lineColumnPos) return
this.lineColumnPos = lineColumnPos this.lineColumnPos = lineColumnPos
this.rawLocation = rawLocation this.rawLocation = rawLocation
this.events.emit('source', [lineColumnPos, rawLocation]) this.events.emit('source', [lineColumnPos, rawLocation])

@ -15,13 +15,20 @@ class BreakpointManager {
* @param {Object} _debugger - type of EthDebugger * @param {Object} _debugger - type of EthDebugger
* @return {Function} _locationToRowConverter - function implemented by editor which return a column/line position for a char source location * @return {Function} _locationToRowConverter - function implemented by editor which return a column/line position for a char source location
*/ */
constructor (_debugger, _locationToRowConverter, _jumpToCallback) { constructor ({traceManager, callTree, solidityProxy, locationToRowConverter}) {
this.event = new EventManager() this.event = new EventManager()
this.debugger = _debugger this.traceManager = traceManager
this.callTree = callTree
this.solidityProxy = solidityProxy
this.breakpoints = {} this.breakpoints = {}
this.locationToRowConverter = _locationToRowConverter this.locationToRowConverter = locationToRowConverter
this.previousLine this.previousLine
this.jumpToCallback = _jumpToCallback || (() => {}) // eslint-disable-line }
setManagers({traceManager, callTree, solidityProxy}) {
this.traceManager = traceManager
this.callTree = callTree
this.solidityProxy = solidityProxy
} }
/** /**
@ -30,7 +37,10 @@ class BreakpointManager {
* *
*/ */
async jumpNextBreakpoint (fromStep, defaultToLimit) { async jumpNextBreakpoint (fromStep, defaultToLimit) {
this.jump(fromStep || 0, 1, defaultToLimit) if (!this.locationToRowConverter) {
return console.log('row converter not provided')
}
this.jump(fromStep || 0, 1, defaultToLimit, this.traceManager.trace)
} }
/** /**
@ -39,64 +49,61 @@ class BreakpointManager {
* *
*/ */
async jumpPreviousBreakpoint (fromStep, defaultToLimit) { async jumpPreviousBreakpoint (fromStep, defaultToLimit) {
this.jump(fromStep || 0, -1, defaultToLimit)
}
/**
* start looking for the previous or next breakpoint
* @param {Int} direction - 1 or -1 direction of the search
* @param {Bool} defaultToLimit - if true jump to the limit (end if direction is 1, beginning if direction is -1) of the trace if no more breakpoint found
*
*/
async jump (fromStep, direction, defaultToLimit) {
if (!this.locationToRowConverter) { if (!this.locationToRowConverter) {
console.log('row converter not provided') return console.log('row converter not provided')
return }
this.jump(fromStep || 0, -1, defaultToLimit, this.traceManager.trace)
} }
function depthChange (step, trace) { depthChange (step, trace) {
return trace[step].depth !== trace[step - 1].depth return trace[step].depth !== trace[step - 1].depth
} }
function hitLine (currentStep, sourceLocation, previousSourceLocation, self) { hitLine(currentStep, sourceLocation, previousSourceLocation, trace) {
// isJumpDestInstruction -> returning from a internal function call // isJumpDestInstruction -> returning from a internal function call
// depthChange -> returning from an external call // depthChange -> returning from an external call
// sourceLocation.start <= previousSourceLocation.start && ... -> previous src is contained in the current one // sourceLocation.start <= previousSourceLocation.start && ... -> previous src is contained in the current one
if ((helper.isJumpDestInstruction(self.debugger.traceManager.trace[currentStep]) && previousSourceLocation.jump === 'o') || if ((helper.isJumpDestInstruction(trace[currentStep]) && previousSourceLocation.jump === 'o') ||
depthChange(currentStep, self.debugger.traceManager.trace) || this.depthChange(currentStep, trace) ||
(sourceLocation.start <= previousSourceLocation.start && (sourceLocation.start <= previousSourceLocation.start &&
sourceLocation.start + sourceLocation.length >= previousSourceLocation.start + previousSourceLocation.length)) { sourceLocation.start + sourceLocation.length >= previousSourceLocation.start + previousSourceLocation.length)) {
return false return false
} }
self.jumpToCallback(currentStep) this.event.trigger('breakpointStep', [currentStep])
self.event.trigger('breakpointHit', [sourceLocation, currentStep]) this.event.trigger('breakpointHit', [sourceLocation, currentStep])
return true return true
} }
/**
* start looking for the previous or next breakpoint
* @param {Int} direction - 1 or -1 direction of the search
* @param {Bool} defaultToLimit - if true jump to the limit (end if direction is 1, beginning if direction is -1) of the trace if no more breakpoint found
*
*/
async jump (fromStep, direction, defaultToLimit, trace) {
let sourceLocation let sourceLocation
let previousSourceLocation let previousSourceLocation
let currentStep = fromStep + direction let currentStep = fromStep + direction
let lineHadBreakpoint = false let lineHadBreakpoint = false
while (currentStep > 0 && currentStep < this.debugger.traceManager.trace.length) { while (currentStep > 0 && currentStep < trace.length) {
try { try {
previousSourceLocation = sourceLocation previousSourceLocation = sourceLocation
sourceLocation = await this.debugger.callTree.extractSourceLocation(currentStep) sourceLocation = await this.callTree.extractValidSourceLocation(currentStep)
} catch (e) { } catch (e) {
console.log('cannot jump to breakpoint ' + e) return console.log('cannot jump to breakpoint ' + e)
return
} }
let lineColumn = await this.locationToRowConverter(sourceLocation) let lineColumn = await this.locationToRowConverter(sourceLocation)
if (this.previousLine !== lineColumn.start.line) { if (this.previousLine !== lineColumn.start.line) {
if (direction === -1 && lineHadBreakpoint) { // TODO : improve this when we will build the correct structure before hand if (direction === -1 && lineHadBreakpoint) { // TODO : improve this when we will build the correct structure before hand
lineHadBreakpoint = false lineHadBreakpoint = false
if (hitLine(currentStep + 1, previousSourceLocation, sourceLocation, this)) { if (this.hitLine(currentStep + 1, previousSourceLocation, sourceLocation, trace)) {
return return
} }
} }
this.previousLine = lineColumn.start.line this.previousLine = lineColumn.start.line
if (this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line)) { if (this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line)) {
lineHadBreakpoint = true lineHadBreakpoint = true
if (direction === 1 && hitLine(currentStep, sourceLocation, previousSourceLocation, this)) { if (direction === 1 && this.hitLine(currentStep, sourceLocation, previousSourceLocation, trace)) {
return return
} }
} }
@ -108,9 +115,9 @@ class BreakpointManager {
return return
} }
if (direction === -1) { if (direction === -1) {
this.jumpToCallback(0) this.event.trigger('breakpointStep', [0])
} else if (direction === 1) { } else if (direction === 1) {
this.jumpToCallback(this.debugger.traceManager.trace.length - 1) this.event.trigger('breakpointStep', [trace.length - 1])
} }
} }
@ -122,7 +129,7 @@ class BreakpointManager {
* @return {Bool} return true if the given @arg fileIndex @arg line refers to a breakpoint * @return {Bool} return true if the given @arg fileIndex @arg line refers to a breakpoint
*/ */
hasBreakpointAtLine (fileIndex, line) { hasBreakpointAtLine (fileIndex, line) {
const filename = this.debugger.solidityProxy.fileNameFromIndex(fileIndex) const filename = this.solidityProxy.fileNameFromIndex(fileIndex)
if (!(filename && this.breakpoints[filename])) { if (!(filename && this.breakpoints[filename])) {
return false return false
} }

@ -16,7 +16,16 @@ function CodeManager (_traceManager) {
this.event = new EventManager() this.event = new EventManager()
this.isLoading = false this.isLoading = false
this.traceManager = _traceManager this.traceManager = _traceManager
this.codeResolver = new CodeResolver({web3: this.traceManager.web3}) this.codeResolver = new CodeResolver({getCode: async (address) => {
return new Promise((resolve, reject) => {
this.traceManager.web3.eth.getCode(address, (error, code) => {
if (error) {
return reject(error)
}
return resolve(code)
})
})
}})
} }
/** /**

@ -1,8 +1,8 @@
'use strict' 'use strict'
const codeUtils = require('./codeUtils') const codeUtils = require('./codeUtils')
function CodeResolver (options) { function CodeResolver ({getCode}) {
this.web3 = options.web3 this.getCode = getCode
this.bytecodeByAddress = {} // bytes code by contract addesses this.bytecodeByAddress = {} // bytes code by contract addesses
this.instructionsByAddress = {} // assembly items instructions list by contract addesses this.instructionsByAddress = {} // assembly items instructions list by contract addesses
@ -16,20 +16,13 @@ CodeResolver.prototype.clear = function () {
} }
CodeResolver.prototype.resolveCode = async function (address) { CodeResolver.prototype.resolveCode = async function (address) {
return new Promise((resolve, reject) => {
const cache = this.getExecutingCodeFromCache(address) const cache = this.getExecutingCodeFromCache(address)
if (cache) { if (cache) {
return resolve(cache) return cache
} }
this.web3.eth.getCode(address, (error, code) => { const code = await this.getCode(address)
if (error) { return this.cacheExecutingCode(address, code)
// return console.log(error)
return reject(error)
}
return resolve(this.cacheExecutingCode(address, code))
})
})
} }
CodeResolver.prototype.cacheExecutingCode = function (address, hexCode) { CodeResolver.prototype.cacheExecutingCode = function (address, hexCode) {
@ -41,11 +34,8 @@ CodeResolver.prototype.cacheExecutingCode = function (address, hexCode) {
} }
CodeResolver.prototype.formatCode = function (hexCode) { CodeResolver.prototype.formatCode = function (hexCode) {
const code = codeUtils.nameOpCodes(Buffer.from(hexCode.substring(2), 'hex')) const [code, instructionsIndexByBytesOffset] = codeUtils.nameOpCodes(Buffer.from(hexCode.substring(2), 'hex'))
return { return {code, instructionsIndexByBytesOffset}
code: code[0],
instructionsIndexByBytesOffset: code[1]
}
} }
CodeResolver.prototype.getExecutingCodeFromCache = function (address) { CodeResolver.prototype.getExecutingCodeFromCache = function (address) {

@ -228,6 +228,10 @@ class VmDebuggerLogic {
listenToSolidityLocalsEvents () { listenToSolidityLocalsEvents () {
this.event.register('sourceLocationChanged', this.debuggerSolidityLocals.init.bind(this.debuggerSolidityLocals)) this.event.register('sourceLocationChanged', this.debuggerSolidityLocals.init.bind(this.debuggerSolidityLocals))
this.event.register('solidityLocalsLoadMore', this.debuggerSolidityLocals.decodeMore.bind(this.debuggerSolidityLocals))
this.debuggerSolidityLocals.event.register('solidityLocalsLoadMoreCompleted', (locals) => {
this.event.trigger('solidityLocalsLoadMoreCompleted', [locals])
})
this.debuggerSolidityLocals.event.register('solidityLocals', (state) => { this.debuggerSolidityLocals.event.register('solidityLocals', (state) => {
this.event.trigger('solidityLocals', [state]) this.event.trigger('solidityLocals', [state])
}) })

@ -18,15 +18,24 @@ function Debugger (options) {
this.debugger = new Ethdebugger({ this.debugger = new Ethdebugger({
web3: options.web3, web3: options.web3,
compilationResult: this.compilationResult debugWithGeneratedSources: options.debugWithGeneratedSources,
compilationResult: this.compilationResult,
}) })
this.breakPointManager = new BreakpointManager(this.debugger, async (sourceLocation) => { const {traceManager, callTree, solidityProxy} = this.debugger
this.breakPointManager = new BreakpointManager({traceManager, callTree, solidityProxy, locationToRowConverter: async (sourceLocation) => {
const compilationResult = await this.compilationResult() const compilationResult = await this.compilationResult()
if (!compilationResult) return { start: null, end: null } if (!compilationResult) return { start: null, end: null }
return this.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, compilationResult.source.sources, compilationResult.data.sources) return this.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, compilationResult.source.sources, compilationResult.data.sources)
}, (step) => { }})
this.event.trigger('breakpointStep', [step])
this.breakPointManager.event.register('managersChanged', () => {
const {traceManager, callTree, solidityProxy} = this.debugger
this.breakPointManager.setManagers({traceManager, callTree, solidityProxy})
})
this.breakPointManager.event.register('breakpointStep', (step) => {
this.step_manager.jumpTo(step)
}) })
this.debugger.setBreakpointManager(this.breakPointManager) this.debugger.setBreakpointManager(this.breakPointManager)
@ -38,10 +47,6 @@ function Debugger (options) {
this.debugger.event.register('traceUnloaded', this, () => { this.debugger.event.register('traceUnloaded', this, () => {
this.event.trigger('debuggerStatus', [false]) this.event.trigger('debuggerStatus', [false])
}) })
this.event.register('breakpointStep', (step) => {
this.step_manager.jumpTo(step)
})
} }
Debugger.prototype.registerAndHighlightCodeItem = async function (index) { Debugger.prototype.registerAndHighlightCodeItem = async function (index) {
@ -53,10 +58,19 @@ Debugger.prototype.registerAndHighlightCodeItem = async function (index) {
const compilationResultForAddress = await this.compilationResult(address) const compilationResultForAddress = await this.compilationResult(address)
if (!compilationResultForAddress) return if (!compilationResultForAddress) return
this.debugger.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then((rawLocation) => { this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then((rawLocation) => {
if (compilationResultForAddress && compilationResultForAddress.data) { if (compilationResultForAddress && compilationResultForAddress.data) {
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, compilationResultForAddress.source.sources, compilationResultForAddress.data.sources) const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation]) const astSources = Object.assign({}, compilationResultForAddress.data.sources)
const sources = Object.assign({}, compilationResultForAddress.source.sources)
if (generatedSources) {
for (const genSource of generatedSources) {
astSources[genSource.name] = { id: genSource.id, ast: genSource.ast }
sources[genSource.name] = { content: genSource.contents }
}
}
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources])
} else { } else {
this.event.trigger('newSourceLocation', [null]) this.event.trigger('newSourceLocation', [null])
} }
@ -114,7 +128,7 @@ Debugger.prototype.debugTx = function (tx, loadingCb) {
this.step_manager = new StepManager(this.debugger, this.debugger.traceManager) this.step_manager = new StepManager(this.debugger, this.debugger.traceManager)
this.debugger.codeManager.event.register('changed', this, (code, address, instIndex) => { this.debugger.codeManager.event.register('changed', this, (code, address, instIndex) => {
this.debugger.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, this.step_manager.currentStepIndex, this.debugger.solidityProxy.contracts).then((sourceLocation) => { this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, this.step_manager.currentStepIndex, this.debugger.solidityProxy.contracts).then((sourceLocation) => {
this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [sourceLocation]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [sourceLocation])
}) })
}) })

@ -15,6 +15,7 @@ class DebuggerSolidityLocals {
} }
init (sourceLocation) { init (sourceLocation) {
this._sourceLocation = sourceLocation
var decodeTimeout = null var decodeTimeout = null
if (!this.storageResolver) { if (!this.storageResolver) {
return this.event.trigger('solidityLocalsMessage', ['storage not ready']) return this.event.trigger('solidityLocalsMessage', ['storage not ready'])
@ -28,7 +29,7 @@ class DebuggerSolidityLocals {
}, 500) }, 500)
} }
decode (sourceLocation) { decode (sourceLocation, cursor) {
const self = this const self = this
this.event.trigger('solidityLocalsMessage', ['']) this.event.trigger('solidityLocalsMessage', [''])
this.traceManager.waterfall([ this.traceManager.waterfall([
@ -65,13 +66,19 @@ class DebuggerSolidityLocals {
var memory = result[1].value var memory = result[1].value
try { try {
var storageViewer = new StorageViewer({ stepIndex: this.stepManager.currentStepIndex, tx: this.tx, address: result[2].value }, this.storageResolver, this.traceManager) var storageViewer = new StorageViewer({ stepIndex: this.stepManager.currentStepIndex, tx: this.tx, address: result[2].value }, this.storageResolver, this.traceManager)
localDecoder.solidityLocals(this.stepManager.currentStepIndex, this.internalTreeCall, stack, memory, storageViewer, sourceLocation).then((locals) => { localDecoder.solidityLocals(this.stepManager.currentStepIndex, this.internalTreeCall, stack, memory, storageViewer, sourceLocation, cursor).then((locals) => {
if (!cursor) {
if (!locals.error) { if (!locals.error) {
this.event.trigger('solidityLocals', [locals]) this.event.trigger('solidityLocals', [locals])
} }
if (!Object.keys(locals).length) { if (!Object.keys(locals).length) {
this.event.trigger('solidityLocalsMessage', ['no locals']) this.event.trigger('solidityLocalsMessage', ['no locals'])
} }
} else {
if (!locals.error) {
this.event.trigger('solidityLocalsLoadMoreCompleted', [locals])
}
}
}) })
} catch (e) { } catch (e) {
this.event.trigger('solidityLocalsMessage', [e.message]) this.event.trigger('solidityLocalsMessage', [e.message])
@ -79,6 +86,15 @@ class DebuggerSolidityLocals {
}) })
} }
decodeMore (cursor) {
let decodeTimeout = null
if (!this.storageResolver) return this.event.trigger('solidityLocalsMessage', ['storage not ready'])
if (decodeTimeout) window.clearTimeout(decodeTimeout)
decodeTimeout = setTimeout(() => {
this.decode(this._sourceLocation, cursor)
}, 500)
}
} }
module.exports = DebuggerSolidityLocals module.exports = DebuggerSolidityLocals

@ -369,8 +369,8 @@ function computeOffsets (types, stateDefinitions, contractName, location) {
} }
module.exports = { module.exports = {
parseType: parseType, parseType,
computeOffsets: computeOffsets, computeOffsets,
Uint: uint, Uint: uint,
Address: address, Address: address,
Bool: bool, Bool: bool,

@ -3,9 +3,4 @@ const stateDecoder = require('./stateDecoder')
const localDecoder = require('./localDecoder') const localDecoder = require('./localDecoder')
const InternalCallTree = require('./internalCallTree') const InternalCallTree = require('./internalCallTree')
module.exports = { module.exports = {SolidityProxy, stateDecoder, localDecoder, InternalCallTree}
SolidityProxy: SolidityProxy,
stateDecoder: stateDecoder,
localDecoder: localDecoder,
InternalCallTree: InternalCallTree
}

@ -22,21 +22,24 @@ class InternalCallTree {
* @param {Object} traceManager - trace manager * @param {Object} traceManager - trace manager
* @param {Object} solidityProxy - solidity proxy * @param {Object} solidityProxy - solidity proxy
* @param {Object} codeManager - code manager * @param {Object} codeManager - code manager
* @param {Object} opts - { includeLocalVariables } * @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources }
*/ */
constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) { constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) {
this.includeLocalVariables = opts.includeLocalVariables this.includeLocalVariables = opts.includeLocalVariables
this.debugWithGeneratedSources = opts.debugWithGeneratedSources
this.event = new EventManager() this.event = new EventManager()
this.solidityProxy = solidityProxy this.solidityProxy = solidityProxy
this.traceManager = traceManager this.traceManager = traceManager
this.sourceLocationTracker = new SourceLocationTracker(codeManager) this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources })
debuggerEvent.register('newTraceLoaded', (trace) => { debuggerEvent.register('newTraceLoaded', (trace) => {
this.reset() this.reset()
if (!this.solidityProxy.loaded()) { if (!this.solidityProxy.loaded()) {
this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree']) this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree'])
} else { } else {
// each recursive call to buildTree represent a new context (either call, delegatecall, internal function) // each recursive call to buildTree represent a new context (either call, delegatecall, internal function)
buildTree(this, 0, '', true).then((result) => { const calledAddress = traceManager.getCurrentCalledAddressAt(0)
const isCreation = traceHelper.isContractCreation(calledAddress)
buildTree(this, 0, '', true, isCreation).then((result) => {
if (result.error) { if (result.error) {
this.event.trigger('callTreeBuildFailed', [result.error]) this.event.trigger('callTreeBuildFailed', [result.error])
} else { } else {
@ -134,12 +137,22 @@ class InternalCallTree {
throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error) throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error)
} }
} }
async extractValidSourceLocation (step) {
try {
const address = this.traceManager.getCurrentCalledAddressAt(step)
const location = await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts)
return location
} catch (error) {
throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error)
}
}
} }
async function buildTree (tree, step, scopeId, isExternalCall) { async function buildTree (tree, step, scopeId, isExternalCall, isCreation) {
let subScope = 1 let subScope = 1
tree.scopeStarts[step] = scopeId tree.scopeStarts[step] = scopeId
tree.scopes[scopeId] = { firstStep: step, locals: {} } tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation }
function callDepthChange (step, trace) { function callDepthChange (step, trace) {
if (step + 1 < trace.length) { if (step + 1 < trace.length) {
@ -157,7 +170,7 @@ async function buildTree (tree, step, scopeId, isExternalCall) {
included.file === source.file) included.file === source.file)
} }
let currentSourceLocation = {start: -1, length: -1, file: -1} let currentSourceLocation = { start: -1, length: -1, file: -1 }
let previousSourceLocation = currentSourceLocation let previousSourceLocation = currentSourceLocation
while (step < tree.traceManager.trace.length) { while (step < tree.traceManager.trace.length) {
let sourceLocation let sourceLocation
@ -176,10 +189,11 @@ async function buildTree (tree, step, scopeId, isExternalCall) {
return { outStep: step, error: 'InternalCallTree - No source Location. ' + step } return { outStep: step, error: 'InternalCallTree - No source Location. ' + step }
} }
const isCallInstruction = traceHelper.isCallInstruction(tree.traceManager.trace[step]) const isCallInstruction = traceHelper.isCallInstruction(tree.traceManager.trace[step])
const isCreateInstruction = traceHelper.isCreateInstruction(tree.traceManager.trace[step])
// we are checking if we are jumping in a new CALL or in an internal function // we are checking if we are jumping in a new CALL or in an internal function
if (isCallInstruction || sourceLocation.jump === 'i') { if (isCallInstruction || sourceLocation.jump === 'i') {
try { try {
const externalCallResult = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope, isCallInstruction) const externalCallResult = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope, isCallInstruction, isCreateInstruction)
if (externalCallResult.error) { if (externalCallResult.error) {
return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error }
} else { } else {
@ -211,8 +225,19 @@ function createReducedTrace (tree, index) {
tree.reducedTrace.push(index) tree.reducedTrace.push(index)
} }
function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) { function getGeneratedSources (tree, scopeId, contractObj) {
const variableDeclaration = resolveVariableDeclaration(tree, step, sourceLocation) if (tree.debugWithGeneratedSources && contractObj && tree.scopes[scopeId]) {
return tree.scopes[scopeId].isCreation ? contractObj.contract.evm.bytecode.generatedSources : contractObj.contract.evm.deployedBytecode.generatedSources
}
return null
}
async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) {
const contractObj = await tree.solidityProxy.contractObjectAt(step)
let states = null
const generatedSources = getGeneratedSources(tree, scopeId, contractObj)
const variableDeclaration = resolveVariableDeclaration(tree, sourceLocation, generatedSources)
// using the vm trace step, the current source location and the ast, // using the vm trace step, the current source location and the ast,
// we check if the current vm trace step target a new ast node of type VariableDeclaration // we check if the current vm trace step target a new ast node of type VariableDeclaration
// that way we know that there is a new local variable from here. // that way we know that there is a new local variable from here.
@ -221,37 +246,39 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
const stack = tree.traceManager.getStackAt(step) const stack = tree.traceManager.getStackAt(step)
// the stack length at this point is where the value of the new local variable will be stored. // the stack length at this point is where the value of the new local variable will be stored.
// so, either this is the direct value, or the offset in memory. That depends on the type. // so, either this is the direct value, or the offset in memory. That depends on the type.
tree.solidityProxy.contractNameAt(step).then((contractName) => {
if (variableDeclaration.name !== '') { if (variableDeclaration.name !== '') {
var states = tree.solidityProxy.extractStatesDefinitions() states = tree.solidityProxy.extractStatesDefinitions()
var location = typesUtil.extractLocationFromAstVariable(variableDeclaration) var location = typesUtil.extractLocationFromAstVariable(variableDeclaration)
location = location === 'default' ? 'storage' : location location = location === 'default' ? 'storage' : location
// we push the new local variable in our tree // we push the new local variable in our tree
tree.scopes[scopeId].locals[variableDeclaration.name] = { tree.scopes[scopeId].locals[variableDeclaration.name] = {
name: variableDeclaration.name, name: variableDeclaration.name,
type: decodeInfo.parseType(variableDeclaration.typeDescriptions.typeString, states, contractName, location), type: decodeInfo.parseType(variableDeclaration.typeDescriptions.typeString, states, contractObj.name, location),
stackDepth: stack.length, stackDepth: stack.length,
sourceLocation: sourceLocation sourceLocation: sourceLocation
} }
} }
})
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
} }
// we check here if we are at the beginning inside a new function. // we check here if we are at the beginning inside a new function.
// if that is the case, we have to add to locals tree the inputs and output params // if that is the case, we have to add to locals tree the inputs and output params
const functionDefinition = resolveFunctionDefinition(tree, step, previousSourceLocation) const functionDefinition = resolveFunctionDefinition(tree, previousSourceLocation, generatedSources)
if (functionDefinition && (newLocation && traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 1]) || functionDefinition.kind === 'constructor')) { if (!functionDefinition) return
const previousIsJumpDest2 = traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 2])
const previousIsJumpDest1 = traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 1])
const isConstructor = functionDefinition.kind === 'constructor'
if (newLocation && (previousIsJumpDest1 || previousIsJumpDest2 || isConstructor)) {
tree.functionCallStack.push(step) tree.functionCallStack.push(step)
const functionDefinitionAndInputs = {functionDefinition, inputs: []} const functionDefinitionAndInputs = { functionDefinition, inputs: [] }
// means: the previous location was a function definition && JUMPDEST // means: the previous location was a function definition && JUMPDEST
// => we are at the beginning of the function and input/output are setup // => we are at the beginning of the function and input/output are setup
tree.solidityProxy.contractNameAt(step).then((contractName) => { // cached
try { try {
const stack = tree.traceManager.getStackAt(step) const stack = tree.traceManager.getStackAt(step)
var states = tree.solidityProxy.extractStatesDefinitions() states = tree.solidityProxy.extractStatesDefinitions()
if (functionDefinition.parameters) { if (functionDefinition.parameters) {
let inputs = functionDefinition.parameters let inputs = functionDefinition.parameters
let outputs = functionDefinition.returnParameters let outputs = functionDefinition.returnParameters
@ -266,15 +293,14 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
// } // }
// input params // input params
if (inputs) { if (inputs) {
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, inputs.parameters.length, -1) functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj.name, previousSourceLocation, stack.length, inputs.parameters.length, -1)
} }
// output params // output params
if (outputs) addParams(outputs, tree, scopeId, states, contractName, previousSourceLocation, stack.length, 0, 1) if (outputs) addParams(outputs, tree, scopeId, states, contractObj.name, previousSourceLocation, stack.length, 0, 1)
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
})
tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs
} }
@ -282,13 +308,12 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
// this extract all the variable declaration for a given ast and file // this extract all the variable declaration for a given ast and file
// and keep this in a cache // and keep this in a cache
function resolveVariableDeclaration (tree, step, sourceLocation) { function resolveVariableDeclaration (tree, sourceLocation, generatedSources) {
if (!tree.variableDeclarationByFile[sourceLocation.file]) { if (!tree.variableDeclarationByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation) const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
if (ast) { if (ast) {
tree.variableDeclarationByFile[sourceLocation.file] = extractVariableDeclarations(ast, tree.astWalker) tree.variableDeclarationByFile[sourceLocation.file] = extractVariableDeclarations(ast, tree.astWalker)
} else { } else {
// console.log('Ast not found for step ' + step + '. file ' + sourceLocation.file)
return null return null
} }
} }
@ -297,13 +322,12 @@ function resolveVariableDeclaration (tree, step, sourceLocation) {
// this extract all the function definition for a given ast and file // this extract all the function definition for a given ast and file
// and keep this in a cache // and keep this in a cache
function resolveFunctionDefinition (tree, step, sourceLocation) { function resolveFunctionDefinition (tree, sourceLocation, generatedSources) {
if (!tree.functionDefinitionByFile[sourceLocation.file]) { if (!tree.functionDefinitionByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation) const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
if (ast) { if (ast) {
tree.functionDefinitionByFile[sourceLocation.file] = extractFunctionDefinitions(ast, tree.astWalker) tree.functionDefinitionByFile[sourceLocation.file] = extractFunctionDefinitions(ast, tree.astWalker)
} else { } else {
// console.log('Ast not found for step ' + step + '. file ' + sourceLocation.file)
return null return null
} }
} }
@ -313,7 +337,7 @@ function resolveFunctionDefinition (tree, step, sourceLocation) {
function extractVariableDeclarations (ast, astWalker) { function extractVariableDeclarations (ast, astWalker) {
const ret = {} const ret = {}
astWalker.walkFull(ast, (node) => { astWalker.walkFull(ast, (node) => {
if (node.nodeType === 'VariableDeclaration') { if (node.nodeType === 'VariableDeclaration' || node.nodeType === 'YulVariableDeclaration') {
ret[node.src] = node ret[node.src] = node
} }
}) })
@ -323,7 +347,7 @@ function extractVariableDeclarations (ast, astWalker) {
function extractFunctionDefinitions (ast, astWalker) { function extractFunctionDefinitions (ast, astWalker) {
const ret = {} const ret = {}
astWalker.walkFull(ast, (node) => { astWalker.walkFull(ast, (node) => {
if (node.nodeType === 'FunctionDefinition') { if (node.nodeType === 'FunctionDefinition' || node.nodeType === 'YulFunctionDefinition') {
ret[node.src] = node ret[node.src] = node
} }
}) })

@ -1,6 +1,6 @@
'use strict' 'use strict'
async function solidityLocals (vmtraceIndex, internalTreeCall, stack, memory, storageResolver, currentSourceLocation) { async function solidityLocals (vmtraceIndex, internalTreeCall, stack, memory, storageResolver, currentSourceLocation, cursor) {
const scope = internalTreeCall.findScope(vmtraceIndex) const scope = internalTreeCall.findScope(vmtraceIndex)
if (!scope) { if (!scope) {
const error = { 'message': 'Can\'t display locals. reason: compilation result might not have been provided' } const error = { 'message': 'Can\'t display locals. reason: compilation result might not have been provided' }
@ -18,7 +18,7 @@ async function solidityLocals (vmtraceIndex, internalTreeCall, stack, memory, st
anonymousIncr++ anonymousIncr++
} }
try { try {
locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver) locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver, cursor)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
locals[name] = '<decoding failed - ' + e.message + '>' locals[name] = '<decoding failed - ' + e.message + '>'
@ -35,6 +35,4 @@ function formatMemory (memory) {
return memory return memory
} }
module.exports = { module.exports = {solidityLocals}
solidityLocals: solidityLocals
}

@ -6,11 +6,11 @@ const astHelper = require('./astHelper')
const util = remixLib.util const util = remixLib.util
class SolidityProxy { class SolidityProxy {
constructor (traceManager, codeManager) { constructor ({getCurrentCalledAddressAt, getCode}) {
this.cache = new Cache() this.cache = new Cache()
this.reset({}) this.reset({})
this.traceManager = traceManager this.getCurrentCalledAddressAt = getCurrentCalledAddressAt
this.codeManager = codeManager this.getCode = getCode
} }
/** /**
@ -39,15 +39,15 @@ class SolidityProxy {
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name * @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName) * @param {Function} cb - callback returns (error, contractName)
*/ */
async contractNameAt (vmTraceIndex) { async contractObjectAt (vmTraceIndex) {
const address = this.traceManager.getCurrentCalledAddressAt(vmTraceIndex) const address = this.getCurrentCalledAddressAt(vmTraceIndex)
if (this.cache.contractNameByAddress[address]) { if (this.cache.contractObjectByAddress[address]) {
return this.cache.contractNameByAddress[address] return this.cache.contractObjectByAddress[address]
} }
const code = await this.codeManager.getCode(address) const code = await this.getCode(address)
const contractName = contractNameFromCode(this.contracts, code.bytecode, address) const contract = contractObjectFromCode(this.contracts, code.bytecode, address)
this.cache.contractNameByAddress[address] = contractName this.cache.contractObjectByAddress[address] = contract
return contractName return contract
} }
/** /**
@ -86,8 +86,8 @@ class SolidityProxy {
* @return {Object} - returns state variables of @args vmTraceIndex * @return {Object} - returns state variables of @args vmTraceIndex
*/ */
async extractStateVariablesAt (vmtraceIndex) { async extractStateVariablesAt (vmtraceIndex) {
const contractName = await this.contractNameAt(vmtraceIndex) const contract = await this.contractObjectAt(vmtraceIndex)
return this.extractStateVariables(contractName) return this.extractStateVariables(contract.name)
} }
/** /**
@ -96,9 +96,13 @@ class SolidityProxy {
* @param {Object} sourceLocation - source location containing the 'file' to retrieve the AST from * @param {Object} sourceLocation - source location containing the 'file' to retrieve the AST from
* @return {Object} - AST of the current file * @return {Object} - AST of the current file
*/ */
ast (sourceLocation) { ast (sourceLocation, generatedSources) {
const file = this.fileNameFromIndex(sourceLocation.file) const file = this.fileNameFromIndex(sourceLocation.file)
if (this.sources[file]) { if (!file && generatedSources && generatedSources.length) {
for (const source of generatedSources) {
if (source.id === sourceLocation.file) return source.ast
}
} else if (this.sources[file]) {
return this.sources[file].ast return this.sources[file].ast
} }
return null return null
@ -115,13 +119,13 @@ class SolidityProxy {
} }
} }
function contractNameFromCode (contracts, code, address) { function contractObjectFromCode (contracts, code, address) {
const isCreation = traceHelper.isContractCreation(address) const isCreation = traceHelper.isContractCreation(address)
for (let file in contracts) { for (let file in contracts) {
for (let contract in contracts[file]) { for (let contract in contracts[file]) {
const bytecode = isCreation ? contracts[file][contract].evm.bytecode.object : contracts[file][contract].evm.deployedBytecode.object const bytecode = isCreation ? contracts[file][contract].evm.bytecode.object : contracts[file][contract].evm.deployedBytecode.object
if (util.compareByteCode(code, '0x' + bytecode)) { if (util.compareByteCode(code, '0x' + bytecode)) {
return contract return { name: contract, contract: contracts[file][contract] }
} }
} }
} }
@ -133,7 +137,7 @@ class Cache {
this.reset() this.reset()
} }
reset () { reset () {
this.contractNameByAddress = {} this.contractObjectByAddress = {}
this.stateVariablesByContractName = {} this.stateVariablesByContractName = {}
this.contractDeclarations = null this.contractDeclarations = null
this.statesDefinitions = null this.statesDefinitions = null

@ -1,5 +1,5 @@
const astHelper = require('./astHelper') const astHelper = require('./astHelper')
const decodeInfo = require('./decodeInfo') const {computeOffsets} = require('./decodeInfo')
/** /**
* decode the contract state storage * decode the contract state storage
@ -40,7 +40,7 @@ function extractStateVariables (contractName, sourcesList) {
return [] return []
} }
const types = states[contractName].stateVariables const types = states[contractName].stateVariables
const offsets = decodeInfo.computeOffsets(types, states, contractName, 'storage') const offsets = computeOffsets(types, states, contractName, 'storage')
if (!offsets) { if (!offsets) {
return [] // TODO should maybe return an error return [] // TODO should maybe return an error
} }
@ -64,8 +64,4 @@ async function solidityState (storageResolver, astList, contractName) {
} }
} }
module.exports = { module.exports = {solidityState, extractStateVariables, decodeState}
solidityState: solidityState,
extractStateVariables: extractStateVariables,
decodeState: decodeState
}

@ -10,9 +10,8 @@ class Address extends ValueType {
decodeValue (value) { decodeValue (value) {
if (!value) { if (!value) {
return '0x0000000000000000000000000000000000000000' return '0x0000000000000000000000000000000000000000'
} else {
return '0x' + util.extractHexByteSlice(value, this.storageBytes, 0).toUpperCase()
} }
return '0x' + util.extractHexByteSlice(value, this.storageBytes, 0).toUpperCase()
} }
} }

@ -69,14 +69,10 @@ class ArrayType extends RefType {
currentLocation.offset = 0 currentLocation.offset = 0
} }
} }
return { return {value: ret, length: '0x' + size.toString(16), type: this.typeName}
value: ret,
length: '0x' + size.toString(16),
type: this.typeName
}
} }
decodeFromMemoryInternal (offset, memory) { decodeFromMemoryInternal (offset, memory, skip) {
const ret = [] const ret = []
let length = this.arraySize let length = this.arraySize
if (this.arraySize === 'dynamic') { if (this.arraySize === 'dynamic') {
@ -84,7 +80,17 @@ class ArrayType extends RefType {
length = parseInt(length, 16) length = parseInt(length, 16)
offset = offset + 32 offset = offset + 32
} }
for (var k = 0; k < length; k++) { if (isNaN(length)) {
return {
value: '<decoding failed - length is NaN>',
type: this.typeName
}
}
if (!skip) skip = 0
if (skip) offset = offset + (32 * skip)
let limit = length - skip
if (limit > 100) limit = 100
for (var k = 0; k < limit; k++) {
var contentOffset = offset var contentOffset = offset
ret.push(this.underlyingType.decodeFromMemory(contentOffset, memory)) ret.push(this.underlyingType.decodeFromMemory(contentOffset, memory))
offset += 32 offset += 32
@ -92,7 +98,9 @@ class ArrayType extends RefType {
return { return {
value: ret, value: ret,
length: '0x' + length.toString(16), length: '0x' + length.toString(16),
type: this.typeName type: this.typeName,
cursor: skip + limit,
hasNext: length > (skip + limit)
} }
} }
} }

@ -16,10 +16,7 @@ class DynamicByteArray extends RefType {
value = await util.extractHexValue(location, storageResolver, this.storageBytes) value = await util.extractHexValue(location, storageResolver, this.storageBytes)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return { return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
} }
const bn = new BN(value, 16) const bn = new BN(value, 16)
if (bn.testn(0)) { if (bn.testn(0)) {
@ -31,10 +28,7 @@ class DynamicByteArray extends RefType {
currentSlot = await util.readFromStorage(dataPos, storageResolver) currentSlot = await util.readFromStorage(dataPos, storageResolver)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return { return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
} }
while (length.gt(ret.length) && ret.length < 32000) { while (length.gt(ret.length) && ret.length < 32000) {
currentSlot = currentSlot.replace('0x', '') currentSlot = currentSlot.replace('0x', '')
@ -44,24 +38,13 @@ class DynamicByteArray extends RefType {
currentSlot = await util.readFromStorage(dataPos, storageResolver) currentSlot = await util.readFromStorage(dataPos, storageResolver)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return { return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
} }
} }
return { return {value: '0x' + ret.replace(/(00)+$/, ''), length: '0x' + length.toString(16), type: this.typeName}
value: '0x' + ret.replace(/(00)+$/, ''),
length: '0x' + length.toString(16),
type: this.typeName
}
} else { } else {
var size = parseInt(value.substr(value.length - 2, 2), 16) / 2 var size = parseInt(value.substr(value.length - 2, 2), 16) / 2
return { return {value: '0x' + value.substr(0, size * 2), length: '0x' + size.toString(16), type: this.typeName}
value: '0x' + value.substr(0, size * 2),
length: '0x' + size.toString(16),
type: this.typeName
}
} }
} }
@ -69,11 +52,7 @@ class DynamicByteArray extends RefType {
offset = 2 * offset offset = 2 * offset
let length = memory.substr(offset, 64) let length = memory.substr(offset, 64)
length = 2 * parseInt(length, 16) length = 2 * parseInt(length, 16)
return { return {length: '0x' + length.toString(16), value: '0x' + memory.substr(offset + 64, length), type: this.typeName}
length: '0x' + length.toString(16),
value: '0x' + memory.substr(offset + 64, length),
type: this.typeName
}
} }
} }

@ -28,20 +28,13 @@ class Mapping extends RefType {
const mappingPreimages = await storageResolver.mappingsLocation(corrections) const mappingPreimages = await storageResolver.mappingsLocation(corrections)
let ret = await this.decodeMappingsLocation(mappingPreimages, location, storageResolver) // fetch mapping storage changes let ret = await this.decodeMappingsLocation(mappingPreimages, location, storageResolver) // fetch mapping storage changes
ret = Object.assign({}, this.initialDecodedState, ret) // merge changes ret = Object.assign({}, this.initialDecodedState, ret) // merge changes
return { return {value: ret, type: this.typeName}
value: ret,
type: this.typeName
}
} }
decodeFromMemoryInternal (offset, memory) { decodeFromMemoryInternal (offset, memory) {
// mappings can only exist in storage and not in memory // mappings can only exist in storage and not in memory
// so this should never be called // so this should never be called
return { return {value: '<not implemented>', length: '0x', type: this.typeName}
value: '<not implemented>',
length: '0x',
type: this.typeName
}
} }
async decodeMappingsLocation (preimages, location, storageResolver) { async decodeMappingsLocation (preimages, location, storageResolver) {

@ -19,12 +19,9 @@ class RefType {
* @param {Object} - storageResolver * @param {Object} - storageResolver
* @return {Object} decoded value * @return {Object} decoded value
*/ */
async decodeFromStack (stackDepth, stack, memory, storageResolver) { async decodeFromStack (stackDepth, stack, memory, storageResolver, cursor) {
if (stack.length - 1 < stackDepth) { if (stack.length - 1 < stackDepth) {
return { return {error: '<decoding failed - stack underflow ' + stackDepth + '>', type: this.typeName}
error: '<decoding failed - stack underflow ' + stackDepth + '>',
type: this.typeName
}
} }
let offset = stack[stack.length - 1 - stackDepth] let offset = stack[stack.length - 1 - stackDepth]
if (this.isInStorage()) { if (this.isInStorage()) {
@ -33,19 +30,13 @@ class RefType {
return await this.decodeFromStorage({ offset: 0, slot: offset }, storageResolver) return await this.decodeFromStorage({ offset: 0, slot: offset }, storageResolver)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return { return {error: '<decoding failed - ' + e.message + '>', type: this.typeName}
error: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
} }
} else if (this.isInMemory()) { } else if (this.isInMemory()) {
offset = parseInt(offset, 16) offset = parseInt(offset, 16)
return this.decodeFromMemoryInternal(offset, memory) return this.decodeFromMemoryInternal(offset, memory, cursor)
} else { } else {
return { return {error: '<decoding failed - no decoder for ' + this.location + '>', type: this.typeName}
error: '<decoding failed - no decoder for ' + this.location + '>',
type: this.typeName
}
} }
} }

@ -39,11 +39,7 @@ function format (decoded) {
} }
let value = decoded.value let value = decoded.value
value = value.replace('0x', '').replace(/(..)/g, '%$1') value = value.replace('0x', '').replace(/(..)/g, '%$1')
const ret = { const ret = {length: decoded.length, raw: decoded.value, type: 'string'}
length: decoded.length,
raw: decoded.value,
type: 'string'
}
try { try {
ret.value = decodeURIComponent(value) ret.value = decodeURIComponent(value)
} catch (e) { } catch (e) {

@ -22,10 +22,7 @@ class Struct extends RefType {
ret[item.name] = '<decoding failed - ' + e.message + '>' ret[item.name] = '<decoding failed - ' + e.message + '>'
} }
} }
return { return {value: ret, type: this.typeName}
value: ret,
type: this.typeName
}
} }
decodeFromMemoryInternal (offset, memory) { decodeFromMemoryInternal (offset, memory) {
@ -36,10 +33,7 @@ class Struct extends RefType {
ret[item.name] = member ret[item.name] = member
offset += 32 offset += 32
}) })
return { return {value: ret, type: this.typeName}
value: ret,
type: this.typeName
}
} }
} }

@ -19,16 +19,10 @@ class ValueType {
async decodeFromStorage (location, storageResolver) { async decodeFromStorage (location, storageResolver) {
try { try {
var value = await util.extractHexValue(location, storageResolver, this.storageBytes) var value = await util.extractHexValue(location, storageResolver, this.storageBytes)
return { return {value: this.decodeValue(value), type: this.typeName}
value: this.decodeValue(value),
type: this.typeName
}
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return { return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
} }
} }
@ -47,10 +41,7 @@ class ValueType {
} else { } else {
value = this.decodeValue(stack[stack.length - 1 - stackDepth].replace('0x', '')) value = this.decodeValue(stack[stack.length - 1 - stackDepth].replace('0x', ''))
} }
return { return {value, type: this.typeName}
value: value,
type: this.typeName
}
} }
/** /**
@ -62,10 +53,7 @@ class ValueType {
*/ */
decodeFromMemory (offset, memory) { decodeFromMemory (offset, memory) {
let value = memory.substr(2 * offset, 64) let value = memory.substr(2 * offset, 64)
return { return {value: this.decodeValue(value), type: this.typeName}
value: this.decodeValue(value),
type: this.typeName
}
} }
} }

@ -2,20 +2,6 @@
const ethutil = require('ethereumjs-util') const ethutil = require('ethereumjs-util')
const BN = require('ethereumjs-util').BN const BN = require('ethereumjs-util').BN
module.exports = {
readFromStorage: readFromStorage,
decodeIntFromHex: decodeIntFromHex,
extractHexValue: extractHexValue,
extractHexByteSlice: extractHexByteSlice,
toBN: toBN,
add: add,
sub: sub,
extractLocation: extractLocation,
removeLocation: removeLocation,
normalizeHex: normalizeHex,
extractLocationFromAstVariable: extractLocationFromAstVariable
}
function decodeIntFromHex (value, byteLength, signed) { function decodeIntFromHex (value, byteLength, signed) {
let bigNumber = new BN(value, 16) let bigNumber = new BN(value, 16)
if (signed) { if (signed) {
@ -24,21 +10,17 @@ function decodeIntFromHex (value, byteLength, signed) {
return bigNumber.toString(10) return bigNumber.toString(10)
} }
function readFromStorage (slot, storageResolver) { function readFromStorage(slot, storageResolver) {
const hexSlot = '0x' + normalizeHex(ethutil.bufferToHex(slot)) const hexSlot = '0x' + normalizeHex(ethutil.bufferToHex(slot))
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
storageResolver.storageSlot(hexSlot, (error, slot) => { storageResolver.storageSlot(hexSlot, (error, slot) => {
if (error) { if (error) {
return reject(error) return reject(error)
} else {
if (!slot) {
slot = {
key: slot,
value: ''
} }
if (!slot) {
slot = { key: slot, value: '' }
} }
return resolve(normalizeHex(slot.value)) return resolve(normalizeHex(slot.value))
}
}) })
}) })
} }
@ -120,3 +102,5 @@ function normalizeHex (hex) {
} }
return hex return hex
} }
module.exports = {readFromStorage, decodeIntFromHex, extractHexValue, extractHexByteSlice, toBN, add, sub, extractLocation, removeLocation, normalizeHex, extractLocationFromAstVariable}

@ -3,12 +3,16 @@ const EventManager = require('../eventManager')
const helper = require('../trace/traceHelper') const helper = require('../trace/traceHelper')
const SourceMappingDecoder = require('./sourceMappingDecoder') const SourceMappingDecoder = require('./sourceMappingDecoder')
const remixLib = require('@remix-project/remix-lib') const remixLib = require('@remix-project/remix-lib')
const { map } = require('jquery')
const util = remixLib.util const util = remixLib.util
/** /**
* Process the source code location for the current executing bytecode * Process the source code location for the current executing bytecode
*/ */
function SourceLocationTracker (_codeManager) { function SourceLocationTracker (_codeManager, { debugWithGeneratedSources }) {
this.opts = {
debugWithGeneratedSources: debugWithGeneratedSources || false
}
this.codeManager = _codeManager this.codeManager = _codeManager
this.event = new EventManager() this.event = new EventManager()
this.sourceMappingDecoder = new SourceMappingDecoder() this.sourceMappingDecoder = new SourceMappingDecoder()
@ -16,30 +20,57 @@ function SourceLocationTracker (_codeManager) {
} }
/** /**
* Return the source location associated with the given @arg index * Return the source location associated with the given @arg index (instruction index)
* *
* @param {String} address - contract address from which the source location is retrieved * @param {String} address - contract address from which the source location is retrieved
* @param {Int} index - index in the instruction list from where the source location is retrieved * @param {Int} index - index in the instruction list from where the source location is retrieved
* @param {Object} contractDetails - AST of compiled contracts * @param {Object} contractDetails - AST of compiled contracts
* @param {Function} cb - callback function
*/ */
SourceLocationTracker.prototype.getSourceLocationFromInstructionIndex = async function (address, index, contracts) { SourceLocationTracker.prototype.getSourceLocationFromInstructionIndex = async function (address, index, contracts) {
const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts) const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts)
return this.sourceMappingDecoder.atIndex(index, sourceMap) return this.sourceMappingDecoder.atIndex(index, sourceMap.map)
} }
/** /**
* Return the source location associated with the given @arg pc * Return the source location associated with the given @arg vmTraceIndex
* *
* @param {String} address - contract address from which the source location is retrieved * @param {String} address - contract address from which the source location is retrieved
* @param {Int} vmtraceStepIndex - index of the current code in the vmtrace * @param {Int} vmtraceStepIndex - index of the current code in the vmtrace
* @param {Object} contractDetails - AST of compiled contracts * @param {Object} contractDetails - AST of compiled contracts
* @param {Function} cb - callback function
*/ */
SourceLocationTracker.prototype.getSourceLocationFromVMTraceIndex = async function (address, vmtraceStepIndex, contracts) { SourceLocationTracker.prototype.getSourceLocationFromVMTraceIndex = async function (address, vmtraceStepIndex, contracts) {
const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts) const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts)
const index = this.codeManager.getInstructionIndex(address, vmtraceStepIndex) const index = this.codeManager.getInstructionIndex(address, vmtraceStepIndex)
return this.sourceMappingDecoder.atIndex(index, sourceMap) return this.sourceMappingDecoder.atIndex(index, sourceMap.map)
}
/**
* Returns the generated sources from a specific @arg address
*
* @param {String} address - contract address from which has generated sources
* @param {Object} generatedSources - Object containing the sourceid, ast and the source code.
*/
SourceLocationTracker.prototype.getGeneratedSourcesFromAddress = function (address) {
if (!this.opts.debugWithGeneratedSources) return null
if (this.sourceMapByAddress[address]) return this.sourceMapByAddress[address].generatedSources
return null
}
/**
* Return a valid source location associated with the given @arg vmTraceIndex
*
* @param {String} address - contract address from which the source location is retrieved
* @param {Int} vmtraceStepIndex - index of the current code in the vmtrace
* @param {Object} contractDetails - AST of compiled contracts
*/
SourceLocationTracker.prototype.getValidSourceLocationFromVMTraceIndex = async function (address, vmtraceStepIndex, contracts) {
let map = { file: -1}
while (vmtraceStepIndex >= 0 && map.file === -1) {
map = await this.getSourceLocationFromVMTraceIndex(address, vmtraceStepIndex, contracts)
vmtraceStepIndex = vmtraceStepIndex - 1
}
console.log(map, vmtraceStepIndex)
return map
} }
SourceLocationTracker.prototype.clearCache = function () { SourceLocationTracker.prototype.clearCache = function () {
@ -57,7 +88,9 @@ function getSourceMap (address, code, contracts) {
bytes = isCreation ? bytecode.object : deployedBytecode.object bytes = isCreation ? bytecode.object : deployedBytecode.object
if (util.compareByteCode(code, '0x' + bytes)) { if (util.compareByteCode(code, '0x' + bytes)) {
return isCreation ? bytecode.sourceMap : deployedBytecode.sourceMap const generatedSources = isCreation ? bytecode.generatedSources : deployedBytecode.generatedSources
const map = isCreation ? bytecode.sourceMap : deployedBytecode.sourceMap
return { generatedSources, map }
} }
} }
} }
@ -72,10 +105,9 @@ function extractSourceMap (self, codeManager, address, contracts) {
const sourceMap = getSourceMap(address, result.bytecode, contracts) const sourceMap = getSourceMap(address, result.bytecode, contracts)
if (sourceMap) { if (sourceMap) {
if (!helper.isContractCreation(address)) self.sourceMapByAddress[address] = sourceMap if (!helper.isContractCreation(address)) self.sourceMapByAddress[address] = sourceMap
resolve(sourceMap) return resolve(sourceMap)
} else {
reject('no sourcemap associated with the code ' + address)
} }
reject('no sourcemap associated with the code ' + address)
}).catch(reject) }).catch(reject)
}) })
} }

@ -94,12 +94,8 @@ SourceMappingDecoder.prototype.convertOffsetToLineColumn = function (sourceLocat
start: convertFromCharPosition(sourceLocation.start, lineBreakPositions), start: convertFromCharPosition(sourceLocation.start, lineBreakPositions),
end: convertFromCharPosition(sourceLocation.start + sourceLocation.length, lineBreakPositions) end: convertFromCharPosition(sourceLocation.start + sourceLocation.length, lineBreakPositions)
} }
} else {
return {
start: null,
end: null
}
} }
return {start: null, end: null}
} }
/** /**
@ -119,10 +115,7 @@ function convertFromCharPosition (pos, lineBreakPositions) {
} }
const beginColumn = line === 0 ? 0 : (lineBreakPositions[line - 1] + 1) const beginColumn = line === 0 ? 0 : (lineBreakPositions[line - 1] + 1)
const column = pos - beginColumn const column = pos - beginColumn
return { return {line, column}
line: line,
column: column
}
} }
function sourceLocationFromAstNode (astNode) { function sourceLocationFromAstNode (astNode) {
@ -205,19 +198,13 @@ function atIndex (index, mapping) {
continue continue
} }
current = current.split(':') current = current.split(':')
if (current[2] === '-1') { // if the current step has -1 for the file attribute, we discard it
// case: 'file' is not yet assigned, while processing the srcmap (reverse looping) to find 'start', 'length' (etc..), we tumble on -1 for the file.
// in that case the step has to be discarded
if (ret.file === undefined) ret = {}
continue
}
if (ret.start === undefined && current[0] && current[0] !== '-1' && current[0].length) { if (ret.start === undefined && current[0] && current[0] !== '-1' && current[0].length) {
ret.start = parseInt(current[0]) ret.start = parseInt(current[0])
} }
if (ret.length === undefined && current[1] && current[1] !== '-1' && current[1].length) { if (ret.length === undefined && current[1] && current[1] !== '-1' && current[1].length) {
ret.length = parseInt(current[1]) ret.length = parseInt(current[1])
} }
if (ret.file === undefined && current[2] && current[2] !== '-1' && current[2].length) { if (ret.file === undefined && current[2] && current[2].length) {
ret.file = parseInt(current[2]) ret.file = parseInt(current[2])
} }
if (ret.jump === undefined && current[3] && current[3].length) { if (ret.jump === undefined && current[3] && current[3].length) {

@ -51,10 +51,9 @@ function getPreimage (web3, key) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
web3.debug.preimage(key.indexOf('0x') === 0 ? key : '0x' + key, (error, preimage) => { web3.debug.preimage(key.indexOf('0x') === 0 ? key : '0x' + key, (error, preimage) => {
if (error) { if (error) {
resolve(null) return resolve(null)
} else {
resolve(preimage)
} }
resolve(preimage)
}) })
}) })
} }

@ -1,61 +0,0 @@
'use strict'
const tape = require('tape')
const AstWalker = require('../src/source/astWalker')
const node = require('./resources/ast')
tape('ASTWalker', function (t) {
t.test('ASTWalker.walk', function (st) {
st.plan(24)
const astwalker = new AstWalker()
astwalker.walk(node.ast.ast, function (node) {
if (node.nodeType === 'ContractDefinition') {
checkContract(st, node)
}
if (node.nodeType === 'FunctionDefinition') {
checkSetFunction(st, node)
}
return true
})
const callback = {}
callback.FunctionDefinition = function (node) {
st.equal(node.nodeType, 'FunctionDefinition')
st.equal(node.name === 'set' || node.name === 'get', true)
return true
}
astwalker.walk(node.ast.ast, callback)
})
})
function checkContract (st, node) {
st.equal(node.name, 'test')
st.equal(node.nodes[0].name, 'x')
st.equal(node.nodes[0].typeDescriptions.typeString, 'int256')
st.equal(node.nodes[1].name, 'y')
st.equal(node.nodes[1].typeDescriptions.typeString, 'int256')
st.equal(node.nodes[2].nodeType, 'FunctionDefinition')
st.equal(node.nodes[2].stateMutability, 'nonpayable')
st.equal(node.nodes[2].name, 'set')
st.equal(node.nodes[2].visibility, 'public')
}
function checkSetFunction (st, node) {
if (node.name === 'set') {
st.equal(node.parameters.nodeType, 'ParameterList')
st.equal(node.returnParameters.nodeType, 'ParameterList')
st.equal(node.body.nodeType, 'Block')
st.equal(node.body.statements[0].nodeType, 'ExpressionStatement')
checkExpressionStatement(st, node.body.statements[0])
}
}
function checkExpressionStatement (st, node) {
st.equal(node.expression.nodeType, 'Assignment')
st.equal(node.expression.operator, '=')
st.equal(node.expression.typeDescriptions.typeString, 'int256')
st.equal(node.expression.leftHandSide.nodeType, 'Identifier')
st.equal(node.expression.leftHandSide.name, 'x')
st.equal(node.expression.rightHandSide.nodeType, 'Identifier')
st.equal(node.expression.rightHandSide.name, '_x')
}

@ -1,4 +1,5 @@
var tape = require('tape') var tape = require('tape')
var deepequal = require('deep-equal')
var remixLib = require('@remix-project/remix-lib') var remixLib = require('@remix-project/remix-lib')
var compilerInput = require('./helpers/compilerHelper').compilerInput var compilerInput = require('./helpers/compilerHelper').compilerInput
var SourceMappingDecoder = require('../src/source/sourceMappingDecoder') var SourceMappingDecoder = require('../src/source/sourceMappingDecoder')
@ -257,13 +258,14 @@ function testDebugging (debugManager) {
tape('traceManager.decodeLocalsAt', async (t) => { tape('traceManager.decodeLocalsAt', async (t) => {
t.plan(1) t.plan(1)
const tested = JSON.parse('{"proposalNames":{"value":[{"value":"0x48656C6C6F20576F726C64210000000000000000000000000000000000000000","type":"bytes32"}],"length":"0x1","type":"bytes32[]"},"p":{"value":"45","type":"uint256"},"addressLocal":{"value":"0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB","type":"address"},"i":{"value":"2","type":"uint256"},"proposalsLocals":{"value":[{"value":{"name":{"value":"0x48656C6C6F20576F726C64210000000000000000000000000000000000000000","type":"bytes32"},"voteCount":{"value":"0","type":"uint256"}},"type":"struct Ballot.Proposal"}],"length":"0x1","type":"struct Ballot.Proposal[]"}}') const tested = JSON.parse('{"proposalNames":{"value":[{"value":"0x48656C6C6F20576F726C64210000000000000000000000000000000000000000","type":"bytes32"}],"length":"0x1","type":"bytes32[]","cursor":1,"hasNext":false},"p":{"value":"45","type":"uint256"},"addressLocal":{"value":"0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB","type":"address"},"i":{"value":"2","type":"uint256"},"proposalsLocals":{"value":[{"value":{"name":{"value":"0x48656C6C6F20576F726C64210000000000000000000000000000000000000000","type":"bytes32"},"voteCount":{"value":"0","type":"uint256"}},"type":"struct Ballot.Proposal"}],"length":"0x1","type":"struct Ballot.Proposal[]"}}')
try { try {
const address = debugManager.traceManager.getCurrentCalledAddressAt(330) const address = debugManager.traceManager.getCurrentCalledAddressAt(330)
const location = await debugManager.sourceLocationFromVMTraceIndex(address, 330) const location = await debugManager.sourceLocationFromVMTraceIndex(address, 330)
debugManager.decodeLocalsAt(330, location, (error, decodedlocals) => { debugManager.decodeLocalsAt(330, location, (error, decodedlocals) => {
if (error) return t.end(error) if (error) return t.end(error)
t.equal(JSON.stringify(decodedlocals), JSON.stringify(tested))
t.ok(deepequal(decodedlocals, tested), `locals does not match. expected: ${JSON.stringify(tested)} - current: ${decodedlocals}`)
}) })
} catch (error) { } catch (error) {
return t.end(error) return t.end(error)
@ -273,8 +275,14 @@ function testDebugging (debugManager) {
tape('breakPointManager', (t) => { tape('breakPointManager', (t) => {
t.plan(2) t.plan(2)
var sourceMappingDecoder = new SourceMappingDecoder() var sourceMappingDecoder = new SourceMappingDecoder()
var breakPointManager = new BreakpointManager(debugManager, (rawLocation) => { const {traceManager, callTree, solidityProxy} = debugManager
var breakPointManager = new BreakpointManager({traceManager, callTree, solidityProxy, locationToRowConverter: async (rawLocation) => {
return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, sourceMappingDecoder.getLinebreakPositions(ballot)) return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, sourceMappingDecoder.getLinebreakPositions(ballot))
}})
breakPointManager.event.register('managersChanged', () => {
const {traceManager, callTree, solidityProxy} = debugManager
breakPointManager.setManagers({traceManager, callTree, solidityProxy})
}) })
breakPointManager.add({fileName: 'test.sol', row: 38}) breakPointManager.add({fileName: 'test.sol', row: 38})

@ -25,13 +25,12 @@ function decodeLocal (st, index, traceManager, callTree, verifier) {
}], }],
index, index,
function (error, result) { function (error, result) {
if (!error) { if (error) {
localDecoder.solidityLocals(index, callTree, result[0].value, result[1].value, {}, {start: 5000}).then((locals) => { return st.fail(error)
}
localDecoder.solidityLocals(index, callTree, result[0].value, result[1].value, {}, { start: 5000 }).then((locals) => {
verifier(locals) verifier(locals)
}) })
} else {
st.fail(error)
}
}) })
} catch (e) { } catch (e) {
st.fail(e.message) st.fail(e.message)

@ -13,19 +13,19 @@ var EventManager = require('../../../src/eventManager')
var helper = require('./helper') var helper = require('./helper')
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) { module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) {
vmCall.sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, contractBytecode, function (error, txHash) { vmCall.sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, txHash) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
vm.web3.eth.getTransaction(txHash, function (error, tx) { vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
tx.to = traceHelper.contractCreationToken('0') tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3}) var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager) var codeManager = new CodeManager(traceManager)
codeManager.clear() codeManager.clear()
var solidityProxy = new SolidityProxy(traceManager, codeManager) var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult) solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager() var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }) var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -44,6 +44,7 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
st.equals(functions1.length, 1) st.equals(functions1.length, 1)
st.equals(functions2.length, 2) st.equals(functions2.length, 2)
st.equals(functions3.length, 0) st.equals(functions3.length, 0)
st.equals(Object.keys(functions1[0])[0], 'functionDefinition') st.equals(Object.keys(functions1[0])[0], 'functionDefinition')
st.equals(Object.keys(functions1[0])[1], 'inputs') st.equals(Object.keys(functions1[0])[1], 'inputs')
st.equals(functions1[0].inputs[0], 'foo') st.equals(functions1[0].inputs[0], 'foo')
@ -123,9 +124,7 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => { }).catch((error) => {
st.fail(error) st.fail(error)
}) })
}
}) })
}
}) })
} }

@ -10,19 +10,19 @@ var TraceManager = require('../../../src/trace/traceManager')
var CodeManager = require('../../../src/code/codeManager') var CodeManager = require('../../../src/code/codeManager')
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) { module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) {
vmCall.sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, contractBytecode, function (error, txHash) { vmCall.sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, txHash) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
vm.web3.eth.getTransaction(txHash, function (error, tx) { vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
tx.to = traceHelper.contractCreationToken('0') tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3}) var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager) var codeManager = new CodeManager(traceManager)
codeManager.clear() codeManager.clear()
var solidityProxy = new SolidityProxy(traceManager, codeManager) var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult) solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager() var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }) var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -69,8 +69,6 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => { }).catch((error) => {
st.fail(error) st.fail(error)
}) })
}
}) })
}
}) })
} }

@ -11,19 +11,19 @@ var TraceManager = require('../../../src/trace/traceManager')
var CodeManager = require('../../../src/code/codeManager') var CodeManager = require('../../../src/code/codeManager')
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) { module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) {
vmCall.sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, contractBytecode, function (error, txHash) { vmCall.sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, txHash) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
vm.web3.eth.getTransaction(txHash, function (error, tx) { vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
tx.to = traceHelper.contractCreationToken('0') tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3}) var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager) var codeManager = new CodeManager(traceManager)
codeManager.clear() codeManager.clear()
var solidityProxy = new SolidityProxy(traceManager, codeManager) var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult) solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager() var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }) var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -56,8 +56,6 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => { }).catch((error) => {
st.fail(error) st.fail(error)
}) })
}
}) })
}
}) })
} }

@ -10,19 +10,19 @@ var TraceManager = require('../../../src/trace/traceManager')
var CodeManager = require('../../../src/code/codeManager') var CodeManager = require('../../../src/code/codeManager')
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) { module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) {
vmCall.sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, contractBytecode, function (error, txHash) { vmCall.sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, txHash) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
vm.web3.eth.getTransaction(txHash, function (error, tx) { vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) { if (error) {
st.fail(error) return st.fail(error)
} else { }
tx.to = traceHelper.contractCreationToken('0') tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3}) var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager) var codeManager = new CodeManager(traceManager)
codeManager.clear() codeManager.clear()
var solidityProxy = new SolidityProxy(traceManager, codeManager) var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult) solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager() var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }) var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -113,8 +113,6 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => { }).catch((error) => {
st.fail(error) st.fail(error)
}) })
}
}) })
}
}) })
} }

@ -6,6 +6,10 @@ web3Override.debug = {}
var data = init.readFile(require('path').resolve(__dirname, 'testWeb3.json')) var data = init.readFile(require('path').resolve(__dirname, 'testWeb3.json'))
data = JSON.parse(data) data = JSON.parse(data)
var traceWithABIEncoder = init.readFile(require('path').resolve(__dirname, 'traceWithABIEncoder.json'))
traceWithABIEncoder =
data.testTraces['0x20ef65b8b186ca942fcccd634f37074dde49b541c27994fc7596740ef44cfd53'] = JSON.parse(traceWithABIEncoder)
web3Override.eth.getCode = function (address, callback) { web3Override.eth.getCode = function (address, callback) {
if (callback) { if (callback) {
callback(null, data.testCodes[address]) callback(null, data.testCodes[address])

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,113 @@
'use strict'
const tape = require('tape')
const TraceManager = require('../src/trace/traceManager')
const CodeManager = require('../src/code/codeManager')
const web3Test = require('./resources/testWeb3')
const sourceMapping = require('./resources/sourceMapping')
const SourceLocationTracker = require('../src/source/sourceLocationTracker')
const compiler = require('solc')
const compilerInput = require('./helpers/compilerHelper').compilerInput
tape('SourceLocationTracker', function (t) {
t.test('SourceLocationTracker.getSourceLocationFromVMTraceIndex - simple contract', async function (st) {
const traceManager = new TraceManager({web3: web3Test})
let codeManager = new CodeManager(traceManager)
let output = compiler.compile(compilerInput(contracts))
output = JSON.parse(output)
codeManager.codeResolver.cacheExecutingCode('0x0d3a18d64dfe4f927832ab58d6451cecc4e517c5', '0x' + output.contracts['test.sol']['test'].evm.deployedBytecode.object)
const tx = web3Test.eth.getTransaction('0x20ef65b8b186ca942fcccd634f37074dde49b541c27994fc7596740ef44cfd52')
traceManager.resolveTrace(tx).then(async () => {
const sourceLocationTracker = new SourceLocationTracker(codeManager, {debugWithGeneratedSources: false})
try {
const map = await sourceLocationTracker.getSourceLocationFromVMTraceIndex('0x0d3a18d64dfe4f927832ab58d6451cecc4e517c5', 0, output.contracts)
st.equal(map.file, 0)
st.equal(map.start, 0)
} catch (e) {
console.log(e)
}
st.end()
}).catch((e) => {
t.fail(' - traceManager.resolveTrace - failed ')
console.error(e)
})
})
t.test('SourceLocationTracker.getSourceLocationFromVMTraceIndex - ABIEncoder V2 contract', async function (st) {
const traceManager = new TraceManager({web3: web3Test})
let codeManager = new CodeManager(traceManager)
let output = compiler.compile(compilerInput(ABIEncoderV2))
output = JSON.parse(output)
codeManager.codeResolver.cacheExecutingCode('0x0d3a18d64dfe4f927832ab58d6451cecc4e517c5', '0x' + output.contracts['test.sol']['test'].evm.deployedBytecode.object)
const tx = web3Test.eth.getTransaction('0x20ef65b8b186ca942fcccd634f37074dde49b541c27994fc7596740ef44cfd53')
traceManager.resolveTrace(tx).then(async () => {
const sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: false })
try {
let map = await sourceLocationTracker.getSourceLocationFromVMTraceIndex('0x0d3a18d64dfe4f927832ab58d6451cecc4e517c5', 0, output.contracts)
console.log(map)
st.equal(map.file, 0)
st.equal(map.start, 35)
map = await sourceLocationTracker.getSourceLocationFromVMTraceIndex('0x0d3a18d64dfe4f927832ab58d6451cecc4e517c5', 45, output.contracts)
st.equal(map.file, -1)
map = await sourceLocationTracker.getValidSourceLocationFromVMTraceIndex('0x0d3a18d64dfe4f927832ab58d6451cecc4e517c5', 45, output.contracts)
st.equal(map.file, 0)
st.equal(map.start, 303)
st.equal(map.length, 448)
} catch (e) {
console.log(e)
}
st.end()
}).catch(() => {
t.fail(' - traceManager.resolveTrace - failed ')
})
})
})
const contracts = `contract test {
function f1() public returns (uint) {
uint t = 4;
return t;
}
function f2() public {
}
}
`
const ABIEncoderV2 = `pragma experimental ABIEncoderV2;
contract test {
// 000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000015b38da6a701c568545dcfcb03fcb875f56beddc4
// 0000000000000000000000000000000000000000000000000000000000000002
function testg (bytes calldata userData) external returns (bytes memory, bytes32, bytes32, uint) {
bytes32 idAsk = abi.decode(userData[:33], (bytes32));
bytes32 idOffer = abi.decode(userData[32:64], (bytes32));
// bytes4 sellerAddress = abi.decode(userData[:4], (bytes4));
bytes memory ro = abi.encodePacked(msg.sender, msg.sender, idAsk, idOffer);
return (ro, idAsk, idOffer, userData.length);
}
function testgp (bytes calldata userData) external returns (bytes4) {
return abi.decode(userData[:4], (bytes4));
}
}
`

@ -85,9 +85,9 @@ tape('SourceMappingDecoder', function (t) {
// TokenSaleChallenge - function test(uint256) // TokenSaleChallenge - function test(uint256)
const tokenSaleChallengeMap = sourceMappingDecoder.atIndex(170, sourceMapping.tokenSaleChallengeSourceMap) const tokenSaleChallengeMap = sourceMappingDecoder.atIndex(170, sourceMapping.tokenSaleChallengeSourceMap)
console.log(tokenSaleChallengeMap) console.log(tokenSaleChallengeMap)
st.equal(tokenSaleChallengeMap.start, 211) st.equal(tokenSaleChallengeMap.start, 45)
st.equal(tokenSaleChallengeMap.length, 48) st.equal(tokenSaleChallengeMap.length, 16)
st.equal(tokenSaleChallengeMap.file, 0) st.equal(tokenSaleChallengeMap.file, -1)
st.equal(tokenSaleChallengeMap.jump, '-') st.equal(tokenSaleChallengeMap.jump, '-')
}) })

@ -4,6 +4,7 @@ require('./traceManager.js')
require('./codeManager.js') require('./codeManager.js')
require('./disassembler.js') require('./disassembler.js')
require('./sourceMappingDecoder.js') require('./sourceMappingDecoder.js')
require('./sourceLocationTracker.js')
require('./decoder/decodeInfo.js') require('./decoder/decodeInfo.js')
require('./decoder/storageLocation.js') require('./decoder/storageLocation.js')
require('./decoder/storageDecoder.js') require('./decoder/storageDecoder.js')

@ -44,10 +44,7 @@ eventManager.prototype.register = function (eventName, obj, func) {
func = obj func = obj
obj = this.anonymous obj = this.anonymous
} }
this.registered[eventName].push({ this.registered[eventName].push({obj, func})
obj: obj,
func: func
})
} }
/* /*

@ -7,8 +7,8 @@ const txHelper = require('./txHelper')
* *
*/ */
class EventsDecoder { class EventsDecoder {
constructor (opt = {}) { constructor ({resolveReceipt}) {
this._api = opt.api this.resolveReceipt = resolveReceipt
} }
/** /**
@ -20,7 +20,7 @@ class EventsDecoder {
*/ */
parseLogs (tx, contractName, compiledContracts, cb) { parseLogs (tx, contractName, compiledContracts, cb) {
if (tx.isCall) return cb(null, { decoded: [], raw: [] }) if (tx.isCall) return cb(null, { decoded: [], raw: [] })
this._api.resolveReceipt(tx, (error, receipt) => { this.resolveReceipt(tx, (error, receipt) => {
if (error) return cb(error) if (error) return cb(error)
this._decodeLogs(tx, receipt, contractName, compiledContracts, cb) this._decodeLogs(tx, receipt, contractName, compiledContracts, cb)
}) })

@ -9,6 +9,16 @@ const defaultExecutionContext = require('./execution-context')
const txFormat = require('./txFormat') const txFormat = require('./txFormat')
const txHelper = require('./txHelper') const txHelper = require('./txHelper')
function addExecutionCosts(txResult, tx) {
if (txResult && txResult.result) {
if (txResult.result.execResult) {
tx.returnValue = txResult.result.execResult.returnValue
if (txResult.result.execResult.gasUsed) tx.executionCost = txResult.result.execResult.gasUsed.toString(10)
}
if (txResult.result.gasUsed) tx.transactionCost = txResult.result.gasUsed.toString(10)
}
}
/** /**
* poll web3 each 2s if web3 * poll web3 each 2s if web3
* listen on transaction executed event if VM * listen on transaction executed event if VM
@ -80,16 +90,6 @@ class TxListener {
}) })
}) })
}) })
function addExecutionCosts (txResult, tx) {
if (txResult && txResult.result) {
if (txResult.result.execResult) {
tx.returnValue = txResult.result.execResult.returnValue
if (txResult.result.execResult.gasUsed) tx.executionCost = txResult.result.execResult.gasUsed.toString(10)
}
if (txResult.result.gasUsed) tx.transactionCost = txResult.result.gasUsed.toString(10)
}
}
} }
/** /**

@ -77,22 +77,21 @@ class TxRunner {
} }
} }
execute (args, confirmationCb, gasEstimationForceSend, promptCb, callback) { execute(args, confirmationCb, gasEstimationForceSend, promptCb, callback) {
let data = args.data let data = args.data
if (data.slice(0, 2) !== '0x') { if (data.slice(0, 2) !== '0x') {
data = '0x' + data data = '0x' + data
} }
if (!this.executionContext.isVM()) { if (!this.executionContext.isVM()) {
this.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback) return this.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback)
} else { }
try { try {
this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, callback) this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, callback)
} catch (e) { } catch (e) {
callback(e, null) callback(e, null)
} }
} }
}
runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) { runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) {
const self = this const self = this
@ -228,9 +227,8 @@ async function tryTillReceiptAvailable (txhash, executionContext) {
// Try again with a bit of delay if error or if result still null // Try again with a bit of delay if error or if result still null
await pause() await pause()
return resolve(await tryTillReceiptAvailable(txhash, executionContext)) return resolve(await tryTillReceiptAvailable(txhash, executionContext))
} else {
return resolve(receipt)
} }
return resolve(receipt)
}) })
}) })
} }
@ -242,21 +240,20 @@ async function tryTillTxAvailable (txhash, executionContext) {
// Try again with a bit of delay if error or if result still null // Try again with a bit of delay if error or if result still null
await pause() await pause()
return resolve(await tryTillTxAvailable(txhash, executionContext)) return resolve(await tryTillTxAvailable(txhash, executionContext))
} else {
return resolve(tx)
} }
return resolve(tx)
}) })
}) })
} }
async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) } async function pause () { return new Promise((resolve, reject) => { setTimeout(resolve, 500) }) }
function run (self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb, callback) { function run(self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb, callback) {
if (!self.runAsync && Object.keys(self.pendingTxs).length) { if (!self.runAsync && Object.keys(self.pendingTxs).length) {
self.queusTxs.push({ tx, stamp, callback }) return self.queusTxs.push({ tx, stamp, callback })
} else { }
self.pendingTxs[stamp] = tx self.pendingTxs[stamp] = tx
self.execute(tx, confirmationCb, gasEstimationForceSend, promptCb, function(error, result) { self.execute(tx, confirmationCb, gasEstimationForceSend, promptCb, function (error, result) {
delete self.pendingTxs[stamp] delete self.pendingTxs[stamp]
if (callback && typeof callback === 'function') callback(error, result) if (callback && typeof callback === 'function') callback(error, result)
if (self.queusTxs.length) { if (self.queusTxs.length) {
@ -264,7 +261,6 @@ function run (self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb,
run(self, next.tx, next.stamp, next.callback) run(self, next.tx, next.stamp, next.callback)
} }
}) })
}
} }
module.exports = TxRunner module.exports = TxRunner

@ -27,10 +27,8 @@ function Storage (prefix) {
this.remove = function (name) { this.remove = function (name) {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.localStorage.removeItem(prefix + name) window.localStorage.removeItem(prefix + name)
return true
} else {
return true
} }
return true
} }
this.rename = function (originalName, newName) { this.rename = function (originalName, newName) {
@ -46,9 +44,8 @@ function Storage (prefix) {
// NOTE: this is a workaround for some browsers // NOTE: this is a workaround for some browsers
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
return Object.keys(window.localStorage).filter(function (item) { return item !== null && item !== undefined }) return Object.keys(window.localStorage).filter(function (item) { return item !== null && item !== undefined })
} else {
return []
} }
return []
} }
this.keys = function () { this.keys = function () {

@ -91,10 +91,10 @@ module.exports = class UniversalDApp {
if (!this.config.get('settings/personal-mode')) { if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode') return cb('Not running in personal mode')
} }
passwordPromptCb((passphrase) => { return passwordPromptCb((passphrase) => {
this.executionContext.web3().personal.newAccount(passphrase, cb) this.executionContext.web3().personal.newAccount(passphrase, cb)
}) })
} else { }
let privateKey let privateKey
do { do {
privateKey = crypto.randomBytes(32) privateKey = crypto.randomBytes(32)
@ -102,7 +102,6 @@ module.exports = class UniversalDApp {
this._addAccount(privateKey, '0x56BC75E2D63100000') this._addAccount(privateKey, '0x56BC75E2D63100000')
cb(null, '0x' + privateToAddress(privateKey).toString('hex')) cb(null, '0x' + privateToAddress(privateKey).toString('hex'))
} }
}
/** Add an account to the list of account (only for Javascript VM) */ /** Add an account to the list of account (only for Javascript VM) */
_addAccount (privateKey, balance) { _addAccount (privateKey, balance) {
@ -110,7 +109,9 @@ module.exports = class UniversalDApp {
throw new Error('_addAccount() cannot be called in non-VM mode') throw new Error('_addAccount() cannot be called in non-VM mode')
} }
if (this.accounts) { if (!this.accounts) {
return
}
privateKey = Buffer.from(privateKey, 'hex') privateKey = Buffer.from(privateKey, 'hex')
const address = privateToAddress(privateKey) const address = privateToAddress(privateKey)
@ -119,14 +120,13 @@ module.exports = class UniversalDApp {
stateManager.getAccount(address, (error, account) => { stateManager.getAccount(address, (error, account) => {
if (error) return console.log(error) if (error) return console.log(error)
account.balance = balance || '0xf00000000000000001' account.balance = balance || '0xf00000000000000001'
stateManager.putAccount(address, account, function cb (error) { stateManager.putAccount(address, account, function cb(error) {
if (error) console.log(error) if (error) console.log(error)
}) })
}) })
this.accounts[toChecksumAddress('0x' + address.toString('hex'))] = { privateKey, nonce: 0 } this.accounts[toChecksumAddress('0x' + address.toString('hex'))] = { privateKey, nonce: 0 }
} }
}
/** Return the list of accounts */ /** Return the list of accounts */
getAccounts (cb) { getAccounts (cb) {
@ -171,40 +171,36 @@ module.exports = class UniversalDApp {
} }
/** Get the balance of an address */ /** Get the balance of an address */
getBalance (address, cb) { getBalance(address, cb) {
address = stripHexPrefix(address) address = stripHexPrefix(address)
if (!this.executionContext.isVM()) { if (!this.executionContext.isVM()) {
this.executionContext.web3().eth.getBalance(address, (err, res) => { return this.executionContext.web3().eth.getBalance(address, (err, res) => {
if (err) { if (err) {
cb(err) return cb(err)
} else {
cb(null, res.toString(10))
} }
cb(null, res.toString(10))
}) })
} else { }
if (!this.accounts) { if (!this.accounts) {
return cb('No accounts?') return cb('No accounts?')
} }
this.executionContext.vm().stateManager.getAccount(Buffer.from(address, 'hex'), (err, res) => { this.executionContext.vm().stateManager.getAccount(Buffer.from(address, 'hex'), (err, res) => {
if (err) { if (err) {
cb('Account not found') return cb('Account not found')
} else {
cb(null, new BN(res.balance).toString(10))
} }
cb(null, new BN(res.balance).toString(10))
}) })
} }
}
/** Get the balance of an address, and convert wei to ether */ /** Get the balance of an address, and convert wei to ether */
getBalanceInEther (address, callback) { getBalanceInEther (address, callback) {
this.getBalance(address, (error, balance) => { this.getBalance(address, (error, balance) => {
if (error) { if (error) {
callback(error) return callback(error)
} else {
callback(null, this.executionContext.web3().utils.fromWei(balance, 'ether'))
} }
callback(null, this.executionContext.web3().utils.fromWei(balance, 'ether'))
}) })
} }
@ -219,10 +215,7 @@ module.exports = class UniversalDApp {
* @param {Function} callback - callback. * @param {Function} callback - callback.
*/ */
createContract (data, confirmationCb, continueCb, promptCb, callback) { createContract (data, confirmationCb, continueCb, promptCb, callback) {
this.runTx({data: data, useCall: false}, confirmationCb, continueCb, promptCb, (error, txResult) => { this.runTx({data: data, useCall: false}, confirmationCb, continueCb, promptCb, callback)
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
} }
/** /**
@ -235,10 +228,7 @@ module.exports = class UniversalDApp {
*/ */
callFunction (to, data, funAbi, confirmationCb, continueCb, promptCb, callback) { callFunction (to, data, funAbi, confirmationCb, continueCb, promptCb, callback) {
const useCall = funAbi.stateMutability === 'view' || funAbi.stateMutability === 'pure' const useCall = funAbi.stateMutability === 'view' || funAbi.stateMutability === 'pure'
this.runTx({to, data, useCall}, confirmationCb, continueCb, promptCb, (error, txResult) => { this.runTx({to, data, useCall}, confirmationCb, continueCb, promptCb, callback)
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
} }
/** /**
@ -249,10 +239,7 @@ module.exports = class UniversalDApp {
* @param {Function} callback - callback. * @param {Function} callback - callback.
*/ */
sendRawTransaction (to, data, confirmationCb, continueCb, promptCb, callback) { sendRawTransaction (to, data, confirmationCb, continueCb, promptCb, callback) {
this.runTx({to, data, useCall: false}, confirmationCb, continueCb, promptCb, (error, txResult) => { this.runTx({to, data, useCall: false}, confirmationCb, continueCb, promptCb, callback)
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult)
})
} }
context () { context () {

@ -19,10 +19,9 @@ Web3Providers.prototype.addProvider = function (type, obj) {
Web3Providers.prototype.get = function (type, cb) { Web3Providers.prototype.get = function (type, cb) {
if (this.modes[type]) { if (this.modes[type]) {
cb(null, this.modes[type]) return cb(null, this.modes[type])
} else {
cb('error: this provider has not been setup (' + type + ')', null)
} }
cb('error: this provider has not been setup (' + type + ')', null)
} }
Web3Providers.prototype.addWeb3 = function (type, web3) { Web3Providers.prototype.addWeb3 = function (type, web3) {

@ -211,11 +211,10 @@ web3VmProvider.prototype.traceTransaction = function (txHash, options, cb) {
cb(null, this.vmTraces[txHash]) cb(null, this.vmTraces[txHash])
} }
return this.vmTraces[txHash] return this.vmTraces[txHash]
} else { }
if (cb) { if (cb) {
cb('unable to retrieve traces ' + txHash, null) cb('unable to retrieve traces ' + txHash, null)
} }
}
} }
web3VmProvider.prototype.storageRangeAt = function (blockNumber, txIndex, address, start, maxLength, cb) { // txIndex is the hash in the case of the VM web3VmProvider.prototype.storageRangeAt = function (blockNumber, txIndex, address, start, maxLength, cb) { // txIndex is the hash in the case of the VM
@ -232,9 +231,8 @@ web3VmProvider.prototype.storageRangeAt = function (blockNumber, txIndex, addres
storage: JSON.parse(JSON.stringify(storage)), storage: JSON.parse(JSON.stringify(storage)),
nextKey: null nextKey: null
}) })
} else {
cb('unable to retrieve storage ' + txIndex + ' ' + address)
} }
cb('unable to retrieve storage ' + txIndex + ' ' + address)
} }
web3VmProvider.prototype.getBlockNumber = function (cb) { cb(null, 'vm provider') } web3VmProvider.prototype.getBlockNumber = function (cb) { cb(null, 'vm provider') }
@ -245,11 +243,10 @@ web3VmProvider.prototype.getTransaction = function (txHash, cb) {
cb(null, this.txs[txHash]) cb(null, this.txs[txHash])
} }
return this.txs[txHash] return this.txs[txHash]
} else { }
if (cb) { if (cb) {
cb('unable to retrieve tx ' + txHash, null) cb('unable to retrieve tx ' + txHash, null)
} }
}
} }
web3VmProvider.prototype.getTransactionReceipt = function (txHash, cb) { web3VmProvider.prototype.getTransactionReceipt = function (txHash, cb) {
@ -259,11 +256,10 @@ web3VmProvider.prototype.getTransactionReceipt = function (txHash, cb) {
cb(null, this.txsReceipt[txHash]) cb(null, this.txsReceipt[txHash])
} }
return this.txsReceipt[txHash] return this.txsReceipt[txHash]
} else { }
if (cb) { if (cb) {
cb('unable to retrieve txReceipt ' + txHash, null) cb('unable to retrieve txReceipt ' + txHash, null)
} }
}
} }
web3VmProvider.prototype.getTransactionFromBlock = function (blockNumber, txIndex, cb) { web3VmProvider.prototype.getTransactionFromBlock = function (blockNumber, txIndex, cb) {

@ -41,7 +41,7 @@ class Provider {
} }
sendAsync (payload, callback) { sendAsync (payload, callback) {
//log.info('payload method is ', payload.method) // log.info('payload method is ', payload.method) // commented because, this floods the IDE console
const method = this.methods[payload.method] const method = this.methods[payload.method]
if (this.options.logDetails) { if (this.options.logDetails) {

@ -1,5 +1,5 @@
module.exports = ` module.exports = `
pragma solidity >=0.4.22 <0.7.0; pragma solidity >=0.4.22 <0.8.0;
library Assert { library Assert {

@ -1 +0,0 @@
Subproject commit 1a9ec3230e7a3c278ddc6344e5c89d488a316910

@ -0,0 +1 @@
{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] }

@ -0,0 +1,49 @@
# Remixd
`remixd` is a tool that intend to be used with [Remix IDE](https://github.com/ethereum/remix-project) (aka. Browser-Solidity). It allows a websocket connection between
`Remix IDE` (web application) and the local computer.
Practically Remix IDE makes available a folder shared by `remixd`.
More details are explained in this [tutorial](https://remix-ide.readthedocs.io/en/latest/remixd.html).
Alternatively `remixd` can be used to setup a development environment that can be used with other popular frameworks like Embark, Truffle, Ganache, etc..
`remixd` needs `npm` and `node`
## INSTALLATION
`npm install -g @remix-project/remixd`
## HELP SECTION
```
Usage: remixd -s <shared folder> --remix-ide https://remix.ethereum.org
Provide a two-way connection between the local computer and Remix IDE.
Options:
--remix-ide <url> URL of remix instance allowed to connect to this
web sockect connection
-s, --shared-folder <path> Folder to share with Remix IDE
--read-only Treat shared folder as read-only (experimental)
-h, --help output usage information
```
## SHARE A FOLDER
`remixd -s <absolute-path> --remix-ide https://remix.ethereum.org`
The current user should have `read/write` access to the folder (at least `read` access).
It is important to notice that changes made to the current file in `Remix IDE` are automatically saved to the local computer every 5000 ms. There is no `Save` action. But the `Ctrl-Z` (undo) can be used.
Furthermore :
- No copy of the shared folder are kept in the browser storage.
- It is not possible to create a file from `Remix IDE` (that might change).
- If a folder does not contain any file, the folder will not be displayed in the explorer (that might change).
- Symbolic links are not forwarded to Remix IDE.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save