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 downloadsolc_root
- 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/build_and_publish_docker_images.sh
deploy-remix-alpha:
docker:
@ -245,6 +246,36 @@ jobs:
./apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh;
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:
version: 2
build_all:
@ -280,3 +311,11 @@ workflows:
filters:
branches:
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>
account_passphrase=<passphrase>
account_password=<password>
NODE_OPTIONS=--max-old-space-size=2048

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

@ -33,7 +33,7 @@ function checkDebug (browser: NightwatchBrowser, id: string, debugValue: Nightwa
}
const equal = deepequal(debugValue, value)
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()
})

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

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

@ -1,6 +1,6 @@
'use strict'
const storage = `pragma solidity >=0.4.22 <0.7.0;
const storage = `pragma solidity >=0.7.0 <0.8.0;
/**
* @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
@ -54,7 +54,7 @@ contract 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
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
@ -109,7 +109,7 @@ contract Ballot {
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) public {
constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender;
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 "../3_Ballot.sol";

@ -16,7 +16,7 @@ module.exports = {
'Should launch debugger': function (browser: NightwatchBrowser) {
browser.addFile('blah.sol', sources[0]['browser/blah.sol'])
.clickLaunchIcon('udapp')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]')
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 45000)
.click('*[title="Deploy - transact (not payable)"]')
.debugTransaction(0)
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER')
@ -42,7 +42,6 @@ module.exports = {
.waitForElementVisible('*[data-id="slider"]')
.click('*[data-id="slider"]')
.setValue('*[data-id="slider"]', '50')
.pause(2000)
.assert.containsText('*[data-id="solidityLocals"]', 'no locals')
.assert.containsText('*[data-id="stepdetail"]', 'vm trace step:\n92')
},
@ -79,8 +78,10 @@ module.exports = {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.6.12+commit.27d51765.js')
.pause(2000)
.clickLaunchIcon('udapp')
.testContracts('externalImport.sol', sources[1]['browser/externalImport.sol'], ['ERC20'])
.waitForElementPresent('*[title="Deploy - transact (not payable)"]', 35000)
.selectContract('ERC20')
.createContract('"tokenName", "symbol"')
.debugTransaction(2)
@ -94,6 +95,88 @@ module.exports = {
}`) != -1,
'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()
},
@ -104,7 +187,7 @@ const sources = [
{
'browser/blah.sol': {
content: `
pragma solidity >=0.4.22 <0.6.0;
pragma solidity >=0.7.0 <0.8.0;
contract Kickstarter {
@ -119,7 +202,7 @@ const sources = [
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/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 })
.executeScript(`remix.exeCurrent()`)
.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) {

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

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

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

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

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

@ -2,22 +2,14 @@
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}}
echo "$BUILD_ID"
TEST_EXITCODE=0
npm run ganache-cli &
npm run serve &
setupRemixd
echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &
npm run remixd &
sleep 5

@ -2,22 +2,14 @@
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}}
echo "$BUILD_ID"
TEST_EXITCODE=0
npm run ganache-cli &
npm run serve &
setupRemixd
echo 'sharing folder: ' $PWD '/apps/remix-ide/contracts' &
npm run remixd &
sleep 5

@ -2,22 +2,12 @@
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}}
echo "$BUILD_ID"
TEST_EXITCODE=0
npm run ganache-cli &
npm run serve &
setupRemixd
sleep 5

@ -7,11 +7,6 @@ if [ "$CIRCLE_BRANCH" == "master" ]; then
export TAG="latest";
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-compose -f docker-compose.yaml -f build.yaml build
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 compiler = require('solc')
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')
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {

@ -1,6 +1,6 @@
'use strict'
const storage = `pragma solidity >=0.4.22 <0.7.0;
const storage = `pragma solidity >=0.7.0 <0.8.0;
/**
* @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
@ -54,7 +54,7 @@ contract 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
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
@ -109,7 +109,7 @@ contract Ballot {
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) public {
constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender;
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 "../3_Ballot.sol";

@ -23,7 +23,7 @@ class CompilerContainer {
timeout: 300,
allversions: 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 {
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 {
@ -32,7 +52,9 @@ class DebuggerUI {
this.event = new EventManager()
this.isActive = false
this.opt = {
debugWithGeneratedSources: false
}
this.sourceHighlighter = new SourceHighlighter()
this.startTxBrowser()
@ -72,12 +94,31 @@ class DebuggerUI {
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(
this.currentReceipt.contractAddress || this.currentReceipt.to,
this.currentReceipt)
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) {
await this.debuggerModule.call('editor', 'discardHighlight')
await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path)
@ -137,7 +178,8 @@ class DebuggerUI {
console.error(e)
}
return null
}
},
debugWithGeneratedSources: this.opt.debugWithGeneratedSources
})
this.listenToEvents()
@ -167,7 +209,8 @@ class DebuggerUI {
console.error(e)
}
return null
}
},
debugWithGeneratedSources: false
})
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error)
@ -188,10 +231,16 @@ class DebuggerUI {
var view = yo`
<div>
<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.stepManagerView}
${this.debuggerHeadPanelsView}
</div>
<div class="${css.statusMessage}">${this.statusMessage}</div>
${this.debuggerPanelsView}
</div>

@ -83,9 +83,13 @@ function VmDebugger (vmDebuggerLogic) {
this.vmDebuggerLogic.event.register('solidityStateUpdating', this.solidityState.setUpdating.bind(this.solidityState))
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('solidityLocalsMessage', this.solidityLocals.setMessage.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.data = {}

@ -6,18 +6,28 @@ var yo = require('yo-yo')
class SolidityLocals {
constructor (_parent, _traceManager, _internalTreeCall) {
constructor () {
this.event = new EventManager()
this.basicPanel = new DropdownPanel('Solidity Locals', {
json: true,
formatSelf: solidityTypeFormatter.formatSelf,
extractData: solidityTypeFormatter.extractData
extractData: solidityTypeFormatter.extractData,
loadMore: (cursor) => {
this.event.trigger('solidityLocalsLoadMore', [cursor])
}
})
this.view
this._data = null
}
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) {
@ -28,6 +38,18 @@ class SolidityLocals {
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 () {
this.view = yo`<div id='soliditylocals' data-id="solidityLocals">${this.basicPanel.render()}</div>`
return this.view

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

@ -31,7 +31,7 @@ class ContractDropdownUI {
if (success) {
this.selectContractNames.removeAttribute('disabled')
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 {
this.selectContractNames.setAttribute('disabled', true)

@ -4,7 +4,6 @@ var remixLib = require('@remix-project/remix-lib')
var EventManager = remixLib.EventManager
var format = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper
var helper = require('../../../../lib/helper.js')
/**
* Record transaction as long as the user create them.
@ -12,13 +11,11 @@ var helper = require('../../../../lib/helper.js')
*
*/
class Recorder {
constructor (blockchain, fileManager, config) {
constructor (blockchain) {
var self = this
self.event = new EventManager()
self.blockchain = blockchain
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) => {
if (tx.useCall) return
@ -29,7 +26,7 @@ class Recorder {
var record = { value, parameters: payLoad.funArgs }
if (!to) {
var abi = payLoad.contractABI
var keccak = ethutil.bufferToHex(ethutil.keccak(abi))
var keccak = ethutil.bufferToHex(ethutil.keccak(JSON.stringify(abi)))
record.abi = keccak
record.contractName = payLoad.contractName
record.bytecode = payLoad.contractBytecode
@ -279,24 +276,26 @@ class Recorder {
return address
}
runScenario (continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
var currentFile = this.config.get('currentFile')
this.fileManager.fileProviderOf(currentFile).get(currentFile, (error, json) => {
if (error) {
return cb('Invalid Scenario File ' + error)
runScenario (json, continueCb, promptCb, alertCb, confirmationCb, logCallBack, cb) {
if (!json) {
return cb('a json content must be provided')
}
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 {
var obj = JSON.parse(json)
var txArray = obj.transactions || []
var accounts = obj.accounts || []
var options = obj.options || {}
var abis = obj.abis || {}
var linkReferences = obj.linkReferences || {}
var txArray = json.transactions || []
var accounts = json.accounts || []
var options = json.options || {}
var abis = json.abis || {}
var linkReferences = json.linkReferences || {}
} catch (e) {
return cb('Invalid Scenario File, please try again')
return cb('Invalid Scenario File. Please try again')
}
if (!txArray.length) {
@ -306,24 +305,8 @@ class Recorder {
this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, (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

@ -1,16 +1,29 @@
var yo = require('yo-yo')
var remixLib = require('@remix-project/remix-lib')
var EventManager = remixLib.EventManager
import { Plugin } from '@remixproject/engine'
var csjs = require('csjs-inject')
var css = require('../styles/run-tab-styles')
import * as packageJson from '../../../../package.json'
var modalDialogCustom = require('../../ui/modal-dialog-custom')
var modalDialog = require('../../ui/modaldialog')
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.recorder = recorder
this.logCallBack = logCallBack
@ -31,10 +44,16 @@ class RecorderUI {
onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true">
</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) => {
if (error) {
var msg = typeof error !== 'string' ? error.message : error
@ -67,14 +86,16 @@ class RecorderUI {
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
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) {
return modalDialogCustom.alert(error)
}
this.event.trigger('newScenario', [abi, address, contractName])
})
}).catch((error) => modalDialogCustom.alert(error))
}
getConfirmationCb (modalDialog, confirmDialog) {
@ -110,7 +131,7 @@ class RecorderUI {
}
triggerRecordButton () {
this.recorder.saveScenario(
this.saveScenario(
(path, 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

@ -63,7 +63,7 @@ class TestTabLogic {
let relative = remixPath.relative(this.currentPath, remixPath.dirname(fileToImport))
if (relative === '') relative = '.'
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.
${comment}

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

@ -118,13 +118,13 @@ export class RunTab extends LibraryPlugin {
renderRecorder (udappUI, fileManager, config, logCallback) {
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) => {
this.recorderCount.innerText = count
})
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) => {
var noInstancesText = this.noInstancesText

@ -34,6 +34,9 @@ var css = csjs`
.label_value {
min-width: 10%;
}
.cursor_pointer {
cursor: pointer;
}
`
var EventManager = require('../../lib/events')
@ -49,6 +52,7 @@ class TreeView {
this.event = new EventManager()
this.extractData = opts.extractData || this.extractDataDefault
this.formatSelf = opts.formatSelf || this.formatSelfDefault
this.loadMore = opts.loadMore
this.view = null
this.expandPath = []
}
@ -111,6 +115,9 @@ class TreeView {
self.event.trigger('nodeRightClick', [keyPath, data, label, event])
}
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 {
caret.style.visibility = 'hidden'
label.oncontextmenu = function (event) {

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

@ -15,12 +15,14 @@ module.exports = {
},
promptPassphraseCreation: function (ok, cancel) {
var text = 'Please provide a Passphrase for the account creation'
var input = yo`<div>
<input id="prompt1" type="password" name='prompt_text' class="${css['prompt_text']}" >
var input = yo`
<div>
<input id="prompt1" type="password" name='prompt_text' class="${css['prompt_text']}" oninput="${(e) => validateInput(e)}">
<br>
<br>
<input id="prompt2" type="password" name='prompt_text' class="${css['prompt_text']}" >
</div>`
<input id="prompt2" type="password" name='prompt_text' class="${css['prompt_text']}" oninput="${(e) => validateInput(e)}">
</div>
`
return modal(null, yo`<div>${text}<div>${input}</div></div>`,
{
fn: () => {
@ -42,7 +44,16 @@ module.exports = {
},
promptMulti: function ({ title, text, inputValue }, ok, cancel) {
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>`,
{
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) {
if (!inputValue) inputValue = ''
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>`,
{
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) {
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 class="modal-content ${css.modalContent} ${opts.class}">
<div class="modal-header">

@ -81,18 +81,24 @@ Renderer.prototype.error = function (message, container, opt) {
text = message.innerText
}
var errLocation = text.match(/^([^:]*):([0-9]*):(([0-9]*):)? /)
if ((!opt.errFile || !opt.errCol || !opt.errLine) && errLocation) {
errLocation = parseRegExError(errLocation)
opt.errFile = errLocation.errFile
opt.errLine = errLocation.errLine
opt.errCol = errLocation.errCol
}
// ^ e.g:
// browser/gm.sol: Warning: Source file does not specify required compiler version! Consider adding "pragma solidity ^0.6.12
// 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
// extract line / column
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) {
this._error(errLocation.errFile, {
row: errLocation.errLine,
column: errLocation.errCol,
if (!opt.noAnnotations && opt.errFile) {
this._error(opt.errFile, {
row: opt.errLine,
column: opt.errCol,
text: text,
type: opt.type
})
@ -107,7 +113,7 @@ Renderer.prototype.error = function (message, container, opt) {
$error.click((ev) => {
if (opt.click) {
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)
}
})
@ -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

@ -5,7 +5,7 @@ var css = csjs`
.modalFooter {
}
.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-duration: 0.4s;
animation-name: animatetop;

@ -37,7 +37,7 @@ class InjectedProvider {
signMessage (message, account, _passphrase, cb) {
const messageHash = hashPersonalMessage(Buffer.from(message))
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)
})
} 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']
export function isNative (name) {
const nativePlugins = ['vyper', 'workshops']
const nativePlugins = ['vyper', 'workshops', 'debugger']
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-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.).
+ [`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)**.

@ -1,7 +1,8 @@
{
"extends": "../../.eslintrc",
"rules": {
"@typescript-eslint/no-explicit-any": "off"
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/prefer-namespace-keyword": "off"
},
"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" {
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)
@ -200,7 +208,7 @@ export class AstWalker extends EventEmitter {
}
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
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}`);
callback(ast);
for (const k of Object.keys(ast)) {
@ -223,7 +231,7 @@ export class AstWalker extends EventEmitter {
// Normalizes parameter callback and calls walkFullInternal
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
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

@ -1,4 +1,4 @@
import { isAstNode, AstWalker } from './astWalker';
import { isAstNode, isYulAstNode, AstWalker } from './astWalker';
import { AstNode, LineColPosition, LineColRange, Location } from "./types";
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.
*/
export function sourceLocationFromAstNode(astNode: AstNode): Location | null {
if (isAstNode(astNode) && astNode.src) {
if (isAstNode(astNode) && isYulAstNode(astNode) && astNode.src) {
return sourceLocationFromSrc(astNode.src)
}
return null;

@ -26,6 +26,7 @@ const {SolidityProxy, stateDecoder, localDecoder, InternalCallTree} = require('.
function Ethdebugger (opts) {
this.compilationResult = opts.compilationResult || function (contractAddress) { return null }
this.web3 = opts.web3
this.opts = opts
this.event = new EventManager()
@ -33,19 +34,30 @@ function Ethdebugger (opts) {
this.traceManager = new TraceManager({web3: this.web3})
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.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 () {
this.traceManager = new TraceManager({web3: this.web3})
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.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,
{ ...this.opts, includeLocalVariables})
this.event.trigger('managersChanged')
}
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)
}
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) {
return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts)
}

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

@ -15,13 +15,20 @@ class BreakpointManager {
* @param {Object} _debugger - type of EthDebugger
* @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.debugger = _debugger
this.traceManager = traceManager
this.callTree = callTree
this.solidityProxy = solidityProxy
this.breakpoints = {}
this.locationToRowConverter = _locationToRowConverter
this.locationToRowConverter = locationToRowConverter
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) {
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) {
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) {
console.log('row converter not provided')
return
return console.log('row converter not provided')
}
this.jump(fromStep || 0, -1, defaultToLimit, this.traceManager.trace)
}
function depthChange (step, trace) {
depthChange (step, trace) {
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
// depthChange -> returning from an external call
// sourceLocation.start <= previousSourceLocation.start && ... -> previous src is contained in the current one
if ((helper.isJumpDestInstruction(self.debugger.traceManager.trace[currentStep]) && previousSourceLocation.jump === 'o') ||
depthChange(currentStep, self.debugger.traceManager.trace) ||
if ((helper.isJumpDestInstruction(trace[currentStep]) && previousSourceLocation.jump === 'o') ||
this.depthChange(currentStep, trace) ||
(sourceLocation.start <= previousSourceLocation.start &&
sourceLocation.start + sourceLocation.length >= previousSourceLocation.start + previousSourceLocation.length)) {
return false
}
self.jumpToCallback(currentStep)
self.event.trigger('breakpointHit', [sourceLocation, currentStep])
this.event.trigger('breakpointStep', [currentStep])
this.event.trigger('breakpointHit', [sourceLocation, currentStep])
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 previousSourceLocation
let currentStep = fromStep + direction
let lineHadBreakpoint = false
while (currentStep > 0 && currentStep < this.debugger.traceManager.trace.length) {
while (currentStep > 0 && currentStep < trace.length) {
try {
previousSourceLocation = sourceLocation
sourceLocation = await this.debugger.callTree.extractSourceLocation(currentStep)
sourceLocation = await this.callTree.extractValidSourceLocation(currentStep)
} catch (e) {
console.log('cannot jump to breakpoint ' + e)
return
return console.log('cannot jump to breakpoint ' + e)
}
let lineColumn = await this.locationToRowConverter(sourceLocation)
if (this.previousLine !== lineColumn.start.line) {
if (direction === -1 && lineHadBreakpoint) { // TODO : improve this when we will build the correct structure before hand
lineHadBreakpoint = false
if (hitLine(currentStep + 1, previousSourceLocation, sourceLocation, this)) {
if (this.hitLine(currentStep + 1, previousSourceLocation, sourceLocation, trace)) {
return
}
}
this.previousLine = lineColumn.start.line
if (this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line)) {
lineHadBreakpoint = true
if (direction === 1 && hitLine(currentStep, sourceLocation, previousSourceLocation, this)) {
if (direction === 1 && this.hitLine(currentStep, sourceLocation, previousSourceLocation, trace)) {
return
}
}
@ -108,9 +115,9 @@ class BreakpointManager {
return
}
if (direction === -1) {
this.jumpToCallback(0)
this.event.trigger('breakpointStep', [0])
} 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
*/
hasBreakpointAtLine (fileIndex, line) {
const filename = this.debugger.solidityProxy.fileNameFromIndex(fileIndex)
const filename = this.solidityProxy.fileNameFromIndex(fileIndex)
if (!(filename && this.breakpoints[filename])) {
return false
}

@ -16,7 +16,16 @@ function CodeManager (_traceManager) {
this.event = new EventManager()
this.isLoading = false
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'
const codeUtils = require('./codeUtils')
function CodeResolver (options) {
this.web3 = options.web3
function CodeResolver ({getCode}) {
this.getCode = getCode
this.bytecodeByAddress = {} // bytes code 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) {
return new Promise((resolve, reject) => {
const cache = this.getExecutingCodeFromCache(address)
if (cache) {
return resolve(cache)
return cache
}
this.web3.eth.getCode(address, (error, code) => {
if (error) {
// return console.log(error)
return reject(error)
}
return resolve(this.cacheExecutingCode(address, code))
})
})
const code = await this.getCode(address)
return this.cacheExecutingCode(address, code)
}
CodeResolver.prototype.cacheExecutingCode = function (address, hexCode) {
@ -41,11 +34,8 @@ CodeResolver.prototype.cacheExecutingCode = function (address, hexCode) {
}
CodeResolver.prototype.formatCode = function (hexCode) {
const code = codeUtils.nameOpCodes(Buffer.from(hexCode.substring(2), 'hex'))
return {
code: code[0],
instructionsIndexByBytesOffset: code[1]
}
const [code, instructionsIndexByBytesOffset] = codeUtils.nameOpCodes(Buffer.from(hexCode.substring(2), 'hex'))
return {code, instructionsIndexByBytesOffset}
}
CodeResolver.prototype.getExecutingCodeFromCache = function (address) {

@ -228,6 +228,10 @@ class VmDebuggerLogic {
listenToSolidityLocalsEvents () {
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.event.trigger('solidityLocals', [state])
})

@ -18,15 +18,24 @@ function Debugger (options) {
this.debugger = new Ethdebugger({
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()
if (!compilationResult) return { start: null, end: null }
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)
@ -38,10 +47,6 @@ function Debugger (options) {
this.debugger.event.register('traceUnloaded', this, () => {
this.event.trigger('debuggerStatus', [false])
})
this.event.register('breakpointStep', (step) => {
this.step_manager.jumpTo(step)
})
}
Debugger.prototype.registerAndHighlightCodeItem = async function (index) {
@ -53,10 +58,19 @@ Debugger.prototype.registerAndHighlightCodeItem = async function (index) {
const compilationResultForAddress = await this.compilationResult(address)
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) {
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, compilationResultForAddress.source.sources, compilationResultForAddress.data.sources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation])
const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
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 {
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.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])
})
})

@ -15,6 +15,7 @@ class DebuggerSolidityLocals {
}
init (sourceLocation) {
this._sourceLocation = sourceLocation
var decodeTimeout = null
if (!this.storageResolver) {
return this.event.trigger('solidityLocalsMessage', ['storage not ready'])
@ -28,7 +29,7 @@ class DebuggerSolidityLocals {
}, 500)
}
decode (sourceLocation) {
decode (sourceLocation, cursor) {
const self = this
this.event.trigger('solidityLocalsMessage', [''])
this.traceManager.waterfall([
@ -65,13 +66,19 @@ class DebuggerSolidityLocals {
var memory = result[1].value
try {
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) {
this.event.trigger('solidityLocals', [locals])
}
if (!Object.keys(locals).length) {
this.event.trigger('solidityLocalsMessage', ['no locals'])
}
} else {
if (!locals.error) {
this.event.trigger('solidityLocalsLoadMoreCompleted', [locals])
}
}
})
} catch (e) {
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

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

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

@ -22,21 +22,24 @@ class InternalCallTree {
* @param {Object} traceManager - trace manager
* @param {Object} solidityProxy - solidity proxy
* @param {Object} codeManager - code manager
* @param {Object} opts - { includeLocalVariables }
* @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources }
*/
constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) {
this.includeLocalVariables = opts.includeLocalVariables
this.debugWithGeneratedSources = opts.debugWithGeneratedSources
this.event = new EventManager()
this.solidityProxy = solidityProxy
this.traceManager = traceManager
this.sourceLocationTracker = new SourceLocationTracker(codeManager)
this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources })
debuggerEvent.register('newTraceLoaded', (trace) => {
this.reset()
if (!this.solidityProxy.loaded()) {
this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree'])
} else {
// 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) {
this.event.trigger('callTreeBuildFailed', [result.error])
} else {
@ -134,12 +137,22 @@ class InternalCallTree {
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
tree.scopeStarts[step] = scopeId
tree.scopes[scopeId] = { firstStep: step, locals: {} }
tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation }
function callDepthChange (step, trace) {
if (step + 1 < trace.length) {
@ -157,7 +170,7 @@ async function buildTree (tree, step, scopeId, isExternalCall) {
included.file === source.file)
}
let currentSourceLocation = {start: -1, length: -1, file: -1}
let currentSourceLocation = { start: -1, length: -1, file: -1 }
let previousSourceLocation = currentSourceLocation
while (step < tree.traceManager.trace.length) {
let sourceLocation
@ -176,10 +189,11 @@ async function buildTree (tree, step, scopeId, isExternalCall) {
return { outStep: step, error: 'InternalCallTree - No source Location. ' + 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
if (isCallInstruction || sourceLocation.jump === 'i') {
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) {
return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error }
} else {
@ -211,8 +225,19 @@ function createReducedTrace (tree, index) {
tree.reducedTrace.push(index)
}
function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) {
const variableDeclaration = resolveVariableDeclaration(tree, step, sourceLocation)
function getGeneratedSources (tree, scopeId, contractObj) {
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,
// 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.
@ -221,37 +246,39 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
const stack = tree.traceManager.getStackAt(step)
// 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.
tree.solidityProxy.contractNameAt(step).then((contractName) => {
if (variableDeclaration.name !== '') {
var states = tree.solidityProxy.extractStatesDefinitions()
states = tree.solidityProxy.extractStatesDefinitions()
var location = typesUtil.extractLocationFromAstVariable(variableDeclaration)
location = location === 'default' ? 'storage' : location
// we push the new local variable in our tree
tree.scopes[scopeId].locals[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,
sourceLocation: sourceLocation
}
}
})
} catch (error) {
console.log(error)
}
}
// 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
const functionDefinition = resolveFunctionDefinition(tree, step, previousSourceLocation)
if (functionDefinition && (newLocation && traceHelper.isJumpDestInstruction(tree.traceManager.trace[step - 1]) || functionDefinition.kind === 'constructor')) {
const functionDefinition = resolveFunctionDefinition(tree, previousSourceLocation, generatedSources)
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)
const functionDefinitionAndInputs = {functionDefinition, inputs: []}
const functionDefinitionAndInputs = { functionDefinition, inputs: [] }
// means: the previous location was a function definition && JUMPDEST
// => we are at the beginning of the function and input/output are setup
tree.solidityProxy.contractNameAt(step).then((contractName) => { // cached
try {
const stack = tree.traceManager.getStackAt(step)
var states = tree.solidityProxy.extractStatesDefinitions()
states = tree.solidityProxy.extractStatesDefinitions()
if (functionDefinition.parameters) {
let inputs = functionDefinition.parameters
let outputs = functionDefinition.returnParameters
@ -266,15 +293,14 @@ function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLoc
// }
// input params
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
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) {
console.log(error)
}
})
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
// and keep this in a cache
function resolveVariableDeclaration (tree, step, sourceLocation) {
function resolveVariableDeclaration (tree, sourceLocation, generatedSources) {
if (!tree.variableDeclarationByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation)
const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
if (ast) {
tree.variableDeclarationByFile[sourceLocation.file] = extractVariableDeclarations(ast, tree.astWalker)
} else {
// console.log('Ast not found for step ' + step + '. file ' + sourceLocation.file)
return null
}
}
@ -297,13 +322,12 @@ function resolveVariableDeclaration (tree, step, sourceLocation) {
// this extract all the function definition for a given ast and file
// and keep this in a cache
function resolveFunctionDefinition (tree, step, sourceLocation) {
function resolveFunctionDefinition (tree, sourceLocation, generatedSources) {
if (!tree.functionDefinitionByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation)
const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
if (ast) {
tree.functionDefinitionByFile[sourceLocation.file] = extractFunctionDefinitions(ast, tree.astWalker)
} else {
// console.log('Ast not found for step ' + step + '. file ' + sourceLocation.file)
return null
}
}
@ -313,7 +337,7 @@ function resolveFunctionDefinition (tree, step, sourceLocation) {
function extractVariableDeclarations (ast, astWalker) {
const ret = {}
astWalker.walkFull(ast, (node) => {
if (node.nodeType === 'VariableDeclaration') {
if (node.nodeType === 'VariableDeclaration' || node.nodeType === 'YulVariableDeclaration') {
ret[node.src] = node
}
})
@ -323,7 +347,7 @@ function extractVariableDeclarations (ast, astWalker) {
function extractFunctionDefinitions (ast, astWalker) {
const ret = {}
astWalker.walkFull(ast, (node) => {
if (node.nodeType === 'FunctionDefinition') {
if (node.nodeType === 'FunctionDefinition' || node.nodeType === 'YulFunctionDefinition') {
ret[node.src] = node
}
})

@ -1,6 +1,6 @@
'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)
if (!scope) {
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++
}
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) {
console.log(e)
locals[name] = '<decoding failed - ' + e.message + '>'
@ -35,6 +35,4 @@ function formatMemory (memory) {
return memory
}
module.exports = {
solidityLocals: solidityLocals
}
module.exports = {solidityLocals}

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

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

@ -10,9 +10,8 @@ class Address extends ValueType {
decodeValue (value) {
if (!value) {
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
}
}
return {
value: ret,
length: '0x' + size.toString(16),
type: this.typeName
}
return {value: ret, length: '0x' + size.toString(16), type: this.typeName}
}
decodeFromMemoryInternal (offset, memory) {
decodeFromMemoryInternal (offset, memory, skip) {
const ret = []
let length = this.arraySize
if (this.arraySize === 'dynamic') {
@ -84,7 +80,17 @@ class ArrayType extends RefType {
length = parseInt(length, 16)
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
ret.push(this.underlyingType.decodeFromMemory(contentOffset, memory))
offset += 32
@ -92,7 +98,9 @@ class ArrayType extends RefType {
return {
value: ret,
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)
} catch (e) {
console.log(e)
return {
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
}
const bn = new BN(value, 16)
if (bn.testn(0)) {
@ -31,10 +28,7 @@ class DynamicByteArray extends RefType {
currentSlot = await util.readFromStorage(dataPos, storageResolver)
} catch (e) {
console.log(e)
return {
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
}
while (length.gt(ret.length) && ret.length < 32000) {
currentSlot = currentSlot.replace('0x', '')
@ -44,24 +38,13 @@ class DynamicByteArray extends RefType {
currentSlot = await util.readFromStorage(dataPos, storageResolver)
} catch (e) {
console.log(e)
return {
value: '<decoding failed - ' + e.message + '>',
type: this.typeName
}
return {value: '<decoding failed - ' + e.message + '>', type: this.typeName}
}
}
return {
value: '0x' + ret.replace(/(00)+$/, ''),
length: '0x' + length.toString(16),
type: this.typeName
}
return {value: '0x' + ret.replace(/(00)+$/, ''), length: '0x' + length.toString(16), type: this.typeName}
} else {
var size = parseInt(value.substr(value.length - 2, 2), 16) / 2
return {
value: '0x' + value.substr(0, size * 2),
length: '0x' + size.toString(16),
type: this.typeName
}
return {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
let length = memory.substr(offset, 64)
length = 2 * parseInt(length, 16)
return {
length: '0x' + length.toString(16),
value: '0x' + memory.substr(offset + 64, length),
type: this.typeName
}
return {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)
let ret = await this.decodeMappingsLocation(mappingPreimages, location, storageResolver) // fetch mapping storage changes
ret = Object.assign({}, this.initialDecodedState, ret) // merge changes
return {
value: ret,
type: this.typeName
}
return {value: ret, type: this.typeName}
}
decodeFromMemoryInternal (offset, memory) {
// mappings can only exist in storage and not in memory
// so this should never be called
return {
value: '<not implemented>',
length: '0x',
type: this.typeName
}
return {value: '<not implemented>', length: '0x', type: this.typeName}
}
async decodeMappingsLocation (preimages, location, storageResolver) {

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

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

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

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

@ -2,20 +2,6 @@
const ethutil = require('ethereumjs-util')
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) {
let bigNumber = new BN(value, 16)
if (signed) {
@ -24,21 +10,17 @@ function decodeIntFromHex (value, byteLength, signed) {
return bigNumber.toString(10)
}
function readFromStorage (slot, storageResolver) {
function readFromStorage(slot, storageResolver) {
const hexSlot = '0x' + normalizeHex(ethutil.bufferToHex(slot))
return new Promise((resolve, reject) => {
storageResolver.storageSlot(hexSlot, (error, slot) => {
if (error) {
return reject(error)
} else {
if (!slot) {
slot = {
key: slot,
value: ''
}
if (!slot) {
slot = { key: slot, value: '' }
}
return resolve(normalizeHex(slot.value))
}
})
})
}
@ -120,3 +102,5 @@ function normalizeHex (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 SourceMappingDecoder = require('./sourceMappingDecoder')
const remixLib = require('@remix-project/remix-lib')
const { map } = require('jquery')
const util = remixLib.util
/**
* 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.event = new EventManager()
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 {Int} index - index in the instruction list from where the source location is retrieved
* @param {Object} contractDetails - AST of compiled contracts
* @param {Function} cb - callback function
*/
SourceLocationTracker.prototype.getSourceLocationFromInstructionIndex = async function (address, index, 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 {Int} vmtraceStepIndex - index of the current code in the vmtrace
* @param {Object} contractDetails - AST of compiled contracts
* @param {Function} cb - callback function
*/
SourceLocationTracker.prototype.getSourceLocationFromVMTraceIndex = async function (address, vmtraceStepIndex, contracts) {
const sourceMap = await extractSourceMap(this, this.codeManager, address, contracts)
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 () {
@ -57,7 +88,9 @@ function getSourceMap (address, code, contracts) {
bytes = isCreation ? bytecode.object : deployedBytecode.object
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)
if (sourceMap) {
if (!helper.isContractCreation(address)) self.sourceMapByAddress[address] = sourceMap
resolve(sourceMap)
} else {
reject('no sourcemap associated with the code ' + address)
return resolve(sourceMap)
}
reject('no sourcemap associated with the code ' + address)
}).catch(reject)
})
}

@ -94,12 +94,8 @@ SourceMappingDecoder.prototype.convertOffsetToLineColumn = function (sourceLocat
start: convertFromCharPosition(sourceLocation.start, 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 column = pos - beginColumn
return {
line: line,
column: column
}
return {line, column}
}
function sourceLocationFromAstNode (astNode) {
@ -205,19 +198,13 @@ function atIndex (index, mapping) {
continue
}
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) {
ret.start = parseInt(current[0])
}
if (ret.length === undefined && current[1] && current[1] !== '-1' && current[1].length) {
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])
}
if (ret.jump === undefined && current[3] && current[3].length) {

@ -51,10 +51,9 @@ function getPreimage (web3, key) {
return new Promise((resolve, reject) => {
web3.debug.preimage(key.indexOf('0x') === 0 ? key : '0x' + key, (error, preimage) => {
if (error) {
resolve(null)
} else {
resolve(preimage)
return resolve(null)
}
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 deepequal = require('deep-equal')
var remixLib = require('@remix-project/remix-lib')
var compilerInput = require('./helpers/compilerHelper').compilerInput
var SourceMappingDecoder = require('../src/source/sourceMappingDecoder')
@ -257,13 +258,14 @@ function testDebugging (debugManager) {
tape('traceManager.decodeLocalsAt', async (t) => {
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 {
const address = debugManager.traceManager.getCurrentCalledAddressAt(330)
const location = await debugManager.sourceLocationFromVMTraceIndex(address, 330)
debugManager.decodeLocalsAt(330, location, (error, decodedlocals) => {
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) {
return t.end(error)
@ -273,8 +275,14 @@ function testDebugging (debugManager) {
tape('breakPointManager', (t) => {
t.plan(2)
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))
}})
breakPointManager.event.register('managersChanged', () => {
const {traceManager, callTree, solidityProxy} = debugManager
breakPointManager.setManagers({traceManager, callTree, solidityProxy})
})
breakPointManager.add({fileName: 'test.sol', row: 38})

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

@ -13,19 +13,19 @@ var EventManager = require('../../../src/eventManager')
var helper = require('./helper')
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) {
st.fail(error)
} else {
return st.fail(error)
}
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
st.fail(error)
} else {
return st.fail(error)
}
tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3})
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
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)
var debuggerEvent = new EventManager()
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(functions2.length, 2)
st.equals(functions3.length, 0)
st.equals(Object.keys(functions1[0])[0], 'functionDefinition')
st.equals(Object.keys(functions1[0])[1], 'inputs')
st.equals(functions1[0].inputs[0], 'foo')
@ -123,9 +124,7 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => {
st.fail(error)
})
}
})
}
})
}

@ -10,19 +10,19 @@ var TraceManager = require('../../../src/trace/traceManager')
var CodeManager = require('../../../src/code/codeManager')
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) {
st.fail(error)
} else {
return st.fail(error)
}
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
st.fail(error)
} else {
return st.fail(error)
}
tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3})
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
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)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -69,8 +69,6 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => {
st.fail(error)
})
}
})
}
})
}

@ -11,19 +11,19 @@ var TraceManager = require('../../../src/trace/traceManager')
var CodeManager = require('../../../src/code/codeManager')
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) {
st.fail(error)
} else {
return st.fail(error)
}
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
st.fail(error)
} else {
return st.fail(error)
}
tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3})
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
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)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -56,8 +56,6 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => {
st.fail(error)
})
}
})
}
})
}

@ -10,19 +10,19 @@ var TraceManager = require('../../../src/trace/traceManager')
var CodeManager = require('../../../src/code/codeManager')
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) {
st.fail(error)
} else {
return st.fail(error)
}
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
st.fail(error)
} else {
return st.fail(error)
}
tx.to = traceHelper.contractCreationToken('0')
var traceManager = new TraceManager({web3: vm.web3})
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
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)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
@ -113,8 +113,6 @@ module.exports = function (st, vm, privateKey, contractBytecode, compilationResu
}).catch((error) => {
st.fail(error)
})
}
})
}
})
}

@ -6,6 +6,10 @@ web3Override.debug = {}
var data = init.readFile(require('path').resolve(__dirname, 'testWeb3.json'))
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) {
if (callback) {
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)
const tokenSaleChallengeMap = sourceMappingDecoder.atIndex(170, sourceMapping.tokenSaleChallengeSourceMap)
console.log(tokenSaleChallengeMap)
st.equal(tokenSaleChallengeMap.start, 211)
st.equal(tokenSaleChallengeMap.length, 48)
st.equal(tokenSaleChallengeMap.file, 0)
st.equal(tokenSaleChallengeMap.start, 45)
st.equal(tokenSaleChallengeMap.length, 16)
st.equal(tokenSaleChallengeMap.file, -1)
st.equal(tokenSaleChallengeMap.jump, '-')
})

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

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

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

@ -9,6 +9,16 @@ const defaultExecutionContext = require('./execution-context')
const txFormat = require('./txFormat')
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
* 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
if (data.slice(0, 2) !== '0x') {
data = '0x' + data
}
if (!this.executionContext.isVM()) {
this.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback)
} else {
return this.runInNode(args.from, args.to, data, args.value, args.gasLimit, args.useCall, confirmationCb, gasEstimationForceSend, promptCb, callback)
}
try {
this.runInVm(args.from, args.to, data, args.value, args.gasLimit, args.useCall, args.timestamp, callback)
} catch (e) {
callback(e, null)
}
}
}
runInVm (from, to, data, value, gasLimit, useCall, timestamp, callback) {
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
await pause()
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
await pause()
return resolve(await tryTillTxAvailable(txhash, executionContext))
} else {
return resolve(tx)
}
return resolve(tx)
})
})
}
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) {
self.queusTxs.push({ tx, stamp, callback })
} else {
return self.queusTxs.push({ tx, stamp, callback })
}
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]
if (callback && typeof callback === 'function') callback(error, result)
if (self.queusTxs.length) {
@ -264,7 +261,6 @@ function run (self, tx, stamp, confirmationCb, gasEstimationForceSend, promptCb,
run(self, next.tx, next.stamp, next.callback)
}
})
}
}
module.exports = TxRunner

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

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

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

@ -211,11 +211,10 @@ web3VmProvider.prototype.traceTransaction = function (txHash, options, cb) {
cb(null, this.vmTraces[txHash])
}
return this.vmTraces[txHash]
} else {
}
if (cb) {
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
@ -232,9 +231,8 @@ web3VmProvider.prototype.storageRangeAt = function (blockNumber, txIndex, addres
storage: JSON.parse(JSON.stringify(storage)),
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') }
@ -245,11 +243,10 @@ web3VmProvider.prototype.getTransaction = function (txHash, cb) {
cb(null, this.txs[txHash])
}
return this.txs[txHash]
} else {
}
if (cb) {
cb('unable to retrieve tx ' + txHash, null)
}
}
}
web3VmProvider.prototype.getTransactionReceipt = function (txHash, cb) {
@ -259,11 +256,10 @@ web3VmProvider.prototype.getTransactionReceipt = function (txHash, cb) {
cb(null, this.txsReceipt[txHash])
}
return this.txsReceipt[txHash]
} else {
}
if (cb) {
cb('unable to retrieve txReceipt ' + txHash, null)
}
}
}
web3VmProvider.prototype.getTransactionFromBlock = function (blockNumber, txIndex, cb) {

@ -41,7 +41,7 @@ class Provider {
}
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]
if (this.options.logDetails) {

@ -1,5 +1,5 @@
module.exports = `
pragma solidity >=0.4.22 <0.7.0;
pragma solidity >=0.4.22 <0.8.0;
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