Merge branch 'master' into intl

pull/5370/head
drafish 2 years ago
commit 8fe99f9437
  1. 6
      .circleci/config.yml
  2. 11
      apps/debugger/src/app/debugger-api.ts
  3. 4
      apps/debugger/src/app/debugger.ts
  4. 2
      apps/remix-ide-e2e/src/commands/checkTerminalFilter.ts
  5. 41
      apps/remix-ide-e2e/src/commands/connectToExternalHttpProvider.ts
  6. 4
      apps/remix-ide-e2e/src/commands/goToVMTraceStep.ts
  7. 2
      apps/remix-ide-e2e/src/commands/switchEnvironment.ts
  8. 173
      apps/remix-ide-e2e/src/examples/editor-test-contracts.ts
  9. 2
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  10. 257
      apps/remix-ide-e2e/src/tests/editor.test.ts
  11. 526
      apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts
  12. 244
      apps/remix-ide-e2e/src/tests/editorHoverContext.test.ts
  13. 203
      apps/remix-ide-e2e/src/tests/editorReferences.test.ts
  14. 91
      apps/remix-ide-e2e/src/tests/editor_error_marker.test.ts
  15. 7
      apps/remix-ide-e2e/src/tests/editor_line_text.test.ts
  16. 45
      apps/remix-ide-e2e/src/tests/erc721.test.ts
  17. 4
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  18. 14
      apps/remix-ide-e2e/src/tests/gist.test.ts
  19. 2
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  20. 1
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  21. 83
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  22. 4
      apps/remix-ide-e2e/src/tests/url.test.ts
  23. 17
      apps/remix-ide-e2e/src/tests/vyper_api.ts
  24. 1
      apps/remix-ide-e2e/src/types/index.d.ts
  25. 2
      apps/remix-ide/ci/deploy_from_travis_remix-alpha.sh
  26. 2
      apps/remix-ide/ci/deploy_from_travis_remix-beta.sh
  27. 2
      apps/remix-ide/ci/deploy_from_travis_remix-live.sh
  28. 17
      apps/remix-ide/src/app.js
  29. 2
      apps/remix-ide/src/app/components/side-panel.tsx
  30. 105
      apps/remix-ide/src/app/editor/editor.js
  31. 8
      apps/remix-ide/src/app/files/dgitProvider.js
  32. 81
      apps/remix-ide/src/app/files/fileManager.ts
  33. 9
      apps/remix-ide/src/app/files/fileProvider.js
  34. 13
      apps/remix-ide/src/app/files/workspaceFileProvider.js
  35. 24
      apps/remix-ide/src/app/panels/layout.ts
  36. 653
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  37. 158
      apps/remix-ide/src/app/plugins/parser/services/code-parser-antlr-service.ts
  38. 235
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  39. 76
      apps/remix-ide/src/app/plugins/parser/services/code-parser-gas-service.ts
  40. 732
      apps/remix-ide/src/app/plugins/parser/types/antlr-types.ts
  41. 1
      apps/remix-ide/src/app/plugins/parser/types/index.ts
  42. 6
      apps/remix-ide/src/app/plugins/remixd-handle.tsx
  43. 6
      apps/remix-ide/src/app/tabs/debugger-tab.js
  44. 2
      apps/remix-ide/src/app/tabs/injected-provider.tsx
  45. 12
      apps/remix-ide/src/app/tabs/locales/en-US.js
  46. 20
      apps/remix-ide/src/app/tabs/locales/zh-CN.js
  47. 3
      apps/remix-ide/src/app/tabs/styles/debugger-tab-styles.js
  48. 1
      apps/remix-ide/src/app/tabs/theme-module.js
  49. 6
      apps/remix-ide/src/app/tabs/web3-provider.js
  50. 1
      apps/remix-ide/src/assets/css/themes/bootstrap-cerulean.min.css
  51. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css
  52. 4
      apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css
  53. 6
      apps/remix-ide/src/assets/css/themes/remix-black_undtds.css
  54. 8940
      apps/remix-ide/src/assets/css/themes/remix-hacker_owl.css
  55. 2
      apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css
  56. 2
      apps/remix-ide/src/assets/js/loader.js
  57. 251
      apps/remix-ide/src/assets/js/parser/Solidity-EZVQ6AE4.tokens
  58. 41374
      apps/remix-ide/src/assets/js/parser/antlr.js
  59. 7
      apps/remix-ide/src/assets/js/parser/antlr.js.map
  60. 3
      apps/remix-ide/src/blockchain/execution-context.js
  61. 1
      apps/remix-ide/src/index.html
  62. 75
      apps/remix-ide/src/lib/offsetToLineColumnConverter.js
  63. 2
      apps/remix-ide/src/remixAppManager.js
  64. 5
      apps/solidity-compiler/src/app/compiler-api.ts
  65. 43
      apps/vyper/README.md
  66. 3
      apps/vyper/src/app/app.css
  67. 23
      apps/vyper/src/app/app.tsx
  68. 52
      apps/vyper/src/app/components/CompilerButton.tsx
  69. 2
      apps/vyper/src/app/components/LocalUrl.tsx
  70. 29
      apps/vyper/src/app/components/VyperResult.tsx
  71. 3
      apps/vyper/src/app/components/WarnRemote.tsx
  72. 161
      apps/vyper/src/app/examples/ballot.tsx
  73. 4
      apps/vyper/src/app/utils/compiler.tsx
  74. 53
      apps/vyper/src/app/utils/remix-client.tsx
  75. 4
      jest.preset.js
  76. 8
      libs/remix-analyzer/package.json
  77. 8
      libs/remix-analyzer/src/types.ts
  78. 6
      libs/remix-astwalker/package.json
  79. 1
      libs/remix-core-plugin/src/index.ts
  80. 244
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  81. 8
      libs/remix-core-plugin/tsconfig.lib.json
  82. 10
      libs/remix-debug/package.json
  83. 11
      libs/remix-debug/src/debugger/stepManager.ts
  84. 4
      libs/remix-lib/package.json
  85. 98
      libs/remix-lib/src/execution/txListener.ts
  86. 4
      libs/remix-lib/src/helpers/hhconsoleSigs.ts
  87. 25
      libs/remix-lib/src/util.ts
  88. 1
      libs/remix-lib/test/data/ERC721.ts
  89. 1
      libs/remix-lib/test/data/sampleERC721.ts
  90. 24
      libs/remix-lib/test/util.ts
  91. 6
      libs/remix-simulator/package.json
  92. 6
      libs/remix-solidity/package.json
  93. 4
      libs/remix-solidity/src/compiler/compiler-worker.ts
  94. 25
      libs/remix-solidity/src/compiler/compiler.ts
  95. 10
      libs/remix-solidity/src/compiler/types.ts
  96. 13
      libs/remix-tests/package.json
  97. 4
      libs/remix-tests/src/compiler.ts
  98. 30
      libs/remix-ui/app/src/lib/remix-app/components/dragbar/dragbar.tsx
  99. 16
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  100. 1
      libs/remix-ui/app/src/lib/remix-app/style/remix-app.css
  101. Some files were not shown because too many files have changed in this diff Show More

@ -411,7 +411,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/*raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
@ -440,7 +440,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/*raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:
@ -468,7 +468,7 @@ jobs:
environment:
- COMMIT_AUTHOR_EMAIL: "yann@ethereum.org"
- COMMIT_AUTHOR: "Circle CI"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
- FILES_TO_PACKAGE: "dist/apps/remix-ide/index.html dist/apps/remix-ide/404.html dist/apps/remix-ide/*raw-loader*.js dist/apps/remix-ide/assets dist/apps/remix-ide/main*.js dist/apps/remix-ide/polyfills*.js dist/apps/remix-ide/favicon.ico dist/apps/remix-ide/vendors~app*.js dist/apps/remix-ide/app*.js"
working_directory: ~/remix-project
steps:

@ -8,8 +8,6 @@ export const DebuggerApiMixin = (Base) => class extends Base {
initialWeb3
initDebuggerApi () {
this.debugHash = null
const self = this
this.web3Provider = {
sendAsync (payload, callback) {
@ -132,7 +130,6 @@ export const DebuggerApiMixin = (Base) => class extends Base {
} catch (e) {
console.error(e)
}
this.debugHash = hash
if (web3) this._web3 = web3
else this._web3 = this.initialWeb3
remixDebug.init.extendWeb3(this._web3)
@ -154,6 +151,14 @@ export const DebuggerApiMixin = (Base) => class extends Base {
}
showMessage (title: string, message: string) {}
onStartDebugging () {
this.call('layout', 'maximiseSidePanel')
}
onStopDebugging () {
this.call('layout', 'resetSidePanel')
}
}
export class CompilerAbstract implements CompilationOutput { // this is a subset of /remix-ide/src/app/compiler/compiler-abstract.js

@ -12,8 +12,6 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
}
offsetToLineColumnConverter: IDebuggerApi['offsetToLineColumnConverter']
debugHash: string
debugHashRequest: number
removeHighlights: boolean
onBreakpointCleared: (listener: onBreakpointClearedListener) => void
onBreakpointAdded: (listener: onBreakpointAddedListener) => void
@ -26,5 +24,7 @@ export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
setFile: (path: string, content: string) => Promise<void>
getDebugWeb3: () => any // returns an instance of web3.js, if applicable (mainet, goerli, ...) it returns a reference to a node from devops (so we are sure debug endpoint is available)
web3: () => any // returns an instance of web3.js
onStartDebugging: () => void // called when debug starts
onStopDebugging: () => void // called when debug stops
}

@ -21,7 +21,7 @@ function checkFilter (browser: NightwatchBrowser, filter: string, test: string,
const filterClass = '[data-id="terminalInputSearch"]'
browser.setValue(filterClass, filter, function () {
browser.execute(function () {
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test
return document.querySelector('[data-id="terminalJournal"]').innerHTML === test || ''
}, [], function (result) {
browser.clearValue(filterClass).setValue(filterClass, '', function () {
if (!result.value) {

@ -0,0 +1,41 @@
import { NightwatchBrowser } from 'nightwatch'
import EventEmitter from 'events'
class ConnectToExternalHttpProvider extends EventEmitter {
command(this: NightwatchBrowser, url: string, identifier: string): NightwatchBrowser {
this.api.element('xpath', `//*[@class='udapp_environment' and contains(.,'${identifier}')]`,
(result) => {
if (result.status as any === -1) {
console.log("No connection to external provider found. Adding one.", url)
browser
.click({
locateStrategy: 'css selector',
selector: '[data-id="basic-http-provider-modal-footer-ok-react"]',
abortOnFailure: false,
suppressNotFoundErrors: true,
timeout: 5000
})
.switchEnvironment('External Http Provider')
.waitForElementPresent('[data-id="basic-http-provider-modal-footer-ok-react"]')
.execute(() => {
(document.querySelector('*[data-id="basic-http-providerModalDialogContainer-react"] input[data-id="modalDialogCustomPromp"]') as any).focus()
}, [], () => { })
.setValue('[data-id="modalDialogCustomPromp"]', url)
.modalFooterOKClick('basic-http-provider')
.perform((done) => {
done()
this.emit('complete')
})
} else {
this.api.perform((done) => {
done()
this.emit('complete')
})
}
}
)
return this
}
}
module.exports = ConnectToExternalHttpProvider

@ -10,7 +10,9 @@ class GoToVmTraceStep extends EventEmitter {
}
function goToVMtraceStep (browser: NightwatchBrowser, step: number, incr: number, done: VoidFunction) {
browser.execute(function (step) { (document.getElementById('slider') as HTMLInputElement).value = (step - 1).toString() }, [step])
browser.waitForElementVisible('*[data-id="slider"]')
.waitForElementVisible('#stepdetail')
.execute(function (step) { (document.getElementById('slider') as HTMLInputElement).value = (step - 1).toString() }, [step])
.setValue('*[data-id="slider"]', new Array(1).fill(browser.Keys.RIGHT_ARROW))
.execute((step) => {
(document.querySelector('*[data-id="slider"]') as any).internal_onmouseup({ target: { value: step }})

@ -3,7 +3,7 @@ import EventEmitter from 'events'
class switchEnvironment extends EventEmitter {
command (this: NightwatchBrowser, provider: string): NightwatchBrowser {
this.api.waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
this.api.useCss().waitForElementVisible('[data-id="settingsSelectEnvOptions"]')
.click('[data-id="settingsSelectEnvOptions"] button')
.waitForElementVisible(`[data-id="dropdown-item-${provider}"]`)
.click(`[data-id="dropdown-item-${provider}"]`)

@ -0,0 +1,173 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const testContract = {
name: 'contracts/test.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/base.sol";
import "contracts/import1.sol";
contract test is base {
string public publicstring;
string private privatestring;
string internal internalstring;
struct TestBookDefinition {
string title;
string author;
uint book_id;
}
TestBookDefinition public mybook;
enum MyEnum{ SMALL, MEDIUM, LARGE }
event MyEvent(uint abc);
importcontract importedcontract;
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
constructor(){
}
function testing() public view {
}
function myprivatefunction() private {
}
function myinternalfunction() internal {
}
function myexternalfunction() external {
}
}`}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const baseContract = {
name: 'contracts/base.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "contracts/baseofbase.sol";
contract base is baseofbase {
event BaseEvent(address indexed _from, uint _value);
enum BaseEnum{ SMALL, MEDIUM, LARGE }
struct Book {
string title;
string author;
uint book_id;
}
Book public book;
}`}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const baseOfBaseContract = {
name: 'contracts/baseofbase.sol',
content: `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract baseofbase {
struct BaseBook {
string title;
string author;
uint book_id;
}
BaseBook public basebook;
string private basestring;
string internal internalbasestring;
function privatebase() private {
}
function internalbasefunction() internal {
}
function publicbasefunction() public {
}
function externalbasefunction() external {
}
}`}
const import1Contract = {
name: 'contracts/import1.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "contracts/importbase.sol";
import "contracts/secondimport.sol";
contract importcontract is importbase {
struct ImportedBook {
string title;
string author;
uint book_id;
}
ImportedBook public importedbook;
string private importprivatestring;
string internal internalimportstring;
string public importpublicstring;
function privateimport() private {
}
function internalimport() internal {
}
function publicimport() public {
}
function externalimport() external {
}
}`}
const importbase = {
name: 'contracts/importbase.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract importbase {
string public importbasestring;
}
`}
const secondimport = {
name: 'contracts/secondimport.sol',
content: `
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract secondimport {
string public secondimportstring;
}
`}
export default {
testContract,
baseContract,
baseOfBaseContract,
import1Contract,
importbase,
secondimport
}

@ -205,6 +205,7 @@ module.exports = {
.clickLaunchIcon('debugger')
.waitForElementVisible('*[data-id="slider"]')
.goToVMTraceStep(154)
.scrollInto('*[data-id="stepdetail"]')
.waitForElementContainsText('*[data-id="stepdetail"]', 'vm trace step:\n154', 60000)
},
@ -240,6 +241,7 @@ module.exports = {
.clickInstance(0)
.clickFunction('callA - transact (not payable)')
.debugTransaction(1)
.pause(4000)
.goToVMTraceStep(79)
.waitForElementVisible('*[data-id="debugGoToRevert"]', 60000)
.click('*[data-id="debugGoToRevert"]')

@ -128,119 +128,6 @@ module.exports = {
.waitForElementNotPresent('.highlightLine51', 60000)
},
'Should display the context view #group2': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError)
.pause(2000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(17, 16)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'FunctionDefinition')
.waitForElementContainsText('.contextview .name', 'store')
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(18, 12)
}, [], () => {})
.waitForElementContainsText('.contextview .type', 'uint256')
.waitForElementContainsText('.contextview .name', 'number')
.click('.contextview [data-action="previous"]') // declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
.click('.contextview [data-action="next"]') // back to the initial state
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '323')
})
.click('.contextview [data-action="next"]') // next reference
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '489')
})
.click('.contextview [data-action="gotoref"]') // back to the declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
},
'Should display the context view, loop over "Owner" by switching file #group2': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.click('[for="autoCompile"]') // disable auto compile
.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.clickLaunchIcon('solidity')
.click('*[data-id="compilerContainerCompileBtn"]') // compile
.pause(6000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(14, 6)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'ContractDefinition')
.waitForElementContainsText('.contextview .name', 'Owner')
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '1061')
})
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '153')
})
.currentSelectedFileIs('2_Owner.sol') // make sure the current file has been properly changed
.click('.contextview [data-action="next"]')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '211')
})
.click('.contextview [data-action="next"]')
.currentSelectedFileIs('3_Ballot.sol')
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '1061')
})
.click('.contextview [data-action="gotoref"]') // go to the declaration
.pause(1000)
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '153')
})
.end()
}
}
const aceThemes = {
@ -347,148 +234,4 @@ contract Storage {
}
}`
const BallotWithARefToOwner = `
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "./2_Owner.sol";
/**
* @title Ballot
* @dev Implements voting process along with vote delegation
*/
contract Ballot {
Owner c;
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
struct Proposal {
// If you can limit the length to a certain number of bytes,
// always use one of bytes1 to bytes32 because they are much cheaper
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
c = new Owner();
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 'Proposal({...})' creates a temporary
// Proposal object and 'proposals.push(...)'
// appends it to the end of 'proposals'.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'.
* @param voter address of voter
*/
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/**
* @dev Delegate your vote to the voter 'to'.
* @param to address to which vote is delegated
*/
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/**
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'.
* @param proposal index of proposal in the proposals array
*/
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If 'proposal' is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/**
* @dev Computes the winning proposal taking all previous votes into account.
* @return winningProposal_ index of winning proposal in the proposals array
*/
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
/**
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then
* @return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
`

@ -0,0 +1,526 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
import examples from '../examples/editor-test-contracts'
const autoCompleteLineElement = (name: string) => {
return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]`
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should add test and base files #group1': function (browser: NightwatchBrowser) {
browser.addFile(examples.testContract.name, examples.testContract)
.addFile(examples.baseContract.name, examples.baseContract)
.addFile(examples.import1Contract.name, examples.import1Contract)
.addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract)
.addFile(examples.secondimport.name, examples.secondimport)
.addFile(examples.importbase.name, examples.importbase)
.openFile(examples.testContract.name)
},
'Should put cursor in the () of the function #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(36)
const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]"
browser.waitForElementVisible('#editorView')
.useXpath()
.click(path).pause(1000)
},
'Should complete variable declaration types in a function definition #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('uint25')
})
.waitForElementPresent(autoCompleteLineElement('uint256'))
.click(autoCompleteLineElement('uint256'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' abc')
.sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow
.sendKeys(', testb')
})
.waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"'))
.click(autoCompleteLineElement('"TestBookDefinition"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' memo')
})
.waitForElementPresent(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('memory'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' btextbook')
.sendKeys(this.Keys.ENTER)
.sendKeys(', BaseB')
})
.waitForElementPresent(autoCompleteLineElement('"BaseBook"'))
.click(autoCompleteLineElement('"BaseBook"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' stor')
})
.waitForElementPresent(autoCompleteLineElement('storage'))
.click(autoCompleteLineElement('storage'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' localbbook')
}).pause(3000)
},
'Should put cursor at the end of function #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]"
browser
.useXpath()
.click(path).pause(1000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
// right arrow key
sendKeys(this.Keys.ARROW_RIGHT).
sendKeys(this.Keys.ARROW_RIGHT)
})
},
'Should autcomplete address types #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('addre')
})
.waitForElementPresent(autoCompleteLineElement('address'))
.click(autoCompleteLineElement('address'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' someaddress;')
.sendKeys(this.Keys.ENTER)
}).pause(2000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('someaddress.')
})
.waitForElementVisible(autoCompleteLineElement('balance'))
.waitForElementVisible(autoCompleteLineElement('send'))
.waitForElementVisible(autoCompleteLineElement('transfer'))
.waitForElementVisible(autoCompleteLineElement('code'))
.click(autoCompleteLineElement('balance'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should autcomplete array types #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('uin')
})
.waitForElementPresent(autoCompleteLineElement('uint'))
.click(autoCompleteLineElement('uint'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('[] mem')
})
.waitForElementVisible(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('memory'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' somearray;')
}
).pause(2000)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
.sendKeys('somearray.')
})
.waitForElementVisible(autoCompleteLineElement('push'))
.waitForElementVisible(autoCompleteLineElement('pop'))
.waitForElementVisible(autoCompleteLineElement('length'))
.click(autoCompleteLineElement('length'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should see and autocomplete second import because it was imported by the first import #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('secondi')
})
.waitForElementPresent(autoCompleteLineElement('secondimport'))
.click(autoCompleteLineElement('secondimport'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(' sec;')
.sendKeys(this.Keys.ENTER)
}).pause(3000)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('sec.')
})
.waitForElementVisible(autoCompleteLineElement('secondimportstring'))
.click(autoCompleteLineElement('secondimportstring'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should see and autocomplete imported local class #group1': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('import')
})
.waitForElementPresent(autoCompleteLineElement('importedcontract'))
.click(autoCompleteLineElement('importedcontract'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('externalimport'))
.waitForElementVisible(autoCompleteLineElement('importbasestring'))
.waitForElementVisible(autoCompleteLineElement('importedbook'))
.waitForElementVisible(autoCompleteLineElement('importpublicstring'))
.waitForElementVisible(autoCompleteLineElement('publicimport'))
// no private
.waitForElementNotPresent(autoCompleteLineElement('importprivatestring'))
.waitForElementNotPresent(autoCompleteLineElement('privateimport'))
// no internal
.waitForElementNotPresent(autoCompleteLineElement('importinternalstring'))
.waitForElementNotPresent(autoCompleteLineElement('internalimport'))
.click(autoCompleteLineElement('importbasestring'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should autocomplete derived and local event when not using this. #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('emit base')
})
.waitForElementVisible(autoCompleteLineElement('BaseEvent'))
.click(autoCompleteLineElement('BaseEvent'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys('msg.sender')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium
.sendKeys('3232')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.ENTER)
})
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('emit MyEv')
})
.waitForElementVisible(autoCompleteLineElement('MyEvent'))
.click(autoCompleteLineElement('MyEvent'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys('3232')
.sendKeys(this.Keys.TAB)
.sendKeys(this.Keys.ENTER)
})
},
'Should type and get msg options #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('msg.')
})
.waitForElementVisible(autoCompleteLineElement('sender'))
.waitForElementVisible(autoCompleteLineElement('data'))
.waitForElementVisible(autoCompleteLineElement('value'))
.waitForElementVisible(autoCompleteLineElement('gas'))
.waitForElementVisible(autoCompleteLineElement('sig'))
.click(autoCompleteLineElement('sender'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('balance'))
.waitForElementVisible(autoCompleteLineElement('code'))
.waitForElementVisible(autoCompleteLineElement('codehash'))
.waitForElementVisible(autoCompleteLineElement('send'))
.waitForElementVisible(autoCompleteLineElement('transfer'))
.click(autoCompleteLineElement('balance'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER)
})
},
'Should bo and get book #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('bo')
})
.waitForElementVisible(autoCompleteLineElement('book'))
.click(autoCompleteLineElement('book'))
},
'Should autcomplete derived struct #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should bo and get basebook #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('base')
})
.waitForElementVisible(autoCompleteLineElement('basebook'))
.click(autoCompleteLineElement('basebook'))
},
'Should autcomplete derived struct from base class #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should block scoped localbbook #group1': function (browser: NightwatchBrowser) {
browser.pause(4000).
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('localb')
})
.waitForElementVisible(autoCompleteLineElement('localbbook'))
.click(autoCompleteLineElement('localbbook'))
},
'Should autcomplete derived struct from block localbbook #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should block scoped btextbook #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('btext')
})
.waitForElementVisible(autoCompleteLineElement('btextbook'))
.click(autoCompleteLineElement('btextbook'))
},
'Should autcomplete derived struct from block btextbook #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('.')
})
.waitForElementVisible(autoCompleteLineElement('author'))
.waitForElementVisible(autoCompleteLineElement('book_id'))
.waitForElementVisible(autoCompleteLineElement('title'))
.click(autoCompleteLineElement('title'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
},
'Should find private and internal local functions #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('my')
})
.waitForElementVisible(autoCompleteLineElement('myprivatefunction'))
.waitForElementVisible(autoCompleteLineElement('myinternalfunction'))
.waitForElementVisible(autoCompleteLineElement('memory'))
.click(autoCompleteLineElement('myinternalfunction'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER)
})
},
'Should find internal functions and var from base and owner #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('intern')
})
.waitForElementVisible(autoCompleteLineElement('internalbasefunction'))
.waitForElementVisible(autoCompleteLineElement('internalstring'))
.waitForElementVisible(autoCompleteLineElement('internalbasestring'))
// keyword internal
.waitForElementVisible(autoCompleteLineElement('internal keyword'))
.click(autoCompleteLineElement('internalbasefunction'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should not find external functions without this. #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('extern')
})
.waitForElementNotPresent(autoCompleteLineElement('externalbasefunction'))
.waitForElementNotPresent(autoCompleteLineElement('myexternalfunction'))
// keyword internal
.waitForElementVisible(autoCompleteLineElement('external keyword'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
.sendKeys(this.Keys.BACK_SPACE)
})
},
'Should find external functions using this. #group1': function (browser: NightwatchBrowser) {
browser.
perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(this.Keys.ENTER).
sendKeys('this.')
})
.waitForElementVisible(autoCompleteLineElement('externalbasefunction'))
.waitForElementVisible(autoCompleteLineElement('myexternalfunction'))
},
'Should find public functions and vars using this. but not private & other types of nodes #group1': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible(autoCompleteLineElement('"publicbasefunction"'))
.waitForElementVisible(autoCompleteLineElement('"publicstring"'))
.waitForElementVisible(autoCompleteLineElement('"basebook"'))
.waitForElementVisible(autoCompleteLineElement('"mybook"'))
.waitForElementVisible(autoCompleteLineElement('"testing"'))
// but no private functions or vars or other types of nodes
.waitForElementNotPresent(autoCompleteLineElement('"private"'))
.waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"'))
.waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"'))
.waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"'))
.click(autoCompleteLineElement('"publicbasefunction"'))
.perform(function () {
const actions = this.actions({ async: true });
return actions
.sendKeys(this.Keys.ENTER)
})
},
'Should autocomplete local and derived ENUMS #group1': function (browser: NightwatchBrowser) {
browser.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys('BaseEnum.')
})
.waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
.sendKeys('MyEnum.')
})
.waitForElementVisible(autoCompleteLineElement('SMALL'))
.waitForElementVisible(autoCompleteLineElement('MEDIUM'))
.waitForElementVisible(autoCompleteLineElement('LARGE'))
.click(autoCompleteLineElement('SMALL'))
.perform(function () {
const actions = this.actions({ async: true });
return actions.
sendKeys(';')
.sendKeys(this.Keys.ENTER)
})
}
}

@ -0,0 +1,244 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const checkEditorHoverContent = (browser: NightwatchBrowser, path: string, expectedContent: string, offsetLeft: number = 0) => {
browser.useXpath()
.useXpath()
.moveToElement('//body', 0, 0) // always move away from the hover before the next test in case we hover in the same line on a different element
.waitForElementVisible(path)
.moveToElement(path, offsetLeft, 0)
.useCss()
.waitForElementContainsText('.monaco-hover-content', expectedContent).pause(1000)
}
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(4000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show hover over contract in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[contains(text(),'BallotHoverTest')]"
checkEditorHoverContent(browser, path, 'contract BallotHoverTest is BallotHoverTest')
checkEditorHoverContent(browser, path, 'contracts/3_Ballot.sol 10:0')
checkEditorHoverContent(browser, path, '@title Ballot')
},
'Should show hover over var definition in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'chairperson') and contains(.,'address') and contains(.,'public')]//span//span[contains(.,'chairperson')]"
const expectedContent = 'address public chairperson'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over constructor in editor #group1': function (browser: NightwatchBrowser) {
const path: string = "//*[@class='view-line' and contains(.,'constructor') and contains(.,'bytes32') and contains(.,'memory')]//span//span[contains(.,'constructor')]"
const expectedContent = 'Estimated creation cost: infinite gas Estimated code deposit cost:'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over function in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(58)
const path: string = "//*[@class='view-line' and contains(.,'giveRightToVote(address') and contains(.,'function') and contains(.,'public')]//span//span[contains(.,'giveRightToVote')]"
let expectedContent = 'Estimated execution cost'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = 'function giveRightToVote (address internal voter) public nonpayable returns ()'
checkEditorHoverContent(browser, path, expectedContent)
expectedContent = "@dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'"
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over var components in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(37)
let path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'voters')]"
let expectedContent = 'mapping(address => struct BallotHoverTest.Voter) public voters'
checkEditorHoverContent(browser, path, expectedContent, 15)
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'chairperson')]"
expectedContent = 'address public chairperson'
checkEditorHoverContent(browser, path, expectedContent, 3)
path = "//*[@class='view-line' and contains(.,'voters') and contains(.,'weight')]//span//span[contains(.,'weight')]"
expectedContent = 'uint256 internal weight'
checkEditorHoverContent(browser, path, expectedContent)
},
'Should show hover over new contract creation in editor #group1': function (browser: NightwatchBrowser) {
let path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'cowner')]"
let expectedContent = 'contract Owner internal cowner'
checkEditorHoverContent(browser, path, expectedContent, 10)
path = "//*[@class='view-line' and contains(.,'Owner') and contains(.,'new')]//span//span[contains(.,'Owner')]"
expectedContent = 'contract Owner is Owner'
checkEditorHoverContent(browser, path, expectedContent, 10)
},
'Should show hover over external class member in editor #group1': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
let expectedContent = 'function getOwner () external view returns (address internal )'
checkEditorHoverContent(browser, path, expectedContent, 0)
expectedContent = 'contracts/2_Owner.sol'
checkEditorHoverContent(browser, path, expectedContent, 0)
expectedContent = '@dev Return owner address'
checkEditorHoverContent(browser, path, expectedContent, 0)
},
'Should show hover over struct definition in editor #group1': function (browser: NightwatchBrowser) {
browser.scrollToLine(5)
const path = "//*[@class='view-line' and contains(.,'Voter') and contains(.,'struct')]//span//span[contains(.,'Voter')]"
const expectedContent = 'StructDefinition'
checkEditorHoverContent(browser, path, expectedContent)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "./2_Owner.sol";
/**
* @title Ballot
* @dev Implements voting process along with vote delegation
*/
contract BallotHoverTest {
Owner cowner;
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
struct Proposal {
// If you can limit the length to a certain number of bytes,
// always use one of bytes1 to bytes32 because they are much cheaper
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
cowner = new Owner();
cowner.getOwner();
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 'Proposal({...})' creates a temporary
// Proposal object and 'proposals.push(...)'
// appends it to the end of 'proposals'.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'.
* @param voter address of voter
*/
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/**
* @dev Delegate your vote to the voter 'to'.
* @param to address to which vote is delegated
*/
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/**
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'.
* @param proposal index of proposal in the proposals array
*/
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If 'proposal' is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/**
* @dev Computes the winning proposal taking all previous votes into account.
* @return winningProposal_ index of winning proposal in the proposals array
*/
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
/**
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then
* @return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
`

@ -0,0 +1,203 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const openReferences = (browser: NightwatchBrowser, path: string) => {
(browser as any).useXpath()
.useXpath()
.waitForElementVisible(path)
.click(path)
.perform(function () {
const actions = this.actions({ async: true });
return actions.
keyDown(this.Keys.SHIFT).
sendKeys(this.Keys.F12)
})
}
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', false)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should load the test file': function (browser: NightwatchBrowser) {
browser.openFile('contracts')
.openFile('contracts/3_Ballot.sol')
.waitForElementVisible('#editorView')
.setEditorValue(BallotWithARefToOwner)
.pause(10000) // wait for the compiler to finish
.scrollToLine(37)
},
'Should show local references': function (browser: NightwatchBrowser) {
browser.scrollToLine(48)
const path = "//*[@class='view-line' and contains(.,'length') and contains(.,'proposalNames')]//span//span[contains(.,'proposalNames')]"
openReferences(browser, path)
browser.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'length; i++')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'name:')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'constructor')]")
.keys(browser.Keys.ESCAPE)
},
'Should show references of getOwner': function (browser: NightwatchBrowser) {
browser.scrollToLine(39)
const path = "//*[@class='view-line' and contains(.,'getOwner') and contains(.,'cowner')]//span//span[contains(.,'getOwner')]"
openReferences(browser, path)
browser.useXpath()
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'2_Owner.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label']//span[contains(.,'3_Ballot.sol')]")
.waitForElementVisible("//*[@class='monaco-highlighted-label referenceMatch']//span[contains(.,'cowner.getOwner')]")
.waitForElementVisible("//*[contains(@class, 'results-loaded') and contains(., 'References (2)')]")
.keys(browser.Keys.ESCAPE)
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const BallotWithARefToOwner = `// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "./2_Owner.sol";
/**
* @title Ballot
* @dev Implements voting process along with vote delegation
*/
contract BallotHoverTest {
Owner cowner;
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
struct Proposal {
// If you can limit the length to a certain number of bytes,
// always use one of bytes1 to bytes32 because they are much cheaper
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
mapping(address => Voter) public voters;
Proposal[] public proposals;
/**
* @dev Create a new ballot to choose one of 'proposalNames'.
* @param proposalNames names of proposals
*/
constructor(bytes32[] memory proposalNames) {
cowner = new Owner();
cowner.getOwner();
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
// 'Proposal({...})' creates a temporary
// Proposal object and 'proposals.push(...)'
// appends it to the end of 'proposals'.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
/**
* @dev Give 'voter' the right to vote on this ballot. May only be called by 'chairperson'.
* @param voter address of voter
*/
function giveRightToVote(address voter) public {
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/**
* @dev Delegate your vote to the voter 'to'.
* @param to address to which vote is delegated
*/
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/**
* @dev Give your vote (including votes delegated to you) to proposal 'proposals[proposal].name'.
* @param proposal index of proposal in the proposals array
*/
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If 'proposal' is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/**
* @dev Computes the winning proposal taking all previous votes into account.
* @return winningProposal_ index of winning proposal in the proposals array
*/
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
/**
* @dev Calls winningProposal() function to get the index of the winner contained in the proposals array and then
* @return winnerName_ the name of the winner
*/
function winnerName() public view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
`

@ -0,0 +1,91 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should add error marker': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.addFile('scripts/adderror.ts', {content: addErrorMarker})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementVisible("//*[@class='cdr squiggly-error']")
.waitForElementVisible("//*[@class='cdr squiggly-warning']")
},
'Should clear error marker': function (browser: NightwatchBrowser) {
browser
.useCss()
.addFile('scripts/clear.ts', {content: clearMarkers})
.pause(4000)
.executeScriptInTerminal('remix.exeCurrent()')
.pause(4000)
.openFile('contracts/1_Storage.sol')
.useXpath()
.waitForElementNotPresent("//*[@class='cdr squiggly-error']")
.waitForElementNotPresent("//*[@class='cdr squiggly-warning']")
}
}
const clearMarkers =`
(async () => {
await remix.call('editor', 'clearErrorMarkers' as any, ['contracts/1_Storage.sol'])
})()`
const addErrorMarker = `
(async () => {
let errors = [
{
position: {
start: {
line: 10,
column: 1,
},
end: {
line: 10,
column: 10
}
},
message: 'testing',
severity: 'error',
file: 'contracts/1_Storage.sol'
},
{
position: {
start: {
line: 18,
column: 1,
},
end: {
line: 18,
column: 10
}
},
message: 'testing2',
severity: 'warning',
file: 'contracts/1_Storage.sol'
},
]
await remix.call('editor', 'addErrorMarker' as any, errors)
})()`

@ -8,6 +8,13 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should enable settings': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('settings')
.click('[data-id="settingsAutoCompleteLabel"]')
.click('[data-id="settingsShowGasLabel"]')
.click('[data-id="displayErrorsLabel"]')
},
'Should add line texts': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')

@ -0,0 +1,45 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
const sources = []
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Deploy SampleERC721 whose bytecode is very similar to ERC721': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('filePanel')
.click('*[data-id="workspaceCreate"]')
// create contract
.waitForElementVisible('*[data-id="modalDialogCustomPromptTextCreate"]')
.waitForElementVisible('[data-id="fileSystemModalDialogModalFooter-react"] > button')
// eslint-disable-next-line dot-notation
.execute(function () { document.querySelector('*[data-id="modalDialogCustomPromptTextCreate"]')['value'] = 'workspace_erc721' })
.click('select[id="wstemplate"]')
.click('select[id="wstemplate"] option[value=ozerc721]')
.waitForElementPresent('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
.execute(function () { (document.querySelector('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok') as HTMLElement).click() })
.pause(100)
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts"]')
.waitForElementVisible('*[data-id="treeViewLitreeViewItemcontracts/SampleERC721.sol"]')
.openFile('contracts/SampleERC721.sol')
.verifyContracts(['SampleERC721'])
// deploy contract
.clickLaunchIcon('udapp')
.selectContract('SampleERC721')
.createContract('E,E')
.testFunction('last',
{
status: 'true Transaction mined and execution succeed',
'decoded input': {
'string tokenName': 'E',
'string tokenSymbol': 'E'
}
}).end()
}
}

@ -41,7 +41,7 @@ module.exports = {
.setValue('*[data-id="settingsTabGistAccessToken"]', '**********')
.click('*[data-id="settingsTabSaveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'GitHub credentials updated')
.assert.containsText('*[data-shared="tooltipPopup"]', 'Credentials updated')
.pause(3000)
},
@ -59,7 +59,7 @@ module.exports = {
.pause(1000)
.click('*[data-id="settingsTabRemoveGistToken"]')
.waitForElementVisible('*[data-shared="tooltipPopup"]', 5000)
.assert.containsText('*[data-shared="tooltipPopup"]', 'GitHub credentials removed')
.assert.containsText('*[data-shared="tooltipPopup"]', 'Credentials removed')
.assert.containsText('*[data-id="settingsTabGistAccessToken"]', '')
},

@ -9,10 +9,11 @@ const testData = {
// 99266d6da54cc12f37f11586e8171546c7700d67
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
UploadToGists: function (browser: NightwatchBrowser) {
'UploadToGists #group1': function (browser: NightwatchBrowser) {
/*
- set the access token
- publish to gist
@ -68,7 +69,7 @@ module.exports = {
*/
},
'Load Gist Modal': function (browser: NightwatchBrowser) {
'Load Gist Modal #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
@ -84,7 +85,7 @@ module.exports = {
.modalFooterCancelClick('gisthandler')
},
'Display Error Message For Invalid Gist ID': function (browser: NightwatchBrowser) {
'Display Error Message For Invalid Gist ID #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -101,7 +102,7 @@ module.exports = {
.modalFooterOKClick('gisthandler')
},
'Display Error Message For Missing Gist Token When Publishing': function (browser: NightwatchBrowser) {
'Display Error Message For Missing Gist Token When Publishing #group1': function (browser: NightwatchBrowser) {
browser
.pause(1000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
@ -125,9 +126,9 @@ module.exports = {
.click('[data-id="fileSystemModalDialogModalFooter-react"] .modal-ok')
},
'Import From Gist For Valid Gist ID': function (browser: NightwatchBrowser) {
'Import From Gist For Valid Gist ID #group2': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 15000)
.clickLaunchIcon('settings')
.click('*[data-id="settingsTabGenerateContractMetadataLabel"]')
.setValue('[data-id="settingsTabGistAccessToken"]', process.env.gist_token)
@ -140,6 +141,7 @@ module.exports = {
})
.setValue('*[data-id="gisthandlerModalDialogModalBody-react"] input[data-id="modalDialogCustomPromp"]', testData.validGistId)
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')

@ -295,7 +295,7 @@ module.exports = {
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
}, null, null)
}, null, '/')
},
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:getWorkspaces', [{name:"default_workspace",isGitRepo:false}, {name:"emptyworkspace",isGitRepo:false}, {name:"testspace",isGitRepo:false}], null, null)

@ -287,7 +287,6 @@ module.exports = {
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.waitForElementVisible('*[data-id="dropdownPanelSolidityLocals"]').pause(1000)
.click('*[data-id="dropdownPanelSolidityLocals"]')
.waitForElementContainsText('*[data-id="solidityLocals"]', 'no locals', 60000)
.goToVMTraceStep(316)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)

@ -129,15 +129,16 @@ module.exports = {
.pause(1000) // compile Storage
.executeScriptInTerminal('remix.execute(\'scripts/storage.test.js\')')
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Running tests....')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'RUNS scripts/script.ts....')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'storage contract Address:')
.waitForElementContainsText('*[data-id="terminalJournal"]', '✓ test initial value')
.waitForElementContainsText('*[data-id="terminalJournal"]', '✓ test updating and retrieving updated value')
.waitForElementContainsText('*[data-id="terminalJournal"]', '✘ fail test updating and retrieving updated value')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Expected: 55')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Actual: 56')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Received: 56')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Message: incorrect number: expected 56 to equal 55')
.waitForElementContainsText('*[data-id="terminalJournal"]', '2 passing, 1 failing')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Passed: 2')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Failed: 1')
},
'Run tests using Mocha for a contract with library deployment and check result logging in the terminal #group4': function (browser: NightwatchBrowser) {
browser
@ -151,14 +152,15 @@ module.exports = {
.pause(1000) // compile StorageWithLib
.executeScriptInTerminal('remix.execute(\'scripts/storageWithLib.test.js\')')
.pause(1000)
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Running tests....')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Storage with lib')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'RUNS scripts/script.ts....')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Storage')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'deploying lib:')
.waitForElementContainsText('*[data-id="terminalJournal"]', '✘ test library integration by calling a lib method')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Expected: 34')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Actual: 14')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Received: 14')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Message: expected \'14\' to equal \'34\'')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0 passing, 1 failing')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Passed: 0')
.waitForElementContainsText('*[data-id="terminalJournal"]', 'Failed: 1')
},
'Should print hardhat logs #group4': function (browser: NightwatchBrowser) {
browser
@ -217,15 +219,74 @@ module.exports = {
.addFile('scripts/log_tx_block.js', { content: scriptBlockAndTransaction })
.pause(1000)
.executeScriptInTerminal('remix.execute(\'scripts/log_tx_block.js\')')
.pause(10000)
// check if the input of the transaction is being logged (web3 call)
.journalChildIncludes('0x775526410000000000000000000000000000000000000000000000000000000000000060464c0335b2f1609abd9de25141c0a3b49db516fc7375970dc737c32b986e88e3000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000000602926b30b10e7a514d92bc71e085f5bff2687fac2856ae43ef7621bf1756fa370516d310bec5727543089be9a4d5f68471174ee528e95a2520b0ca36c2b6c6eb0000000000000000000000000000000000000000000000000000000000046f49036f5e4ea4dd042801c8841e3db8e654124305da0f11824fc1db60c405dbb39f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x775526410000000000000000000000000000000000000000000000000000000000000060464c0335b2f1609abd9de25141c0a3b49db516fc7375970dc737c32b986e88e3000000000000000000000000000000000000000000000000000000000000039e000000000000000000000000000000000000000000000000000000000000000602926b30b10e7a514d92bc71e085f5bff2687fac2856ae43ef7621bf1756fa370516d310bec5727543089be9a4d5f68471174ee528e95a2520b0ca36c2b6c6eb0000000000000000000000000000000000000000000000000000000000046f49036f5e4ea4dd042801c8841e3db8e654124305da0f11824fc1db60c405dbb39f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 120000)
// check if the logsBloom is being logged (web3 call)
.journalChildIncludes('0x00000000000000000000000000100000000000000000020000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000040000000060000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000100000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001')
.waitForElementContainsText('*[data-id="terminalJournal"]', '0x00000000000000000000000000100000000000000000020000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000040000000060000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000100000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001', 120000)
// check if the logsBloom is being logged (ethers.js call)
.journalChildIncludes('"hex":"0x025cd8"')
.waitForElementContainsText('*[data-id="terminalJournal"]', '"hex":"0x025cd8"', 120000)
},
'Should listen on all transactions #group8': function (browser: NightwatchBrowser) {
const url = 'http://127.0.0.1:8545'
const identifier = 'Custom'
browser
.clickLaunchIcon('udapp') // connect to mainnet
.connectToExternalHttpProvider(url, identifier)
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.clickLaunchIcon('solidity')
.click({
selector: '*[data-id="compilerContainerCompileAndRunBtn"]',
})
.pause(10000)
.waitForElementNotPresent({
locateStrategy: 'xpath',
selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]",
timeout: 120000
})
.click({
selector: '[data-id="listenNetworkCheckInput"]',
}) // start to listen
.click({
selector: '*[data-id="compilerContainerCompileAndRunBtn"]',
})
.pause(10000)
.findElements(
{
locateStrategy: 'xpath',
selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]",
timeout: 120000,
}
, async (result) => {
if (Array.isArray(result.value) && result.value.length > 0) {
console.log('Found ' + result.value.length + ' transactions')
browser
.click({
selector: '[data-id="listenNetworkCheckInput"]',
})
.click({
selector: '*[data-id="terminalClearConsole"]',
})
.click({
selector: '*[data-id="compilerContainerCompileAndRunBtn"]',
})
.pause(10000)
.waitForElementNotPresent({
locateStrategy: 'xpath',
selector: "//*[@class='remix_ui_terminal_log' and contains(.,'to:') and contains(.,'from:')]",
timeout: 120000
})
.end()
} else {
browser
.assert.fail('No transaction found')
.end()
}
})
}
}
const asyncAwait = `
var p = function () {

@ -85,7 +85,7 @@ module.exports = {
})
},
'Should load Etherscan verified contractss from URL "address" param) #group2': function (browser: NightwatchBrowser) {
'Should load Etherscan verified contracts from URL "address" param)': !function (browser: NightwatchBrowser) {
browser
.pause(5000)
.url('http://127.0.0.1:8080/#address=0x56db08fb78bc6689a1ef66efd079083fed0e4915')
@ -248,7 +248,7 @@ module.exports = {
.openFile('contracts/governance/UnionGovernor.sol')
},
'Should execute function call from URL parameters #group2': function (browser: NightwatchBrowser) {
'Should execute function call from URL parameters #group1': function (browser: NightwatchBrowser) {
browser
.switchWorkspace('default_workspace')
.url('http://127.0.0.1:8080?calls=fileManager//open//contracts/3_Ballot.sol///terminal//log//log')

@ -21,25 +21,30 @@ module.exports = {
.frame(0)
},
'Should add the Ballot.vy #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-ballot"]')
'Should clone the Vyper repo #group1': function (browser: NightwatchBrowser) {
browser.click('button[data-id="add-repository"]')
.frameParent()
.openFile('ballot.vy')
.waitForElementContainsText('*[data-shared="tooltipPopup"]', 'Vyper repository cloned', 30000)
.openFile('examples')
.openFile('examples/auctions')
.openFile('examples/auctions/blind_auction.vy')
},
'Compile ballot.vy should error #group1': function (browser: NightwatchBrowser) {
'Compile blind_auction should success #group1': function (browser: NightwatchBrowser) {
browser.clickLaunchIcon('vyper')
// @ts-ignore
.frame(0)
.click('[data-id="remote-compiler"]')
.click('[data-id="compile"]')
.assert.containsText('[data-id="error-message"]', 'unexpected indent')
.waitForElementVisible('[data-id="copy-abi"]')
},
'Compile test contract should success #group1': function (browser: NightwatchBrowser) {
'Compile test contract and deploy to remix VM #group1': function (browser: NightwatchBrowser) {
let contractAddress
browser
.frameParent()
.clickLaunchIcon('filePanel')
.switchWorkspace('default_workspace')
.addFile('test.vy', { content: testContract })
.clickLaunchIcon('vyper')
// @ts-ignore

@ -65,6 +65,7 @@ declare module 'nightwatch' {
currentSelectedFileIs (name: string): NightwatchBrowser,
switchWorkspace: (workspaceName: string) => NightwatchBrowser
switchEnvironment: (provider: string) => NightwatchBrowser
connectToExternalHttpProvider: (url: string, identifier: string) => NightwatchBrowser
}
export interface NightwatchBrowser {

@ -14,7 +14,7 @@ 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 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js *raw-loader*.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore

@ -14,7 +14,7 @@ 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 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js raw-loader*.js"
FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js favicon.ico vendors~app*.js app*.js *raw-loader*.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore

@ -14,7 +14,7 @@ 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 404.html main*.js polyfills*.js vendors~app*.js app*.js raw-loader*.js"
FILES_TO_DEPLOY="assets index.html 404.html main*.js polyfills*.js vendors~app*.js app*.js *raw-loader*.js"
# ZIP the whole directory
zip -r remix-$SHA.zip $FILES_TO_DEPLOY
# -f is needed because "build" is part of .gitignore

@ -15,10 +15,11 @@ import { MainPanel } from './app/components/main-panel'
import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin'
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser'
import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener, GistHandler } from '@remix-project/core-plugin'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, GistHandler } from '@remix-project/core-plugin'
import Registry from './app/state/registry'
import { ConfigPlugin } from './app/plugins/config'
@ -216,7 +217,9 @@ class AppComponent {
}
}
)
const contextualListener = new EditorContextListener(new AstWalker())
const codeParser = new CodeParser(new AstWalker())
this.notification = new NotificationPlugin()
@ -241,14 +244,14 @@ class AppComponent {
compilersArtefacts,
networkModule,
offsetToLineColumnConverter,
contextualListener,
codeParser,
fileDecorator,
terminal,
web3Provider,
compileAndRun,
fetchAndCompile,
dGitProvider,
storagePlugin,
fileDecorator,
hardhatProvider,
ganacheProvider,
foundryProvider,
@ -356,10 +359,6 @@ class AppComponent {
const queryParams = new QueryParams()
const params = queryParams.get()
if (isElectron()) {
this.appManager.activatePlugin('remixd')
}
try {
this.engine.register(await this.appManager.registeredPlugins())
} catch (e) {
@ -373,7 +372,7 @@ class AppComponent {
await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately
await this.appManager.activatePlugin(['home'])
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'fileDecorator', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder'])

@ -12,7 +12,7 @@ const sidePanel = {
displayName: 'Side Panel',
description: 'Remix IDE side panel',
version: packageJson.version,
methods: ['addView', 'removeView']
methods: ['addView', 'removeView', 'currentFocus']
}
export class SidePanel extends AbstractPanel {

@ -13,7 +13,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition']
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open', 'addModel','addErrorMarker', 'clearErrorMarkers'],
}
class Editor extends Plugin {
@ -145,54 +145,6 @@ class Editor extends Plugin {
this.currentThemeType = theme.quality
this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
})
this.on('fileManager', 'currentFileChanged', async (name) => {
if (name.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
let content = await this.call('fileManager', 'readFile', name)
const paths = name.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let path = match[2]
if (path.startsWith('./') || path.startsWith('../')) path = resolve(fromPath, path)
if (path.startsWith('/')) path = path.substring(1)
if (!path.endsWith('.ts')) path = path + '.ts'
if (await this.call('fileManager', 'exists', path)) {
content = await this.call('fileManager', 'readFile', path)
this.emit('addModel', content, 'typescript', path, false)
}
}
}
})
this.on('fileManager', 'noFileSelected', async () => {
this.currentFile = null
this.renderComponent()
@ -211,6 +163,7 @@ class Editor extends Plugin {
}
async _onChange (file) {
this.triggerEvent('didChangeFile', [file])
const currentFile = await this.call('fileManager', 'file')
if (!currentFile) {
return
@ -260,14 +213,43 @@ class Editor extends Plugin {
return ext && this.modes[ext] ? this.modes[ext] : this.modes.txt
}
async handleTypeScriptDependenciesOf (path, content, readFile) {
if (path.endsWith('.ts')) {
// extract the import, resolve their content
// and add the imported files to Monaco through the `addModel`
// so Monaco can provide auto completion
const paths = path.split('/')
paths.pop()
const fromPath = paths.join('/') // get current execution context path
for (const match of content.matchAll(/import\s+.*\s+from\s+(?:"(.*?)"|'(.*?)')/g)) {
let pathDep = match[2]
if (pathDep.startsWith('./') || pathDep.startsWith('../')) pathDep = resolve(fromPath, pathDep)
if (pathDep.startsWith('/')) pathDep = pathDep.substring(1)
if (!pathDep.endsWith('.ts')) pathDep = pathDep + '.ts'
try {
// we can't use the fileManager plugin call directly
// because it's itself called in a plugin context, and that causes a timeout in the plugin stack
const contentDep = await readFile(pathDep)
if (contentDep !== null) {
this.emit('addModel', contentDep, 'typescript', pathDep, false)
}
} catch (e) {
console.log(e)
}
}
}
}
/**
* Create an editor session
* @param {string} path path of the file
* @param {string} content Content of the file to open
* @param {string} mode Mode for this file [Default is `text`]
*/
_createSession (path, content, mode) {
async _createSession (path, content, mode) {
if (!this.activated) return
this.emit('addModel', content, mode, path, false)
return {
path,
@ -292,6 +274,10 @@ class Editor extends Plugin {
return this.api.findMatches(this.currentFile, string)
}
addModel(path, content) {
this.emit('addModel', content, this._getMode(path), path, false)
}
/**
* Display an Empty read-only session
*/
@ -316,7 +302,7 @@ class Editor extends Plugin {
* @param {string} path Path of the session to open.
* @param {string} content Content of the document or update.
*/
open (path, content) {
async open (path, content) {
/*
we have the following cases:
- URL prepended with "localhost"
@ -324,7 +310,7 @@ class Editor extends Plugin {
- URL not prepended with the file explorer. We assume (as it is in the whole app, that this is a "browser" URL
*/
if (!this.sessions[path]) {
const session = this._createSession(path, content, this._getMode(path))
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = false
} else if (this.sessions[path].getValue() !== content) {
@ -338,9 +324,9 @@ class Editor extends Plugin {
* @param {string} path Path of the session to open.
* @param {string} content Content of the document or update.
*/
openReadOnly (path, content) {
async openReadOnly (path, content) {
if (!this.sessions[path]) {
const session = this._createSession(path, content, this._getMode(path))
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = true
}
@ -504,6 +490,17 @@ class Editor extends Plugin {
}
}
// error markers
async addErrorMarker (error){
const { from } = this.currentRequest
this.api.addErrorMarker(error, from)
}
async clearErrorMarkers(sources){
const { from } = this.currentRequest
this.api.clearErrorMarkers(sources, from)
}
/**
* Clears all the annotations for the given @arg filePath, the plugin name is retrieved from the context, if none is given, the current sesssion is used.
* An annotation has the following shape:

@ -28,10 +28,10 @@ class DGitProvider extends Plugin {
constructor () {
super(profile)
this.ipfsconfig = {
host: 'ipfs.remixproject.org',
host: 'jqgt.remixproject.org',
port: 443,
protocol: 'https',
ipfsurl: 'https://ipfs.remixproject.org/ipfs/'
ipfsurl: 'https://jqgt.remixproject.org/ipfs/'
}
this.globalIPFSConfig = {
host: 'ipfs.io',
@ -40,10 +40,10 @@ class DGitProvider extends Plugin {
ipfsurl: 'https://ipfs.io/ipfs/'
}
this.remixIPFS = {
host: 'ipfs.remixproject.org',
host: 'jqgt.remixproject.org',
port: 443,
protocol: 'https',
ipfsurl: 'https://ipfs.remixproject.org/ipfs/'
ipfsurl: 'https://jqgt.remixproject.org/ipfs/'
}
this.ipfsSources = [this.remixIPFS, this.globalIPFSConfig, this.ipfsconfig]
}

@ -236,7 +236,7 @@ class FileManager extends Plugin {
* @param {string} dest path of the destrination file
* @returns {void}
*/
async copyFile(src, dest, customName) {
async copyFile(src: string, dest: string, customName?: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
@ -262,7 +262,7 @@ class FileManager extends Plugin {
* @param {string} dest path of the destination dir
* @returns {void}
*/
async copyDir(src, dest) {
async copyDir(src: string, dest: string, customName?: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
@ -277,16 +277,16 @@ class FileManager extends Plugin {
if (provider.isSubDirectory(src, dest)) {
this.call('notification', 'toast', recursivePasteToastMsg())
} else {
await this.inDepthCopy(src, dest)
await this.inDepthCopy(src, dest, customName)
}
} catch (e) {
throw new Error(e)
}
}
async inDepthCopy(src, dest, count = 0) {
async inDepthCopy(src: string, dest: string, customName?: string) {
const content = await this.readdir(src)
let copiedFolderPath = count === 0 ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src)
let copiedFolderPath = !customName ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src)
copiedFolderPath = await helper.createNonClashingDirNameAsync(copiedFolderPath, this)
await this.mkdir(copiedFolderPath)
@ -295,7 +295,7 @@ class FileManager extends Plugin {
if (!value.isDirectory) {
await this.copyFile(key, copiedFolderPath, helper.extractNameFromKey(key))
} else {
await this.inDepthCopy(key, copiedFolderPath, count + 1)
await this.inDepthCopy(key, copiedFolderPath, helper.extractNameFromKey(key))
}
}
}
@ -632,10 +632,17 @@ class FileManager extends Plugin {
console.log(error)
throw error
}
try {
// This make sure dependencies are loaded in the editor context.
// This ensure monaco is aware of deps artifacts, so it can provide basic features like "go to" symbols.
await this.editor.handleTypeScriptDependenciesOf(file, content, path => this.readFile(path))
} catch (e) {
console.log('unable to handle TypeScript dependencies of', file)
}
if (provider.isReadOnly(file)) {
this.editor.openReadOnly(file, content)
await this.editor.openReadOnly(file, content)
} else {
this.editor.open(file, content)
await this.editor.open(file, content)
}
// TODO: Only keep `this.emit` (issue#2210)
this.emit('currentFileChanged', file)
@ -817,6 +824,64 @@ class FileManager extends Plugin {
return exists
}
/**
* Moves a file to a new folder
* @param {string} src path of the source file
* @param {string} dest path of the destrination file
* @returns {void}
*/
async moveFile(src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsFile(src, `Cannot move ${src}. Path is not a file.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const fileName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + fileName)) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. File already exists at destination ${dest}`})
}
await this.copyFile(src, dest, fileName)
await this.remove(src)
} catch (e) {
throw new Error(e)
}
}
/**
* Moves a folder to a new folder
* @param {string} src path of the source folder
* @param {string} dest path of the destination folder
* @returns {void}
*/
async moveDir(src: string, dest: string) {
try {
src = this.normalize(src)
dest = this.normalize(dest)
src = this.limitPluginScope(src)
dest = this.limitPluginScope(dest)
await this._handleExists(src, `Cannot move ${src}. Path does not exist.`)
await this._handleExists(dest, `Cannot move content into ${dest}. Path does not exist.`)
await this._handleIsDir(src, `Cannot move ${src}. Path is not directory.`)
await this._handleIsDir(dest, `Cannot move content into ${dest}. Path is not directory.`)
const dirName = helper.extractNameFromKey(src)
if (await this.exists(dest + '/' + dirName) || src === dest) {
throw createError({ code: 'EEXIST', message: `Cannot move ${src}. Folder already exists at destination ${dest}`})
}
await this.copyDir(src, dest, dirName)
await this.remove(src)
} catch (e) {
throw new Error(e)
}
}
}
module.exports = FileManager

@ -127,7 +127,13 @@ class FileProvider {
async createDir (path, cb) {
const unprefixedpath = this.removePrefix(path)
const paths = unprefixedpath.split('/')
await this.forceCreateDir(unprefixedpath)
if (cb) cb()
}
async forceCreateDir (path) {
const paths = path.split('/')
if (paths.length && paths[0] === '') paths.shift()
let currentCheck = ''
for (const value of paths) {
@ -141,7 +147,6 @@ class FileProvider {
}
}
}
if (cb) cb()
}
// this will not add a folder as readonly but keep the original url to be able to restore it later

@ -2,7 +2,6 @@
const EventManager = require('events')
const FileProvider = require('./fileProvider')
const pathModule = require('path')
class WorkspaceFileProvider extends FileProvider {
constructor () {
@ -31,16 +30,10 @@ class WorkspaceFileProvider extends FileProvider {
}
removePrefix (path) {
if (!path) path = '/'
path = path.replace(/^\/|\/$/g, '') // remove first and last slash
path = path.replace(/^\.\/+/, '') // remove ./ from start of string
if (path.startsWith(this.workspacesPath + '/' + this.workspace)) return path
const splitPath = path.split('/')
if (splitPath[0] === this.workspace) {
splitPath[0] = this.workspacesPath + '/' + this.workspace
path = splitPath.join('/')
return path
}
path = super.removePrefix(path)
let ret = this.workspacesPath + '/' + this.workspace + '/' + (path === '/' ? '' : path)
@ -80,8 +73,10 @@ class WorkspaceFileProvider extends FileProvider {
async createWorkspace (name) {
try {
if (!name) name = 'default_workspace'
const path = this.workspacesPath + '/' + name
await super.forceCreateDir(path)
this.setWorkspace(name)
await super.createDir(name)
this.event.emit('createWorkspace', name)
} catch (e) {
throw new Error(e)

@ -6,7 +6,7 @@ import { QueryParams } from '@remix-project/remix-lib'
const profile: Profile = {
name: 'layout',
description: 'layout',
methods: ['minimize']
methods: ['minimize', 'maximiseSidePanel', 'resetSidePanel']
}
interface panelState {
@ -30,8 +30,10 @@ export type PanelConfiguration = {
export class Layout extends Plugin {
event: any
panels: panels
maximised: { [key: string]: boolean }
constructor () {
super(profile)
this.maximised = {}
this.event = new EventEmitter()
}
@ -64,6 +66,14 @@ export class Layout extends Plugin {
break
}
})
this.on('sidePanel', 'focusChanged', async (name) => {
const current = await this.call('sidePanel', 'currentFocus')
if (this.maximised[current]) {
this.event.emit('maximisesidepanel')
} else {
this.event.emit('resetsidepanel')
}
})
document.addEventListener('keypress', e => {
if (e.shiftKey && e.ctrlKey) {
if (e.code === 'KeyF') {
@ -92,4 +102,16 @@ export class Layout extends Plugin {
this.panels[name].minimized = minimized
this.event.emit('change', null)
}
async maximiseSidePanel () {
this.event.emit('maximisesidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = true
}
async resetSidePanel () {
this.event.emit('resetsidepanel')
const current = await this.call('sidePanel', 'currentFocus')
this.maximised[current] = false
}
}

@ -0,0 +1,653 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { CompilationResult } from '@remix-project/remix-solidity'
import CodeParserGasService from './services/code-parser-gas-service'
import CodeParserCompiler from './services/code-parser-compiler'
import CodeParserAntlrService from './services/code-parser-antlr-service'
import React from 'react'
import { Profile } from '@remixproject/plugin-utils'
import { ContractDefinitionAstNode, EventDefinitionAstNode, FunctionCallAstNode, FunctionDefinitionAstNode, IdentifierAstNode, ImportDirectiveAstNode, ModifierDefinitionAstNode, SourceUnitAstNode, StructDefinitionAstNode, VariableDeclarationAstNode } from 'dist/libs/remix-analyzer/src/types'
import { lastCompilationResult, RemixApi } from '@remixproject/plugin-api'
import { antlr } from './types'
import { ParseResult } from './types/antlr-types'
const profile: Profile = {
name: 'codeParser',
methods: ['nodesAtPosition', 'getContractNodes', 'getCurrentFileNodes', 'getLineColumnOfNode', 'getLineColumnOfPosition', 'getFunctionParamaters', 'getDeclaration', 'getFunctionReturnParameters', 'getVariableDeclaration', 'getNodeDocumentation', 'getNodeLink', 'listAstNodes', 'getANTLRBlockAtPosition', 'getLastNodeInLine', 'resolveImports', 'parseSolidity', 'getNodesWithScope', 'getNodesWithName', 'getNodes', 'compile', 'getNodeById', 'getLastCompilationResult', 'positionOfDefinition', 'definitionAtPosition', 'jumpToDefinition', 'referrencesAtPosition', 'referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf', 'getGasEstimates'],
events: [],
version: '0.0.1'
}
export function isNodeDefinition(node: genericASTNode) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
export type genericASTNode =
ContractDefinitionAstNode
| FunctionDefinitionAstNode
| ModifierDefinitionAstNode
| VariableDeclarationAstNode
| StructDefinitionAstNode
| EventDefinitionAstNode
| IdentifierAstNode
| FunctionCallAstNode
| ImportDirectiveAstNode
| SourceUnitAstNode
interface flatReferenceIndexNode {
[id: number]: genericASTNode
}
interface declarationIndexNode {
[id: number]: genericASTNode[]
}
interface codeParserIndex {
declarations: declarationIndexNode,
flatReferences: flatReferenceIndexNode,
nodesPerFile: any
}
export class CodeParser extends Plugin {
antlrParserResult: antlr.ParseResult // contains the simple parsed AST for the current file
compilerAbstract: CompilerAbstract
currentFile: string
nodeIndex: codeParserIndex
astWalker: any
errorState: boolean = false
gastEstimateTimeOut: any
gasService: CodeParserGasService
compilerService: CodeParserCompiler
antlrService: CodeParserAntlrService
parseSolidity: (text: string) => Promise<antlr.ParseResult>
getLastNodeInLine: (ast: string) => Promise<any>
listAstNodes: () => Promise<any>
getANTLRBlockAtPosition: (position: any, text?: string) => Promise<any>
getCurrentFileAST: (text?: string) => Promise<ParseResult>
constructor(astWalker: any) {
super(profile)
this.astWalker = astWalker
this.nodeIndex = {
declarations: [[]],
flatReferences: [],
nodesPerFile: {}
}
}
async handleChangeEvents() {
const completionSettings = await this.call('config', 'getAppParameter', 'auto-completion')
if (completionSettings) {
await this.antlrService.getCurrentFileAST()
}
const showGasSettings = await this.call('config', 'getAppParameter', 'show-gas')
const showErrorSettings = await this.call('config', 'getAppParameter', 'display-errors')
if(showGasSettings || showErrorSettings || completionSettings) {
await this.compilerService.compile()
}
}
async onActivation() {
this.gasService = new CodeParserGasService(this)
this.compilerService = new CodeParserCompiler(this)
this.antlrService = new CodeParserAntlrService(this)
this.parseSolidity = this.antlrService.parseSolidity.bind(this.antlrService)
this.getLastNodeInLine = this.antlrService.getLastNodeInLine.bind(this.antlrService)
this.listAstNodes = this.antlrService.listAstNodes.bind(this.antlrService)
this.getANTLRBlockAtPosition = this.antlrService.getANTLRBlockAtPosition.bind(this.antlrService)
this.getCurrentFileAST = this.antlrService.getCurrentFileAST.bind(this.antlrService)
this.on('editor', 'didChangeFile', async (file) => {
await this.call('editor', 'discardLineTexts')
await this.handleChangeEvents()
})
this.on('filePanel', 'setWorkspace', async () => {
await this.call('fileDecorator', 'clearFileDecorators')
})
this.on('fileManager', 'currentFileChanged', async () => {
await this.call('editor', 'discardLineTexts')
await this.handleChangeEvents()
})
this.on('solidity', 'loadingCompiler', async (url) => {
this.compilerService.compiler.loadVersion(true, `${url}?t=${Date.now()}`)
})
await this.compilerService.init()
}
/**
*
* @returns
*/
async getLastCompilationResult() {
return this.compilerAbstract
}
getSubNodes<T extends genericASTNode>(node: T): number[] {
return node.nodeType == "ContractDefinition" && node.contractDependencies;
}
/**
* Builds a flat index and declarations of all the nodes in the compilation result
* @param compilationResult
* @param source
*/
_buildIndex(compilationResult: CompilationResult, source) {
if (compilationResult && compilationResult.sources) {
const callback = (node: genericASTNode) => {
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) {
if (!this.nodeIndex.declarations[node.referencedDeclaration]) {
this.nodeIndex.declarations[node.referencedDeclaration] = []
}
this.nodeIndex.declarations[node.referencedDeclaration].push(node)
}
this.nodeIndex.flatReferences[node.id] = node
}
for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
}
}
// NODE HELPERS
_getInputParams(node: FunctionDefinitionAstNode) {
const params = []
const target = node.parameters
if (target) {
const children = target.parameters
for (const j in children) {
if (children[j].nodeType === 'VariableDeclaration') {
params.push(children[j].typeDescriptions.typeString)
}
}
}
return '(' + params.toString() + ')'
}
_flatNodeList(contractNode: ContractDefinitionAstNode, fileName: string, inScope: boolean, compilatioResult: any) {
const index = {}
const contractName: string = contractNode.name
const callback = (node) => {
if (inScope && node.scope !== contractNode.id
&& !(node.nodeType === 'EnumDefinition' || node.nodeType === 'EventDefinition' || node.nodeType === 'ModifierDefinition'))
return
if (inScope) node.isClassNode = true;
node.gasEstimate = this._getContractGasEstimate(node, contractName, fileName, compilatioResult)
node.functionName = node.name + this._getInputParams(node)
node.contractName = contractName
node.contractId = contractNode.id
index[node.id] = node
}
this.astWalker.walkFull(contractNode, callback)
return index
}
_extractFileNodes(fileName: string, compilationResult: lastCompilationResult) {
if (compilationResult && compilationResult.data.sources && compilationResult.data.sources[fileName]) {
const source = compilationResult.data.sources[fileName]
const nodesByContract: any = {}
nodesByContract.imports = {}
nodesByContract.contracts = {}
this.astWalker.walkFull(source.ast, async (node) => {
if (node.nodeType === 'ContractDefinition') {
const flatNodes = this._flatNodeList(node, fileName, false, compilationResult)
node.gasEstimate = this._getContractGasEstimate(node, node.name, fileName, compilationResult)
nodesByContract.contracts[node.name] = { contractDefinition: node, contractNodes: flatNodes }
const baseNodes = {}
const baseNodesWithBaseContractScope = {}
if (node.linearizedBaseContracts) {
for (const id of node.linearizedBaseContracts) {
if (id !== node.id) {
const baseContract = await this.getNodeById(id)
const callback = (node) => {
node.contractName = (baseContract as any).name
node.contractId = (baseContract as any).id
node.isBaseNode = true;
baseNodes[node.id] = node
if ((node.scope && node.scope === baseContract.id)
|| node.nodeType === 'EnumDefinition'
|| node.nodeType === 'EventDefinition'
) {
baseNodesWithBaseContractScope[node.id] = node
}
if (node.members) {
for (const member of node.members) {
member.contractName = (baseContract as any).name
member.contractId = (baseContract as any).id
member.isBaseNode = true;
}
}
}
this.astWalker.walkFull(baseContract, callback)
}
}
}
nodesByContract.contracts[node.name].baseNodes = baseNodes
nodesByContract.contracts[node.name].baseNodesWithBaseContractScope = baseNodesWithBaseContractScope
nodesByContract.contracts[node.name].contractScopeNodes = this._flatNodeList(node, fileName, true, compilationResult)
}
if (node.nodeType === 'ImportDirective') {
const imported = await this.resolveImports(node, {})
for (const importedNode of (Object.values(imported) as any)) {
if (importedNode.nodes)
for (const subNode of importedNode.nodes) {
nodesByContract.imports[subNode.id] = subNode
}
}
}
})
return nodesByContract
}
}
_getContractGasEstimate(node: ContractDefinitionAstNode | FunctionDefinitionAstNode, contractName: string, fileName: string, compilationResult: lastCompilationResult) {
const contracts = compilationResult.data.contracts && compilationResult.data.contracts[this.currentFile]
for (const name in contracts) {
if (name === contractName) {
const contract = contracts[name]
const estimationObj = contract.evm && contract.evm.gasEstimates
let executionCost = null
if (node.nodeType === 'FunctionDefinition') {
const visibility = node.visibility
if (node.kind !== 'constructor') {
const fnName = node.name
const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') {
executionCost = estimationObj === null ? '-' : estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') {
executionCost = estimationObj === null ? '-' : estimationObj.internal[fn]
}
return { executionCost }
} else {
return {
creationCost: estimationObj === null ? '-' : estimationObj.creation.totalCost,
codeDepositCost: estimationObj === null ? '-' : estimationObj.creation.codeDepositCost,
}
}
}
}
}
}
/**
* Nodes at position where position is a number, offset
* @param position
* @param type
* @returns
*/
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> {
const lastCompilationResult = this.compilerAbstract
if (!lastCompilationResult) return []
const urlFromPath = await this.call('fileManager', 'getUrlFromPath', this.currentFile)
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data && lastCompilationResult.data.sources && lastCompilationResult.data.sources[this.currentFile]) {
const nodes: genericASTNode[] = sourceMappingDecoder.nodesAtPosition(type, position, lastCompilationResult.data.sources[this.currentFile] || lastCompilationResult.data.sources[urlFromPath.file])
return nodes
}
return []
}
/**
*
* @param id
* @returns
*/
async getNodeById(id: number) {
for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === id) {
return this.nodeIndex.flatReferences[key]
}
}
}
/**
*
* @param id
* @returns
*/
async getDeclaration(id: number) {
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) return this.nodeIndex.declarations[id]
}
/**
*
* @param scope
* @returns
*/
async getNodesWithScope(scope: number) {
const nodes = []
for (const node of Object.values(this.nodeIndex.flatReferences)) {
if (node.scope === scope) nodes.push(node)
}
return nodes
}
/**
*
* @param name
* @returns
*/
async getNodesWithName(name: string) {
const nodes: genericASTNode[] = []
for (const node of Object.values(this.nodeIndex.flatReferences)) {
if (node.name === name) nodes.push(node)
}
return nodes
}
/**
*
* @param node
* @returns
*/
declarationOf<T extends genericASTNode>(node: T) {
if (node && ('referencedDeclaration' in node) && node.referencedDeclaration) {
return this.nodeIndex.flatReferences[node.referencedDeclaration]
}
return null
}
/**
*
* @param position
* @returns
*/
async definitionAtPosition(position: number) {
const nodes = await this.nodesAtPosition(position)
let nodeDefinition: any
let node: genericASTNode
if (nodes && nodes.length && !this.errorState) {
node = nodes[nodes.length - 1]
nodeDefinition = node
if (!isNodeDefinition(node)) {
nodeDefinition = await this.declarationOf(node) || node
}
if (node.nodeType === 'ImportDirective') {
for (const key in this.nodeIndex.flatReferences) {
if (this.nodeIndex.flatReferences[key].id === node.sourceUnit) {
nodeDefinition = this.nodeIndex.flatReferences[key]
}
}
}
return nodeDefinition
} else {
const astNodes = await this.antlrService.listAstNodes()
for (const node of astNodes) {
if (node.range[0] <= position && node.range[1] >= position) {
if (nodeDefinition && nodeDefinition.range[0] < node.range[0]) {
nodeDefinition = node
}
if (!nodeDefinition) nodeDefinition = node
}
}
if (nodeDefinition && nodeDefinition.type && nodeDefinition.type === 'Identifier') {
const nodeForIdentifier = await this.findIdentifier(nodeDefinition)
if (nodeForIdentifier) nodeDefinition = nodeForIdentifier
}
return nodeDefinition
}
}
async getContractNodes(contractName: string) {
if (this.nodeIndex.nodesPerFile
&& this.nodeIndex.nodesPerFile[this.currentFile]
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
&& this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName].contractNodes) {
return this.nodeIndex.nodesPerFile[this.currentFile].contracts[contractName]
}
return false
}
async getCurrentFileNodes() {
if (this.nodeIndex.nodesPerFile
&& this.nodeIndex.nodesPerFile[this.currentFile]) {
return this.nodeIndex.nodesPerFile[this.currentFile]
}
return false
}
/**
*
* @param identifierNode
* @returns
*/
async findIdentifier(identifierNode: any) {
const astNodes = await this.antlrService.listAstNodes()
for (const node of astNodes) {
if (node.name === identifierNode.name && node.nodeType !== 'Identifier') {
return node
}
}
}
/**
*
* @param node
* @returns
*/
async positionOfDefinition(node: genericASTNode): Promise<any | null> {
if (node) {
if (node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position) {
return position
}
}
}
return null
}
/**
*
* @param node
* @param imported
* @returns
*/
async resolveImports(node: any, imported = {}) {
if (node.nodeType === 'ImportDirective' && !imported[node.sourceUnit]) {
const importNode: any = await this.getNodeById(node.sourceUnit)
imported[importNode.id] = importNode
if (importNode.nodes) {
for (const child of importNode.nodes) {
imported = await this.resolveImports(child, imported)
}
}
}
return imported
}
/**
*
* @param node
* @returns
*/
referencesOf(node: genericASTNode) {
const results: genericASTNode[] = []
const highlights = (id: number) => {
if (this.nodeIndex.declarations && this.nodeIndex.declarations[id]) {
const refs = this.nodeIndex.declarations[id]
for (const ref in refs) {
const node = refs[ref]
results.push(node)
}
}
}
if (node && ("referencedDeclaration" in node) && node.referencedDeclaration) {
highlights(node.referencedDeclaration)
const current = this.nodeIndex.flatReferences[node.referencedDeclaration]
results.push(current)
} else {
highlights(node.id)
}
return results
}
/**
*
* @param position
* @returns
*/
async referrencesAtPosition(position: any): Promise<genericASTNode[]> {
const nodes = await this.nodesAtPosition(position)
if (nodes && nodes.length) {
const node = nodes[nodes.length - 1]
if (node) {
return this.referencesOf(node)
}
}
}
/**
*
* @returns
*/
async getNodes(): Promise<flatReferenceIndexNode> {
return this.nodeIndex.flatReferences
}
/**
*
* @param node
* @returns
*/
async getNodeLink(node: genericASTNode) {
const lineColumn = await this.getLineColumnOfNode(node)
const position = await this.positionOfDefinition(node)
if (this.compilerAbstract && this.compilerAbstract.source) {
const fileName = this.compilerAbstract.getSourceName(position.file)
return lineColumn ? `${fileName} ${lineColumn.start.line}:${lineColumn.start.column}` : null
}
return ''
}
/*
* @param node
*/
async getLineColumnOfNode(node: any) {
const position = await this.positionOfDefinition(node)
return this.getLineColumnOfPosition(position)
}
/*
* @param position
*/
async getLineColumnOfPosition(position: any) {
if (position) {
const fileName = this.compilerAbstract.getSourceName(position.file)
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(this.compilerAbstract.source.sources[fileName].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn(position, lineBreaks)
return lineColumn
}
}
/**
*
* @param node
* @returns
*/
async getNodeDocumentation(node: genericASTNode) {
if (("documentation" in node) && node.documentation && (node.documentation as any).text) {
let text = '';
(node.documentation as any).text.split('\n').forEach(line => {
text += `${line.trim()}\n`
})
return text
}
}
/**
*
* @param node
* @returns
*/
async getVariableDeclaration(node: any) {
const nodeVisibility = node.visibility && node.visibility.length ? node.visibility + ' ' : ''
const nodeName = node.name && node.name.length ? node.name : ''
if (node.typeDescriptions && node.typeDescriptions.typeString) {
return `${node.typeDescriptions.typeString} ${nodeVisibility}${nodeName}`
} else {
if (node.typeName && node.typeName.name) {
return `${node.typeName.name} ${nodeVisibility}${nodeName}`
}
else if (node.typeName && node.typeName.namePath) {
return `${node.typeName.namePath} ${nodeVisibility}${nodeName}`
}
else {
return `${nodeName}${nodeName}`
}
}
}
/**
*
* @param node
* @returns
*/
async getFunctionParamaters(node: any) {
const localParam = (node.parameters && node.parameters.parameters) || (node.parameters)
if (localParam) {
const params = []
for (const param of localParam) {
params.push(await this.getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
}
/**
*
* @param node
* @returns
*/
async getFunctionReturnParameters(node: any) {
const localParam = (node.returnParameters && node.returnParameters.parameters)
if (localParam) {
const params = []
for (const param of localParam) {
params.push(await this.getVariableDeclaration(param))
}
return `(${params.join(', ')})`
}
}
}

@ -0,0 +1,158 @@
'use strict'
import { AstNode } from "@remix-project/remix-solidity-ts"
import { CodeParser } from "../code-parser"
import { antlr } from '../types'
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
export default class CodeParserAntlrService {
plugin: CodeParser
constructor(plugin: CodeParser) {
this.plugin = plugin
}
/*
* simple parsing is used to quickly parse the current file or a text source without using the compiler or having to resolve imports
*/
async parseSolidity(text: string) {
const ast: antlr.ParseResult = (SolidityParser as any).parse(text, { loc: true, range: true, tolerant: true })
return ast
}
/**
* Tries to parse the current file or the given text and returns the AST
* If the parsing fails it returns the last successful AST for this file
* @param text
* @returns
*/
async getCurrentFileAST(text: string | null = null) {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
if (!this.plugin.currentFile) return
const fileContent = text || await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
try {
const ast = await this.parseSolidity(fileContent)
this.plugin.antlrParserResult = ast
} catch (e) {
// do nothing
}
return this.plugin.antlrParserResult
}
}
/**
* Lists the AST nodes from the current file parser
* These nodes need to be changed to match the node types returned by the compiler
* @returns
*/
async listAstNodes() {
await this.getCurrentFileAST();
const nodes: AstNode[] = [];
(SolidityParser as any).visit(this.plugin.antlrParserResult, {
StateVariableDeclaration: (node: antlr.StateVariableDeclaration) => {
if (node.variables) {
for (const variable of node.variables) {
nodes.push({ ...variable, nodeType: 'VariableDeclaration', id: null, src: null })
}
}
},
VariableDeclaration: (node: antlr.VariableDeclaration) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
UserDefinedTypeName: (node: antlr.UserDefinedTypeName) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
FunctionDefinition: (node: antlr.FunctionDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ContractDefinition: (node: antlr.ContractDefinition) => {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
MemberAccess: function (node: antlr.MemberAccess) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
Identifier: function (node: antlr.Identifier) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EventDefinition: function (node: antlr.EventDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
ModifierDefinition: function (node: antlr.ModifierDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
InvalidNode: function (node: antlr.InvalidNode) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
EnumDefinition: function (node: antlr.EnumDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
},
StructDefinition: function (node: antlr.StructDefinition) {
nodes.push({ ...node, nodeType: node.type, id: null, src: null })
}
})
return nodes
}
/**
*
* @param ast
* @returns
*/
async getLastNodeInLine(ast: string) {
let lastNode: any
const checkLastNode = (node: antlr.MemberAccess | antlr.Identifier) => {
if (lastNode && lastNode.range && lastNode.range[1]) {
if (node.range[1] > lastNode.range[1]) {
lastNode = node
}
} else {
lastNode = node
}
}
(SolidityParser as any).visit(ast, {
MemberAccess: function (node: antlr.MemberAccess) {
checkLastNode(node)
},
Identifier: function (node: antlr.Identifier) {
checkLastNode(node)
}
})
if (lastNode && lastNode.expression) {
return lastNode.expression
}
return lastNode
}
/**
* Returns the block surrounding the given position
* For example if the position is in the middle of a function, it will return the function
* @param {position} position
* @param {string} text // optional
* @return {any}
* */
async getANTLRBlockAtPosition(position: any, text: string = null) {
await this.getCurrentFileAST(text)
const allowedTypes = ['SourceUnit', 'ContractDefinition', 'FunctionDefinition']
const walkAst = (node: any) => {
if (node.loc.start.line <= position.lineNumber && node.loc.end.line >= position.lineNumber) {
const children = node.children || node.subNodes
if (children && allowedTypes.indexOf(node.type) !== -1) {
for (const child of children) {
const result = walkAst(child)
if (result) return result
}
}
return node
}
return null
}
if (!this.plugin.antlrParserResult) return
return walkAst(this.plugin.antlrParserResult)
}
}

@ -0,0 +1,235 @@
'use strict'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { Compiler } from '@remix-project/remix-solidity'
import { CompilationResult, CompilationSource } from '@remix-project/remix-solidity'
import { CodeParser } from "../code-parser";
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerRetriggerMode } from '@remix-project/remix-solidity-ts';
import { MarkerSeverity } from 'monaco-editor';
type errorMarker = {
message: string
severity: MarkerSeverity
position: {
start: {
line: number
column: number
},
end: {
line: number
column: number
}
},
file: string
}
export default class CodeParserCompiler {
plugin: CodeParser
compiler: any // used to compile the current file seperately from the main compiler
onAstFinished: (success: any, data: CompilationResult, source: CompilationSource, input: any, version: any) => Promise<void>;
errorState: boolean;
gastEstimateTimeOut: any
constructor(
plugin: CodeParser
) {
this.plugin = plugin
}
init() {
this.onAstFinished = async (success, data: CompilationResult, source: CompilationSource, input: any, version) => {
this.plugin.call('editor', 'clearAnnotations')
this.errorState = true
const result = new CompilerAbstract('soljson', data, source, input)
let allErrors: errorMarker[] = []
if (data.errors) {
const sources = result.getSourceCode().sources
for (const error of data.errors) {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(sources[error.sourceLocation.file].content)
const lineColumn = sourceMappingDecoder.convertOffsetToLineColumn({
start: error.sourceLocation.start,
length: error.sourceLocation.end - error.sourceLocation.start
}, lineBreaks)
const filePath = error.sourceLocation.file
allErrors = [...allErrors, {
message: error.formattedMessage,
severity: error.severity === 'error' ? MarkerSeverity.Error : MarkerSeverity.Warning,
position: {
start: {
line: ((lineColumn.start && lineColumn.start.line) || 0) + 1,
column: ((lineColumn.start && lineColumn.start.column) || 0) + 1
},
end: {
line: ((lineColumn.end && lineColumn.end.line) || 0) + 1,
column: ((lineColumn.end && lineColumn.end.column) || 0) + 1
}
}
, file: filePath
}]
}
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if(displayErrors) await this.plugin.call('editor', 'addErrorMarker', allErrors)
this.addDecorators(allErrors, sources)
} else {
await this.plugin.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
await this.clearDecorators(result.getSourceCode().sources)
}
if (!data.sources) return
if (data.sources && Object.keys(data.sources).length === 0) return
this.plugin.compilerAbstract = new CompilerAbstract('soljson', data, source, input)
this.errorState = false
this.plugin.nodeIndex = {
declarations: {},
flatReferences: {},
nodesPerFile: {},
}
this.plugin._buildIndex(data, source)
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract)
await this.plugin.gasService.showGasEstimates()
this.plugin.emit('astFinished')
}
this.compiler = new Compiler((url, cb) => this.plugin.call('contentImport', 'resolveAndSave', url, undefined).then((result) => cb(null, result)).catch((error) => cb(error.message)))
this.compiler.event.register('compilationFinished', this.onAstFinished)
}
// COMPILER
/**
*
* @returns
*/
async compile() {
try {
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (this.plugin.currentFile && this.plugin.currentFile.endsWith('.sol')) {
const state = await this.plugin.call('solidity', 'getCompilerState')
this.compiler.set('optimize', state.optimize)
this.compiler.set('evmVersion', state.evmVersion)
this.compiler.set('language', state.language)
this.compiler.set('runs', state.runs)
this.compiler.set('useFileConfiguration', true)
this.compiler.set('compilerRetriggerMode', CompilerRetriggerMode.retrigger)
const configFileContent = {
"language": "Solidity",
"settings": {
"optimizer": {
"enabled": false,
"runs": 200
},
"outputSelection": {
"*": {
"": ["ast"],
"*": ["evm.gasEstimates"]
}
},
"evmVersion": state.evmVersion && state.evmVersion.toString() || "byzantium",
}
}
this.compiler.set('configFileContent', JSON.stringify(configFileContent))
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
if (!this.plugin.currentFile) return
const content = await this.plugin.call('fileManager', 'readFile', this.plugin.currentFile)
const sources = { [this.plugin.currentFile]: { content } }
this.compiler.compile(sources, this.plugin.currentFile)
}
} catch (e) {
// do nothing
}
}
async addDecorators(allErrors: errorMarker[], sources: any) {
const displayErrors = await this.plugin.call('config', 'getAppParameter', 'display-errors')
if(!displayErrors) return
const errorsPerFiles: {[fileName: string]: errorMarker[]} = {}
for (const error of allErrors) {
if (!errorsPerFiles[error.file]) {
errorsPerFiles[error.file] = []
}
errorsPerFiles[error.file].push(error)
}
const errorPriority = {
'error': 0,
'warning': 1,
}
// sort errorPerFiles by error priority
const sortedErrorsPerFiles: {[fileName: string]: errorMarker[]} = {}
for (const fileName in errorsPerFiles) {
const errors = errorsPerFiles[fileName]
errors.sort((a, b) => {
return errorPriority[a.severity] - errorPriority[b.severity]
}
)
sortedErrorsPerFiles[fileName] = errors
}
const filesWithOutErrors = Object.keys(sources).filter((fileName) => !sortedErrorsPerFiles[fileName])
// add decorators
const decorators: fileDecoration[] = []
for (const fileName in sortedErrorsPerFiles) {
const errors = sortedErrorsPerFiles[fileName]
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: errors[0].severity == MarkerSeverity.Error? fileDecorationType.Error : fileDecorationType.Warning,
fileStateLabelClass: errors[0].severity == MarkerSeverity.Error ? 'text-danger' : 'text-warning',
fileStateIconClass: '',
fileStateIcon: '',
text: errors.length.toString(),
owner: 'code-parser',
bubble: true,
comment: errors.map((error) => error.message),
}
decorators.push(decorator)
}
for (const fileName of filesWithOutErrors) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
await this.plugin.call('editor', 'clearErrorMarkers', filesWithOutErrors)
}
async clearDecorators(sources: any) {
const decorators: fileDecoration[] = []
for (const fileName of Object.keys(sources)) {
const decorator: fileDecoration = {
path: fileName,
isDirectory: false,
fileStateType: fileDecorationType.None,
fileStateLabelClass: '',
fileStateIconClass: '',
fileStateIcon: '',
text: '',
owner: 'code-parser',
bubble: false
}
decorators.push(decorator)
}
await this.plugin.call('fileDecorator', 'setFileDecorators', decorators)
}
}

@ -0,0 +1,76 @@
import { CodeParser, genericASTNode } from "../code-parser";
import { lineText } from '@remix-ui/editor'
export default class CodeParserGasService {
plugin: CodeParser
constructor(plugin: CodeParser) {
this.plugin = plugin
}
async getGasEstimates(fileName: string) {
if (!fileName) {
fileName = await this.plugin.currentFile
}
if (this.plugin.nodeIndex.nodesPerFile && this.plugin.nodeIndex.nodesPerFile[fileName] && this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
const estimates: any = []
for (const contract in this.plugin.nodeIndex.nodesPerFile[fileName].contracts) {
if (this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes) {
const nodes = this.plugin.nodeIndex.nodesPerFile[fileName].contracts[contract].contractNodes
for (const node of Object.values(nodes) as any[]) {
if (node.gasEstimate) {
estimates.push({
node,
range: await this.plugin.getLineColumnOfNode(node)
})
}
}
}
}
return estimates
}
}
async showGasEstimates() {
const showGasConfig = await this.plugin.call('config', 'getAppParameter', 'show-gas')
if(!showGasConfig) {
await this.plugin.call('editor', 'discardLineTexts')
return
}
this.plugin.currentFile = await this.plugin.call('fileManager', 'file')
this.plugin.nodeIndex.nodesPerFile[this.plugin.currentFile] = await this.plugin._extractFileNodes(this.plugin.currentFile, this.plugin.compilerAbstract)
const gasEstimates = await this.getGasEstimates(this.plugin.currentFile)
const friendlyNames = {
'executionCost': 'Estimated execution cost',
'codeDepositCost': 'Estimated code deposit cost',
'creationCost': 'Estimated creation cost',
}
await this.plugin.call('editor', 'discardLineTexts')
if (gasEstimates) {
for (const estimate of gasEstimates) {
const linetext: lineText = {
content: Object.entries(estimate.node.gasEstimate).map(([, value]) => `${value} gas`).join(' '),
position: estimate.range,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'codeParser',
hoverMessage: [{
value: `${Object.entries(estimate.node.gasEstimate).map(([key, value]) => `${friendlyNames[key]}: ${value} gas`).join(' ')}`,
},
],
}
this.plugin.call('editor', 'addLineText', linetext, estimate.range.fileName)
}
}
}
}

@ -0,0 +1,732 @@
// Base on the original type definitions for solidity-parser-antlr 0.2
// by Leonid Logvinov <https://github.com/LogvinovLeon>
// Alex Browne <https://github.com/albrow>
// Xiao Liang <https://github.com/yxliang01>
export type ParseResult = SourceUnit & {
errors?: any[]
tokens?: Token[]
}
interface Token {
type: string
value: string | undefined
range?: [number, number]
loc?: {
start: {
line: number
column: number
}
end: {
line: number
column: number
}
}
}
interface Location {
start: {
line: number
column: number
}
end: {
line: number
column: number
}
}
export interface BaseASTNode {
type: ASTNodeTypeString
range?: [number, number]
loc?: Location
}
export interface SourceUnit extends BaseASTNode {
type: 'SourceUnit'
children: ASTNode[]
}
export interface ContractDefinition extends BaseASTNode {
type: 'ContractDefinition'
name: string
baseContracts: InheritanceSpecifier[]
kind: string
subNodes: BaseASTNode[]
}
export interface InheritanceSpecifier extends BaseASTNode {
type: 'InheritanceSpecifier'
baseName: UserDefinedTypeName
arguments: Expression[]
}
export interface UserDefinedTypeName extends BaseASTNode {
type: 'UserDefinedTypeName'
namePath: string
}
export type ASTNodeTypeString = typeof astNodeTypes[number]
export interface InvalidNode extends BaseASTNode {
type: 'InvalidNode'
name?: string
value?: string
}
export interface PragmaDirective extends BaseASTNode {
type: 'PragmaDirective'
name: string
value: string
}
export interface ImportDirective extends BaseASTNode {
type: 'ImportDirective'
path: string
pathLiteral: StringLiteral
unitAlias: string | null
unitAliasIdentifier: Identifier | null
symbolAliases: Array<[string, string | null]> | null
symbolAliasesIdentifiers: Array<[Identifier, Identifier | null]> | null
}
export interface StateVariableDeclaration extends BaseASTNode {
type: 'StateVariableDeclaration'
variables: StateVariableDeclarationVariable[]
initialValue: Expression | null
}
export interface FileLevelConstant extends BaseASTNode {
type: 'FileLevelConstant'
typeName: TypeName
name: string
initialValue: Expression
isDeclaredConst: boolean
isImmutable: boolean
}
export interface UsingForDeclaration extends BaseASTNode {
type: 'UsingForDeclaration'
typeName: TypeName | null
functions: string[]
libraryName: string | null
isGlobal: boolean;
}
export interface StructDefinition extends BaseASTNode {
type: 'StructDefinition'
name: string
members: VariableDeclaration[]
}
export interface ModifierDefinition extends BaseASTNode {
type: 'ModifierDefinition'
name: string
parameters: null | VariableDeclaration[]
isVirtual: boolean
override: null | UserDefinedTypeName[]
body: Block | null
}
export interface ModifierInvocation extends BaseASTNode {
type: 'ModifierInvocation'
name: string
arguments: Expression[] | null
}
export interface FunctionDefinition extends BaseASTNode {
type: 'FunctionDefinition'
name: string | null
parameters: VariableDeclaration[]
modifiers: ModifierInvocation[]
stateMutability: 'pure' | 'constant' | 'payable' | 'view' | null
visibility: 'default' | 'external' | 'internal' | 'public' | 'private'
returnParameters: VariableDeclaration[] | null
body: Block | null
override: UserDefinedTypeName[] | null
isConstructor: boolean
isReceiveEther: boolean
isFallback: boolean
isVirtual: boolean
}
export interface CustomErrorDefinition extends BaseASTNode {
type: 'CustomErrorDefinition'
name: string
parameters: VariableDeclaration[]
}
export interface TypeDefinition extends BaseASTNode {
type: 'TypeDefinition'
name: string
definition: ElementaryTypeName
}
export interface RevertStatement extends BaseASTNode {
type: 'RevertStatement'
revertCall: FunctionCall
}
export interface EventDefinition extends BaseASTNode {
type: 'EventDefinition'
name: string
parameters: VariableDeclaration[]
isAnonymous: boolean
}
export interface EnumValue extends BaseASTNode {
type: 'EnumValue'
name: string
}
export interface EnumDefinition extends BaseASTNode {
type: 'EnumDefinition'
name: string
members: EnumValue[]
}
export interface VariableDeclaration extends BaseASTNode {
type: 'VariableDeclaration'
isIndexed: boolean
isStateVar: boolean
typeName: TypeName | null
name: string | null
identifier: Identifier | null
isDeclaredConst?: boolean
storageLocation: string | null
expression: Expression | null
visibility?: 'public' | 'private' | 'internal' | 'default'
}
export interface StateVariableDeclarationVariable extends VariableDeclaration {
override: null | UserDefinedTypeName[]
isImmutable: boolean
}
export interface ArrayTypeName extends BaseASTNode {
type: 'ArrayTypeName'
baseTypeName: TypeName
length: Expression | null
}
export interface Mapping extends BaseASTNode {
type: 'Mapping'
keyType: ElementaryTypeName | UserDefinedTypeName
valueType: TypeName
}
export interface FunctionTypeName extends BaseASTNode {
type: 'FunctionTypeName'
parameterTypes: VariableDeclaration[]
returnTypes: VariableDeclaration[]
visibility: string
stateMutability: string | null
}
export interface Block extends BaseASTNode {
type: 'Block'
statements: BaseASTNode[]
}
export interface ExpressionStatement extends BaseASTNode {
type: 'ExpressionStatement'
expression: Expression | null
}
export interface IfStatement extends BaseASTNode {
type: 'IfStatement'
condition: Expression
trueBody: Statement
falseBody: Statement | null
}
export interface UncheckedStatement extends BaseASTNode {
type: 'UncheckedStatement'
block: Block
}
export interface TryStatement extends BaseASTNode {
type: 'TryStatement'
expression: Expression
returnParameters: VariableDeclaration[] | null
body: Block
catchClauses: CatchClause[]
}
export interface CatchClause extends BaseASTNode {
type: 'CatchClause'
isReasonStringType: boolean
kind: string | null
parameters: VariableDeclaration[] | null
body: Block
}
export interface WhileStatement extends BaseASTNode {
type: 'WhileStatement'
condition: Expression
body: Statement
}
export interface ForStatement extends BaseASTNode {
type: 'ForStatement'
initExpression: SimpleStatement | null
conditionExpression?: Expression
loopExpression: ExpressionStatement
body: Statement
}
export interface InlineAssemblyStatement extends BaseASTNode {
type: 'InlineAssemblyStatement'
language: string | null
flags: string[]
body: AssemblyBlock
}
export interface DoWhileStatement extends BaseASTNode {
type: 'DoWhileStatement'
condition: Expression
body: Statement
}
export interface ContinueStatement extends BaseASTNode {
type: 'ContinueStatement'
}
export interface Break extends BaseASTNode {
type: 'Break'
}
export interface Continue extends BaseASTNode {
type: 'Continue'
}
export interface BreakStatement extends BaseASTNode {
type: 'BreakStatement'
}
export interface ReturnStatement extends BaseASTNode {
type: 'ReturnStatement'
expression: Expression | null
}
export interface EmitStatement extends BaseASTNode {
type: 'EmitStatement'
eventCall: FunctionCall
}
export interface ThrowStatement extends BaseASTNode {
type: 'ThrowStatement'
}
export interface VariableDeclarationStatement extends BaseASTNode {
type: 'VariableDeclarationStatement'
variables: Array<BaseASTNode | null>
initialValue: Expression | null
}
export interface ElementaryTypeName extends BaseASTNode {
type: 'ElementaryTypeName'
name: string
stateMutability: string | null
}
export interface FunctionCall extends BaseASTNode {
type: 'FunctionCall'
expression: Expression
arguments: Expression[]
names: string[]
identifiers: Identifier[]
}
export interface AssemblyBlock extends BaseASTNode {
type: 'AssemblyBlock'
operations: AssemblyItem[]
}
export interface AssemblyCall extends BaseASTNode {
type: 'AssemblyCall'
functionName: string
arguments: AssemblyExpression[]
}
export interface AssemblyLocalDefinition extends BaseASTNode {
type: 'AssemblyLocalDefinition'
names: Identifier[] | AssemblyMemberAccess[]
expression: AssemblyExpression | null
}
export interface AssemblyAssignment extends BaseASTNode {
type: 'AssemblyAssignment'
names: Identifier[] | AssemblyMemberAccess[]
expression: AssemblyExpression
}
export interface AssemblyStackAssignment extends BaseASTNode {
type: 'AssemblyStackAssignment'
name: string
expression: AssemblyExpression
}
export interface LabelDefinition extends BaseASTNode {
type: 'LabelDefinition'
name: string
}
export interface AssemblySwitch extends BaseASTNode {
type: 'AssemblySwitch'
expression: AssemblyExpression
cases: AssemblyCase[]
}
export interface AssemblyCase extends BaseASTNode {
type: 'AssemblyCase'
value: AssemblyLiteral | null
block: AssemblyBlock
default: boolean
}
export interface AssemblyFunctionDefinition extends BaseASTNode {
type: 'AssemblyFunctionDefinition'
name: string
arguments: Identifier[]
returnArguments: Identifier[]
body: AssemblyBlock
}
export interface AssemblyFunctionReturns extends BaseASTNode {
type: 'AssemblyFunctionReturns'
}
export interface AssemblyFor extends BaseASTNode {
type: 'AssemblyFor'
pre: AssemblyBlock | AssemblyExpression
condition: AssemblyExpression
post: AssemblyBlock | AssemblyExpression
body: AssemblyBlock
}
export interface AssemblyIf extends BaseASTNode {
type: 'AssemblyIf'
condition: AssemblyExpression
body: AssemblyBlock
}
export type AssemblyLiteral =
| StringLiteral
| DecimalNumber
| HexNumber
| HexLiteral
export interface SubAssembly extends BaseASTNode {
type: 'SubAssembly'
}
export interface AssemblyMemberAccess extends BaseASTNode {
type: 'AssemblyMemberAccess'
expression: Identifier
memberName: Identifier
}
export interface NewExpression extends BaseASTNode {
type: 'NewExpression'
typeName: TypeName
}
export interface TupleExpression extends BaseASTNode {
type: 'TupleExpression'
components: Array<BaseASTNode | null>
isArray: boolean
}
export interface NameValueExpression extends BaseASTNode {
type: 'NameValueExpression'
expression: Expression
arguments: NameValueList
}
export interface NumberLiteral extends BaseASTNode {
type: 'NumberLiteral'
number: string
subdenomination:
| null
| 'wei'
| 'szabo'
| 'finney'
| 'ether'
| 'seconds'
| 'minutes'
| 'hours'
| 'days'
| 'weeks'
| 'years'
}
export interface BooleanLiteral extends BaseASTNode {
type: 'BooleanLiteral'
value: boolean
}
export interface HexLiteral extends BaseASTNode {
type: 'HexLiteral'
value: string
parts: string[]
}
export interface StringLiteral extends BaseASTNode {
type: 'StringLiteral'
value: string
parts: string[]
isUnicode: boolean[]
}
export interface Identifier extends BaseASTNode {
type: 'Identifier'
name: string
}
export interface BinaryOperation extends BaseASTNode {
type: 'BinaryOperation'
left: Expression
right: Expression
operator: BinOp
}
export interface UnaryOperation extends BaseASTNode {
type: 'UnaryOperation'
operator: UnaryOp
subExpression: Expression
isPrefix: boolean
}
export interface Conditional extends BaseASTNode {
type: 'Conditional'
condition: Expression
trueExpression: Expression
falseExpression: Expression
}
export interface IndexAccess extends BaseASTNode {
type: 'IndexAccess'
base: Expression
index: Expression
}
export interface IndexRangeAccess extends BaseASTNode {
type: 'IndexRangeAccess'
base: Expression
indexStart?: Expression
indexEnd?: Expression
}
export interface MemberAccess extends BaseASTNode {
type: 'MemberAccess'
expression: Expression
memberName: string
}
export interface HexNumber extends BaseASTNode {
type: 'HexNumber'
value: string
}
export interface DecimalNumber extends BaseASTNode {
type: 'DecimalNumber'
value: string
}
export interface NameValueList extends BaseASTNode {
type: 'NameValueList'
names: string[]
identifiers: Identifier[]
arguments: Expression[]
}
export type ASTNode =
| SourceUnit
| PragmaDirective
| ImportDirective
| ContractDefinition
| InheritanceSpecifier
| StateVariableDeclaration
| UsingForDeclaration
| StructDefinition
| ModifierDefinition
| ModifierInvocation
| FunctionDefinition
| EventDefinition
| CustomErrorDefinition
| EnumValue
| EnumDefinition
| VariableDeclaration
| TypeName
| UserDefinedTypeName
| Mapping
| FunctionTypeName
| Block
| Statement
| ElementaryTypeName
| AssemblyBlock
| AssemblyCall
| AssemblyLocalDefinition
| AssemblyAssignment
| AssemblyStackAssignment
| LabelDefinition
| AssemblySwitch
| AssemblyCase
| AssemblyFunctionDefinition
| AssemblyFunctionReturns
| AssemblyFor
| AssemblyIf
| AssemblyLiteral
| SubAssembly
| TupleExpression
| BinaryOperation
| Conditional
| IndexAccess
| IndexRangeAccess
| AssemblyItem
| Expression
| NameValueList
| AssemblyMemberAccess
| CatchClause
| FileLevelConstant
| TypeDefinition
| InvalidNode
export type AssemblyItem =
| Identifier
| AssemblyBlock
| AssemblyExpression
| AssemblyLocalDefinition
| AssemblyAssignment
| AssemblyStackAssignment
| LabelDefinition
| AssemblySwitch
| AssemblyFunctionDefinition
| AssemblyFor
| AssemblyIf
| Break
| Continue
| SubAssembly
| NumberLiteral
| StringLiteral
| HexNumber
| HexLiteral
| DecimalNumber
export type AssemblyExpression = AssemblyCall | AssemblyLiteral
export type Expression =
| IndexAccess
| IndexRangeAccess
| TupleExpression
| BinaryOperation
| Conditional
| MemberAccess
| FunctionCall
| UnaryOperation
| NewExpression
| PrimaryExpression
| NameValueExpression
export type PrimaryExpression =
| BooleanLiteral
| HexLiteral
| StringLiteral
| NumberLiteral
| Identifier
| TupleExpression
| TypeName
export type SimpleStatement = VariableDeclarationStatement | ExpressionStatement
export type TypeName =
| ElementaryTypeName
| UserDefinedTypeName
| Mapping
| ArrayTypeName
| FunctionTypeName
export type Statement =
| IfStatement
| WhileStatement
| ForStatement
| Block
| InlineAssemblyStatement
| DoWhileStatement
| ContinueStatement
| BreakStatement
| ReturnStatement
| EmitStatement
| ThrowStatement
| SimpleStatement
| VariableDeclarationStatement
| UncheckedStatement
| TryStatement
| RevertStatement
type ASTMap<U> = { [K in ASTNodeTypeString]: U extends { type: K } ? U : never }
type ASTTypeMap = ASTMap<ASTNode>
export const astNodeTypes = [
'SourceUnit',
'PragmaDirective',
'ImportDirective',
'ContractDefinition',
'InheritanceSpecifier',
'StateVariableDeclaration',
'UsingForDeclaration',
'StructDefinition',
'ModifierDefinition',
'ModifierInvocation',
'FunctionDefinition',
'EventDefinition',
'CustomErrorDefinition',
'RevertStatement',
'EnumValue',
'EnumDefinition',
'VariableDeclaration',
'UserDefinedTypeName',
'Mapping',
'ArrayTypeName',
'FunctionTypeName',
'Block',
'ExpressionStatement',
'IfStatement',
'WhileStatement',
'ForStatement',
'InlineAssemblyStatement',
'DoWhileStatement',
'ContinueStatement',
'Break',
'Continue',
'BreakStatement',
'ReturnStatement',
'EmitStatement',
'ThrowStatement',
'VariableDeclarationStatement',
'ElementaryTypeName',
'FunctionCall',
'AssemblyBlock',
'AssemblyCall',
'AssemblyLocalDefinition',
'AssemblyAssignment',
'AssemblyStackAssignment',
'LabelDefinition',
'AssemblySwitch',
'AssemblyCase',
'AssemblyFunctionDefinition',
'AssemblyFunctionReturns',
'AssemblyFor',
'AssemblyIf',
'SubAssembly',
'TupleExpression',
'NameValueExpression',
'BooleanLiteral',
'NumberLiteral',
'Identifier',
'BinaryOperation',
'UnaryOperation',
'NewExpression',
'Conditional',
'StringLiteral',
'HexLiteral',
'HexNumber',
'DecimalNumber',
'MemberAccess',
'IndexAccess',
'IndexRangeAccess',
'NameValueList',
'UncheckedStatement',
'TryStatement',
'CatchClause',
'FileLevelConstant',
'AssemblyMemberAccess',
'TypeDefinition',
'InvalidNode'
] as const
export const binaryOpValues = [
'+',
'-',
'*',
'/',
'**',
'%',
'<<',
'>>',
'&&',
'||',
',,',
'&',
',',
'^',
'<',
'>',
'<=',
'>=',
'==',
'!=',
'=',
',=',
'^=',
'&=',
'<<=',
'>>=',
'+=',
'-=',
'*=',
'/=',
'%=',
'|',
'|=',
] as const
export type BinOp = typeof binaryOpValues[number]
export const unaryOpValues = [
'-',
'+',
'++',
'--',
'~',
'after',
'delete',
'!',
] as const
export type UnaryOp = typeof unaryOpValues[number]

@ -0,0 +1 @@
export * as antlr from './antlr-types'

@ -18,7 +18,11 @@ const profile = {
events: [],
description: 'Using Remixd daemon, allow to access file system',
kind: 'other',
version: packageJson.version
version: packageJson.version,
repo: "https://github.com/ethereum/remix-project/tree/master/libs/remixd",
maintainedBy: "Remix",
documentation: "https://remix-ide.readthedocs.io/en/latest/remixd.html",
authorContact: ""
}
enum State {

@ -3,7 +3,7 @@ import { DebuggerApiMixin } from '@remixproject/debugger-plugin' // eslint-disab
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import * as remixBleach from '../../lib/remixBleach'
import { bleach } from '@remix-ui/helper'
import { compilationFinishedToastMsg, compilingToastMsg, localCompilationToastMsg, notFoundToastMsg, sourceVerificationNotAvailableToastMsg } from '@remix-ui/helper'
const css = require('./styles/debugger-tab-styles')
@ -52,7 +52,7 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
return <div className={css.debuggerTabView} id='debugView'><DebuggerUI debuggerAPI={this} /></div>
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} /></div>
}
showMessage (title, message) {
@ -60,7 +60,7 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.call('notification', 'alert', {
id: 'debuggerTabShowMessage',
title,
message: remixBleach.sanitize(message)
message: bleach.sanitize(message)
})
} catch (e) {
console.log(e)

@ -30,7 +30,7 @@ export class InjectedProvider extends Plugin {
return reject(new Error('no injected provider found.'))
}
try {
if ((window as any) && typeof (window as any).ethereum.enable === 'function') (window as any).ethereum.enable()
if ((window as any) && typeof (window as any).ethereum.request === "function") (window as any).ethereum.request({ method: "eth_requestAccounts" });
if (!await (window as any).ethereum._metamask.isUnlocked()) this.call('notification', 'toast', 'Please make sure the injected provider is unlocked (e.g Metamask).')
await addL2Network(this.chainName, this.chainId, this.rpcUrls)
const resultData = await this.provider.currentProvider.send(data.method, data.params)

@ -12,9 +12,9 @@ export default {
'settings.wordWrapText': 'Word wrap in editor',
'settings.enablePersonalModeText': ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n',
'settings.warnText': 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a passphrase in the Remix interface without having to unlock the account. Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). Remix never persists any passphrase'.split('\n').map(s => s.trim()).join(' '),
'settings.githubTitle': 'GitHub Credentials',
'settings.githubText': 'Manage your GitHub credentials used to publish to Gist and retrieve GitHub contents.',
'settings.githubText2': 'Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only \'create gist\' permission.',
'settings.gitAccessTokenTitle': 'GitHub Access Token',
'settings.gitAccessTokenText': 'Manage the access token used to publish to Gist and retrieve GitHub contents.',
'settings.gitAccessTokenText2': 'Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only \'create gist\' permission.',
'settings.etherscanTokenTitle': 'EtherScan Access Token',
'settings.etherscanAccessTokenText': 'Manage the api key used to interact with Etherscan.',
'settings.etherscanAccessTokenText2': 'Go to Etherscan api key page (link below) to create a new api key and save it in Remix.',
@ -57,8 +57,12 @@ export default {
'filePanel.uploadFile': 'Load a local file into current workspace',
'filePanel.updateGist': 'Update the current [gist] explorer',
'home.scamAlert': 'Scam Alert: There are video tutorials going around that provide urls other than remix.ethereum.org, and could be scams. Also, beware of online videos promoting "liquidity front runner bots".',
'home.scamAlert': 'Scam Alert',
'home.scamAlertText': 'The only URL Remix uses is remix.ethereum.org',
'home.scamAlertText2': 'Beware of online videos promoting "liquidity front runner bots"',
'home.scamAlertText3': 'Additional safety tips',
'home.learnMore': 'Learn more',
'home.here': 'here',
'home.featuredPlugins': 'Featured Plugins',
'home.file': 'File',
'home.newFile': 'New File',

@ -12,9 +12,9 @@ export default {
'settings.wordWrapText': '文本换行',
'settings.enablePersonalModeText': '为web3提供器启用私有模式. 通过Web3发送的交易将使用web3.personal API.\n',
'settings.warnText': '在启用之前请确认访问端结点已经开放. \n此模式允许在Remix界面中提供密码而无需解锁账号. 虽然这很方便,但你应当完全信任所连接的后端节点 (Geth, Parity, ...). Remix不会持久化保存任何密码.'.split('\n').map(s => s.trim()).join(' '),
'settings.githubTitle': 'Github 认证信息',
'settings.githubText': '管理用于发布到 Gist 以及读取 Github 内容的 GitHub 认证信息.',
'settings.githubText2': '前往 github (参见下方链接) 创建一个新的token,然后保存到Remix中. 确保这个token只有 \'create gist\' 权限.',
'settings.gitAccessTokenTitle': 'Github 访问令牌',
'settings.gitAccessTokenText': '管理用于发布到 Gist 以及读取 Github 内容的 GitHub 访问令牌.',
'settings.gitAccessTokenText2': '前往 github (参见下方链接) 创建一个新的token,然后保存到Remix中. 确保这个token只有 \'create gist\' 权限.',
'settings.etherscanTokenTitle': 'EtherScan 访问 Token',
'settings.etherscanAccessTokenText': '管理用于与Etherscan交互的api密钥.',
'settings.etherscanAccessTokenText2': '前往 Etherscan api 密钥页面 (参见下方链接),创建一个新的api密钥并保存到Remix中.',
@ -57,8 +57,12 @@ export default {
'filePanel.uploadFile': '加载本地文件到当前工作空间',
'filePanel.updateGist': '更新当前 [gist] 浏览',
'home.scamAlert': '诈骗警报:有一些视频教程提供了 remix.ethereum.org 以外的网址,并且可能是诈骗。此外,请注意宣传“流动性领先机器人”的在线视频。',
'home.scamAlert': '诈骗警报',
'home.scamAlertText': 'Remix 唯一使用的 URL 是 remix.ethereum.org',
'home.scamAlertText2': '注意那些宣传 "liquidity front runner bots" 的在线视频',
'home.scamAlertText3': '其他安全提示',
'home.learnMore': '了解更多',
'home.here': '这里',
'home.featuredPlugins': '精选插件',
'home.file': '文件',
'home.newFile': '新建文件',
@ -105,13 +109,13 @@ export default {
'udapp.deploy': '部署',
'udapp.publishTo': '发布到',
'udapp.or': '或',
'udapp.atAddress': '合约地址',
'udapp.atAddress': 'At Address',
'udapp.noCompiledContracts': '没有已编译的合约',
'udapp.loadContractFromAddress': '加载此地址的合约',
'udapp.deployedContracts': '已部署的合约',
'udapp.deployAndRunClearInstances': '清空合约实例并重置交易记录',
'udapp.deployAndRunNoInstanceText': '当前您没有可交互的合约实例.',
'udapp.transactionsRecorded': '已记录交易',
'udapp.transactionsRecorded': '已记录交易',
'search.displayName': '全文搜索',
'search.replace': '替换',
@ -163,9 +167,9 @@ export default {
'solidity.swarmLocation': '可以找到所有元数据信息的Swarm url (首先需要发布合约) ',
'pluginManager.displayName': '插件管理',
'pluginManager.activate': '激活',
'pluginManager.activate': '启用',
'pluginManager.deactivate': '停用',
'pluginManager.activeModules': '激活的模块',
'pluginManager.activeModules': '启用的模块',
'pluginManager.inactiveModules': '停用的模块',
'pluginManager.connectLocal': '连接本地插件',
'pluginManager.localForm.title': '本地插件',

@ -1,9 +1,6 @@
var csjs = require('csjs-inject')
const css = csjs`
.debuggerTabView {
padding: 2%;
}
.debugger {
margin-bottom: 1%;
}

@ -11,6 +11,7 @@ const themes = [
{ name: 'Midcentury', quality: 'light', url: 'assets/css/themes/remix-midcentury_hrzph3.css' },
{ name: 'Black', quality: 'dark', url: 'assets/css/themes/remix-black_undtds.css' },
{ name: 'Candy', quality: 'light', url: 'assets/css/themes/remix-candy_ikhg4m.css' },
{ name: 'HackerOwl', quality: 'dark', url: 'assets/css/themes/remix-hacker_owl.css' },
{ name: 'Cerulean', quality: 'light', url: 'assets/css/themes/bootstrap-cerulean.min.css' },
{ name: 'Flatly', quality: 'light', url: 'assets/css/themes/bootstrap-flatly.min.css' },

@ -30,8 +30,10 @@ export class Web3ProviderModule extends Plugin {
// see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129
provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => {
if (error) {
this.call('terminal', 'log', error.data ? error.data : error, 'error')
return reject(error.data ? error.data : error)
const errorData = error.data ? error.data : error.message
// See: https://github.com/ethers-io/ethers.js/issues/901
if (!(typeof errorData === 'string' && errorData.includes("unknown method eth_chainId"))) this.call('terminal', 'log', error.data ? error.data : error.message)
return reject(errorData)
}
if (payload.method === 'eth_sendTransaction') {
if (payload.params.length && !payload.params[0].to && message.result) {

@ -34,6 +34,7 @@
--dark:#343a40;
--body-bg: #fff;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;

@ -9,8 +9,7 @@
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@400;
700&display=swap);
*/@import url(https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap);
:root {
--blue:#2a9fd6;
--indigo:#6610f2;
@ -36,6 +35,7 @@
--dark:#adafae;
--body-bg: #060606;
--text-bg-mark: #fcf8e3;
--custom-select: #fff;
--breakpoint-xs:0;
--breakpoint-sm:576px;
--breakpoint-md:768px;

@ -30,7 +30,9 @@
--info:#3399f3;
--warning:#d47500;
--danger:#cd0200;
--text:#eee;
--text: #343a40;
/* --text:#eee; */
/* --text-background: #; */
--light:#eee;
--dark:#333;
--body-bg:#fff;

@ -1,13 +1,13 @@
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans:400,600&display=swap');
:root {
--blue: #28282D;
--blue: #90c3f6;
--indigo: #6610f2;
--purple: #9e77f6;
--pink: #f38abb;
--red: #823a3a;
--orange: #e46b15;
--yellow: #ffc107;
--green: #065337;
--green: #1ea171;
--teal: #20c997;
--cyan: #053c64;
--white: #d5d5d5;
@ -15,7 +15,7 @@
--gray-dark: #343a40;
--primary: #b5b4bc;
--secondary: #3d3e44;
--success: #366a57;
--success: #6bceaa;
--info: #086CB5;
--warning: #c26829;
--danger: #823a3a;

File diff suppressed because it is too large Load Diff

@ -6135,7 +6135,7 @@ button.bg-primary:focus {
}
.bg-secondary {
background-color: #a8b3bc !important;
background-color: #d7dee3 !important;
}
a.bg-secondary:hover,

@ -45,7 +45,7 @@ function isElectron() {
return false
}
const versionUrl = isElectron() ? 'https://remix.ethereum.org/assets/version.json' : 'assets/version.json'
const versionUrl = 'assets/version.json'
fetch(versionUrl, { cache: "no-store" }).then(response => {
response.text().then(function (data) {
const version = JSON.parse(data);

@ -0,0 +1,251 @@
T__0=1
T__1=2
T__2=3
T__3=4
T__4=5
T__5=6
T__6=7
T__7=8
T__8=9
T__9=10
T__10=11
T__11=12
T__12=13
T__13=14
T__14=15
T__15=16
T__16=17
T__17=18
T__18=19
T__19=20
T__20=21
T__21=22
T__22=23
T__23=24
T__24=25
T__25=26
T__26=27
T__27=28
T__28=29
T__29=30
T__30=31
T__31=32
T__32=33
T__33=34
T__34=35
T__35=36
T__36=37
T__37=38
T__38=39
T__39=40
T__40=41
T__41=42
T__42=43
T__43=44
T__44=45
T__45=46
T__46=47
T__47=48
T__48=49
T__49=50
T__50=51
T__51=52
T__52=53
T__53=54
T__54=55
T__55=56
T__56=57
T__57=58
T__58=59
T__59=60
T__60=61
T__61=62
T__62=63
T__63=64
T__64=65
T__65=66
T__66=67
T__67=68
T__68=69
T__69=70
T__70=71
T__71=72
T__72=73
T__73=74
T__74=75
T__75=76
T__76=77
T__77=78
T__78=79
T__79=80
T__80=81
T__81=82
T__82=83
T__83=84
T__84=85
T__85=86
T__86=87
T__87=88
T__88=89
T__89=90
T__90=91
T__91=92
T__92=93
T__93=94
T__94=95
T__95=96
T__96=97
Int=98
Uint=99
Byte=100
Fixed=101
Ufixed=102
BooleanLiteral=103
DecimalNumber=104
HexNumber=105
NumberUnit=106
HexLiteralFragment=107
ReservedKeyword=108
AnonymousKeyword=109
BreakKeyword=110
ConstantKeyword=111
ImmutableKeyword=112
ContinueKeyword=113
LeaveKeyword=114
ExternalKeyword=115
IndexedKeyword=116
InternalKeyword=117
PayableKeyword=118
PrivateKeyword=119
PublicKeyword=120
VirtualKeyword=121
PureKeyword=122
TypeKeyword=123
ViewKeyword=124
GlobalKeyword=125
ConstructorKeyword=126
FallbackKeyword=127
ReceiveKeyword=128
Identifier=129
StringLiteralFragment=130
VersionLiteral=131
WS=132
COMMENT=133
LINE_COMMENT=134
'pragma'=1
';'=2
'*'=3
'||'=4
'^'=5
'~'=6
'>='=7
'>'=8
'<'=9
'<='=10
'='=11
'as'=12
'import'=13
'from'=14
'{'=15
','=16
'}'=17
'abstract'=18
'contract'=19
'interface'=20
'library'=21
'is'=22
'('=23
')'=24
'error'=25
'using'=26
'for'=27
'struct'=28
'modifier'=29
'function'=30
'returns'=31
'event'=32
'enum'=33
'['=34
']'=35
'address'=36
'.'=37
'mapping'=38
'=>'=39
'memory'=40
'storage'=41
'calldata'=42
'if'=43
'else'=44
'try'=45
'catch'=46
'while'=47
'unchecked'=48
'assembly'=49
'do'=50
'return'=51
'throw'=52
'emit'=53
'revert'=54
'var'=55
'bool'=56
'string'=57
'byte'=58
'++'=59
'--'=60
'new'=61
':'=62
'+'=63
'-'=64
'after'=65
'delete'=66
'!'=67
'**'=68
'/'=69
'%'=70
'<<'=71
'>>'=72
'&'=73
'|'=74
'=='=75
'!='=76
'&&'=77
'?'=78
'|='=79
'^='=80
'&='=81
'<<='=82
'>>='=83
'+='=84
'-='=85
'*='=86
'/='=87
'%='=88
'let'=89
':='=90
'=:'=91
'switch'=92
'case'=93
'default'=94
'->'=95
'callback'=96
'override'=97
'anonymous'=109
'break'=110
'constant'=111
'immutable'=112
'continue'=113
'leave'=114
'external'=115
'indexed'=116
'internal'=117
'payable'=118
'private'=119
'public'=120
'virtual'=121
'pure'=122
'type'=123
'view'=124
'global'=125
'constructor'=126
'fallback'=127
'receive'=128

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -43,8 +43,7 @@ export class ExecutionContext {
}
askPermission () {
// metamask
if (ethereum && typeof ethereum.enable === 'function') ethereum.enable()
if (ethereum && typeof ethereum.request === "function") ethereum.request({ method: "eth_requestAccounts" });
}
getProvider () {

@ -41,6 +41,7 @@
<script type="text/javascript" src="assets/js/loader.js"></script>
<script src="https://kit.fontawesome.com/41dd021e94.js" crossorigin="anonymous"></script>
<script type="text/javascript" src="assets/js/intro.min.js"></script>
<script type="text/javascript" src="assets/js/parser/antlr.js"></script>
</body>
</html>

@ -1,75 +0,0 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../package.json'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const profile = {
name: 'offsetToLineColumnConverter',
methods: ['offsetToLineColumn'],
events: [],
version: packageJson.version
}
export class OffsetToLineColumnConverter extends Plugin {
constructor () {
super(profile)
this.lineBreakPositionsByContent = {}
this.sourceMappingDecoder = sourceMappingDecoder
}
/**
* Convert offset representation with line/column representation.
* This is also used to resolve the content:
* @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index.
* So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index.
* @param {{start, length}} rawLocation - offset location
* @param {number} file - The index where to find the source in the sources parameters
* @param {Object.<string, {content}>} sources - Map of content sources
* @param {Object.<string, {ast, id}>} asts - Map of content sources
*/
offsetToLineColumn (rawLocation, file, sources, asts) {
if (!this.lineBreakPositionsByContent[file]) {
const sourcesArray = Object.keys(sources)
if (!asts || (file === 0 && sourcesArray.length === 1)) {
// if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12)
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content)
} else {
for (var filename in asts) {
const source = asts[filename]
if (source.id === file) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content)
break
}
}
}
}
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}
/**
* Convert offset representation with line/column representation.
* @param {{start, length}} rawLocation - offset location
* @param {number} file - The index where to find the source in the sources parameters
* @param {string} content - source
*/
offsetToLineColumnWithContent (rawLocation, file, content) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content)
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
}
/**
* Clear the cache
*/
clear () {
this.lineBreakPositionsByContent = {}
}
/**
* called by plugin API
*/
activate () {
this.on('solidity', 'compilationFinished', () => {
this.clear()
})
}
}

@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider',
'compileAndRun', 'search', 'recorder', 'fileDecorator']
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser']
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['hardhat', 'truffle', 'slither']

@ -219,13 +219,14 @@ export const CompilerApiMixin = (Base) => class extends Base {
this.data.loading = true
this.data.loadingUrl = url
this.statusChanged({ key: 'loading', title: 'loading compiler...', type: 'info' })
this.emit('loadingCompiler', url)
}
this.compiler.event.register('loadingCompiler', this.data.eventHandlers.onLoadingCompiler)
this.data.eventHandlers.onCompilerLoaded = (version) => {
this.data.eventHandlers.onCompilerLoaded = (version, license) => {
this.data.loading = false
this.statusChanged({ key: 'none' })
this.emit('compilerLoaded', version)
this.emit('compilerLoaded', version, license)
}
this.compiler.event.register('compilerLoaded', this.data.eventHandlers.onCompilerLoaded)

@ -0,0 +1,43 @@
# vyper-remix
Vyper Plugin for Remix IDE.
## How to get started
### Remote plugin
This plugin is hosted at https://vyper.remixproject.org .
To use it, open the plugin manager in Remix and click the `Activate` button in front of the `Vyper` button in the plugin section.
### Local plugin
You can host this plugin in your local environment.
```git clone https://github.com/ethereum/remix-project```
```cd remix-project```
```yarn install```
```nx build vyper```
```nx serve vyper```
## How to use plugin
1. Write vyper code(.vy) in the editor
2. Click Compile button
3. Now you can deploy the contract in the Run tab!
## Load example contracts
It is possible to clone the Vyper repository in Remix in order to use example contracts. Click on `Clone Vyper Repository`.
Once it is cloned, you will find the contract in the `examples` folder.
### Local Vyper Compiler
You can use your local Vyper compiler by selecting the radio button `Local` .
First, you need to install Vyper. It is strongly recommended to install Vyper in a virtual Python environment.
```pip3 install vyper```
(see [installing-vyper](https://vyper.readthedocs.io/en/latest/installing-vyper.html#installing-vyper)).
Then, Vyper compiler starts with this command (default: http://localhost:8000).
```vyper-serve```

@ -49,7 +49,7 @@ html, body, #root, main {
}
#compile-btn {
width: 90%;
width: 100%;
}
#compile-btn * {
@ -60,7 +60,6 @@ html, body, #root, main {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
padding-top: 10px;

@ -10,6 +10,7 @@ import VyperResult from './components/VyperResult'
import LocalUrlInput from './components/LocalUrl'
import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'
import ToggleButton from 'react-bootstrap/ToggleButton'
import Button from 'react-bootstrap/Button'
import vyperLogo from './logo.svg'
import './app.css'
@ -39,11 +40,14 @@ const App: React.FC = () => {
try {
await remixClient.loaded()
remixClient.onFileChange(name => setContract(name))
const name = await remixClient.getContractName()
setContract(name)
remixClient.onNoFileSelected(() => setContract(''))
} catch (err) {
console.log(err)
}
try {
const name = await remixClient.getContractName() // throw if no file are selected
setContract(name)
} catch (e) {}
}
start()
}, [])
@ -65,20 +69,25 @@ const App: React.FC = () => {
return (
<main id="vyper-plugin">
<header className="bg-light">
<header>
<div className="title">
<img src={vyperLogo} alt="Vyper logo" />
<h4>yper Compiler</h4>
</div>
<a
rel="noopener noreferrer"
href="https://github.com/GrandSchtroumpf/vyper-remix"
href="https://github.com/ethereum/remix-project/tree/master/apps/vyper"
target="_blank"
>
<i className="fab fa-github"></i>
</a>
</header>
<section>
<div className="px-4 w-100">
<Button data-id="add-repository" className="w-100 text-dark w-100 bg-light btn-outline-primary " onClick={() => remixClient.cloneVyperRepo()}>
Clone Vyper examples repository
</Button>
</div>
<ToggleButtonGroup
name="remote"
onChange={setEnvironment}
@ -86,7 +95,7 @@ const App: React.FC = () => {
value={state.environment}
>
<ToggleButton data-id="remote-compiler" variant="secondary" name="remote" value="remote">
Remote Compiler
Remote Compiler v0.2.16
</ToggleButton>
<ToggleButton data-id="local-compiler" variant="secondary" name="local" value="local">
Local Compiler
@ -98,7 +107,7 @@ const App: React.FC = () => {
environment={state.environment}
/>
<WarnRemote environment={state.environment} />
<div id="compile-btn">
<div className="px-4" id="compile-btn">
<CompilerButton
compilerUrl={compilerUrl()}
contract={contract}
@ -107,7 +116,7 @@ const App: React.FC = () => {
}
/>
</div>
<article id="result">
<article id="result" className="px-2">
<VyperResult output={contract ? output[contract] : undefined} />
</article>
</section>

@ -28,22 +28,60 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
/** Compile a Contract */
async function compileContract() {
try {
const _contract = await remixClient.getContract()
await remixClient.discardHighlight()
let _contract: any
try {
_contract = await remixClient.getContract()
} catch (e: any) {
setOutput('', { status: 'failed', message: e.message})
return
}
remixClient.changeStatus({
key: 'loading',
type: 'info',
title: 'Compiling'
})
const output = await compile(compilerUrl, _contract)
let output
try {
output = await compile(compilerUrl, _contract)
} catch (e: any) {
setOutput(_contract.name, { status: 'failed', message: e.message})
return
}
setOutput(_contract.name, output)
// ERROR
if (isCompilationError(output)) {
const line = output.line
if (line) {
const lineColumnPos = {
start: { line: line - 1 },
end: { line: line - 1 }
start: { line: line - 1, column: 10 },
end: { line: line - 1, column: 10 }
}
remixClient.highlight(lineColumnPos as any, _contract.name, '#e0b4b4')
} else {
const regex = output.message.match(/line ((\d+):(\d+))+/g)
const errors = output.message.split(/line ((\d+):(\d+))+/g) // extract error message
if (regex) {
let errorIndex = 0
regex.map((errorLocation) => {
const location = errorLocation.replace('line ', '').split(':')
let message = errors[errorIndex]
errorIndex = errorIndex + 4
if (message && message.split('\n\n').length > 0) {
try {
message = message.split('\n\n')[message.split('\n\n').length - 1]
} catch (e) {}
}
if (location.length > 0) {
const lineColumnPos = {
start: { line: parseInt(location[0]) - 1, column: 10 },
end: { line: parseInt(location[0]) - 1, column: 10 }
}
remixClient.highlight(lineColumnPos as any, _contract.name, message)
}
})
}
}
throw new Error(output.message)
}
// SUCCESS
@ -61,13 +99,13 @@ function CompilerButton({ contract, setOutput, compilerUrl }: Props) {
type: 'error',
title: err.message
})
console.error(err)
}
}
return (
<Button data-id="compile" onClick={compileContract} variant="primary">
Compile {contract}
<Button data-id="compile" onClick={compileContract} variant="primary" title={contract} className="d-flex flex-column">
<span>Compile</span>
<span className="overflow-hidden text-truncate text-nowrap" >{contract}</span>
</Button>
)
}

@ -20,13 +20,13 @@ function LocalUrlInput({ url, setUrl, environment }: Props) {
return (
<Form id="local-url">
<Form.Group controlId="localUrl">
<Form.Text className="text-warning pb-2">Currently we support vyper version > 0.2.16</Form.Text>
<Form.Label>Local Compiler Url</Form.Label>
<Form.Control onBlur={updateUrl}
defaultValue={url}
type="email"
placeholder="eg http://localhost:8000/compile" />
<Form.Text className="text-muted">
The url to your local compiler
</Form.Text>
</Form.Group>
</Form>

@ -3,45 +3,50 @@ import {
VyperCompilationResult,
VyperCompilationOutput,
isCompilationError,
remixClient
} from '../utils';
import Tabs from 'react-bootstrap/Tabs'
import Tab from 'react-bootstrap/Tab'
import { Ballot } from '../examples/ballot';
import Button from 'react-bootstrap/Button';
import JSONTree from 'react-json-view'
import { CopyToClipboard } from '@remix-ui/clipboard'
interface VyperResultProps {
output?: VyperCompilationOutput;
}
export type ExampleContract = {
name: string,
address: string
}
function VyperResult({ output }: VyperResultProps) {
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi');
const [ active, setActive ] = useState<keyof VyperCompilationResult>('abi')
if (!output) return (
<div id="result">
<p>No contract compiled yet.</p>
<Button data-id="add-ballot" variant="info" onClick={() => remixClient.loadContract(Ballot)}>
Create Ballot.vy example
</Button>
<p className="my-3">No contract compiled yet.</p>
</div>
)
if (isCompilationError(output)) {
return (
<div id="result" className="error">
<div id="result" className="error" title={output.message}>
<i className="fas fa-exclamation-circle text-danger"></i>
<p data-id="error-message" className="alert alert-danger">{output.message}</p>
</div>)
<pre data-id="error-message" className="px-2 w-100 alert alert-danger" style={{
fontSize: "0.5rem",
overflowX: "hidden",
textOverflow: "ellipsis"
}}>{output.message}</pre>
</div>
)
}
return (
<Tabs id="result" activeKey={active} onSelect={(key: any) => setActive(key)}>
<Tab eventKey="abi" title="ABI">
<CopyToClipboard getContent={() => JSON.stringify(output.abi)}>
<Button variant="info" className="copy">Copy ABI</Button>
<Button variant="info" className="copy" data-id="copy-abi">Copy ABI</Button>
</CopyToClipboard>
<JSONTree src={output.abi} />
</Tab>

@ -11,8 +11,7 @@ function WarnRemoteLabel({ environment }: Props) {
}
return (
<div className="alert alert-warning">It is really important to not use the remote compiler for production environment.
Please only use it for testing purpose and prefer to using a local compiler for production like environment.</div>
<small className="mx-4 text-warning pb-4">The remote compiler should only be used for testing NOT for production environments. For production, use a local compiler.</small>
)
}

@ -1,161 +0,0 @@
export const Ballot = {
name: 'browser/ballot.vy',
content: `# Voting with delegation.
# Information about voters
struct Voter:
# weight is accumulated by delegation
weight: int128
# if true, that person already voted (which includes voting by delegating)
voted: bool
# person delegated to
delegate: address
# index of the voted proposal, which is not meaningful unless 'voted' is True.
vote: int128
# Users can create proposals
struct Proposal:
# short name (up to 32 bytes)
name: bytes32
# number of accumulated votes
voteCount: int128
voters: public(map(address, Voter))
proposals: public(map(int128, Proposal))
voterCount: public(int128)
chairperson: public(address)
int128Proposals: public(int128)
@public
@constant
def delegated(addr: address) -> bool:
return self.voters[addr].delegate != ZERO_ADDRESS
@public
@constant
def directlyVoted(addr: address) -> bool:
return self.voters[addr].voted and (self.voters[addr].delegate == ZERO_ADDRESS)
# Setup global variables
@public
def __init__(_proposalNames: bytes32[2]):
self.chairperson = msg.sender
self.voterCount = 0
for i in range(2):
self.proposals[i] = Proposal({
name: _proposalNames[i],
voteCount: 0
})
self.int128Proposals += 1
# Give a 'voter' the right to vote on this ballot.
# This may only be called by the 'chairperson'.
@public
def giveRightToVote(voter: address):
# Throws if the sender is not the chairperson.
assert msg.sender == self.chairperson
# Throws if the voter has already voted.
assert not self.voters[voter].voted
# Throws if the voter's voting weight isn't 0.
assert self.voters[voter].weight == 0
self.voters[voter].weight = 1
self.voterCount += 1
# Used by 'delegate' below, and can be called by anyone.
@public
def forwardWeight(delegate_with_weight_to_forward: address):
assert self.delegated(delegate_with_weight_to_forward)
# Throw if there is nothing to do:
assert self.voters[delegate_with_weight_to_forward].weight > 0
target: address = self.voters[delegate_with_weight_to_forward].delegate
for i in range(4):
if self.delegated(target):
target = self.voters[target].delegate
# The following effectively detects cycles of length <= 5,
# in which the delegation is given back to the delegator.
# This could be done for any int128ber of loops,
# or even infinitely with a while loop.
# However, cycles aren't actually problematic for correctness;
# they just result in spoiled votes.
# So, in the production version, this should instead be
# the responsibility of the contract's client, and this
# check should be removed.
assert target != delegate_with_weight_to_forward
else:
# Weight will be moved to someone who directly voted or
# hasn't voted.
break
weight_to_forward: int128 = self.voters[delegate_with_weight_to_forward].weight
self.voters[delegate_with_weight_to_forward].weight = 0
self.voters[target].weight += weight_to_forward
if self.directlyVoted(target):
self.proposals[self.voters[target].vote].voteCount += weight_to_forward
self.voters[target].weight = 0
# To reiterate: if target is also a delegate, this function will need
# to be called again, similarly to as above.
# Delegate your vote to the voter 'to'.
@public
def delegate(to: address):
# Throws if the sender has already voted
assert not self.voters[msg.sender].voted
# Throws if the sender tries to delegate their vote to themselves or to
# the default address value of 0x0000000000000000000000000000000000000000
# (the latter might not be problematic, but I don't want to think about it).
assert to != msg.sender
assert to != ZERO_ADDRESS
self.voters[msg.sender].voted = True
self.voters[msg.sender].delegate = to
# This call will throw if and only if this delegation would cause a loop
# of length <= 5 that ends up delegating back to the delegator.
self.forwardWeight(msg.sender)
# Give your vote (including votes delegated to you)
# to proposal 'proposals[proposal].name'.
@public
def vote(proposal: int128):
# can't vote twice
assert not self.voters[msg.sender].voted
# can only vote on legitimate proposals
assert proposal < self.int128Proposals
self.voters[msg.sender].vote = proposal
self.voters[msg.sender].voted = True
# transfer msg.sender's weight to proposal
self.proposals[proposal].voteCount += self.voters[msg.sender].weight
self.voters[msg.sender].weight = 0
# Computes the winning proposal taking all
# previous votes into account.
@public
@constant
def winningProposal() -> int128:
winning_vote_count: int128 = 0
winning_proposal: int128 = 0
for i in range(2):
if self.proposals[i].voteCount > winning_vote_count:
winning_vote_count = self.proposals[i].voteCount
winning_proposal = i
return winning_proposal
# Calls winningProposal() function to get the index
# of the winner contained in the proposals array and then
# returns the name of the winner
@public
@constant
def winnerName() -> bytes32:
return self.proposals[self.winningProposal()].name
`
}

@ -18,8 +18,8 @@ export interface VyperCompilationResult {
export interface VyperCompilationError {
status: 'failed'
column: number
line: number
column?: number
line?: number
message: string
}

@ -3,6 +3,7 @@ import { Api, Status } from '@remixproject/plugin-utils';
import { createClient } from '@remixproject/plugin-webview'
import { PluginClient } from '@remixproject/plugin';
import { Contract } from './compiler';
import { ExampleContract } from '../components/VyperResult';
export class RemixClient extends PluginClient {
private client = createClient<Api, Readonly<RemixApi>>(this);
@ -14,34 +15,70 @@ export class RemixClient extends PluginClient {
/** Emit an event when file changed */
async onFileChange(cb: (contract: string) => any) {
this.client.on('fileManager', 'currentFileChanged', async (name: string) => {
if (!name) return
cb(name)
})
}
/** Emit an event when file changed */
async onNoFileSelected(cb: () => any) {
this.client.on('fileManager', 'noFileSelected', async () => {
cb()
})
}
/** Load Ballot contract example into the file manager */
async loadContract({name, content}: Contract) {
async loadContract({name, address}: ExampleContract) {
try {
await this.client.call('fileManager', 'setFile', name, content)
await this.client.call('fileManager', 'switchFile', name)
const content = await this.client.call('contentImport', 'resolve', address)
await this.client.call('fileManager', 'setFile', content.cleanUrl, content.content)
await this.client.call('fileManager', 'switchFile', content.cleanUrl)
} catch (err) {
console.log(err)
}
}
async cloneVyperRepo() {
try {
// @ts-ignore
this.call('notification', 'toast', 'cloning Vyper repository...')
await this.call('manager', 'activatePlugin', 'dGitProvider')
// @ts-ignore
await this.call('dGitProvider', 'clone', { url: 'https://github.com/vyperlang/vyper', token: null }, 'vyper-lang')
// @ts-ignore
this.call('notification', 'toast', 'Vyper repository cloned, the workspace Vyper has been created.')
} catch (e) {
// @ts-ignore
this.call('notification', 'toast', e.message)
}
}
/** Update the status of the plugin in remix */
changeStatus(status: Status) {
this.client.emit('statusChanged', status);
}
/** Highlight a part of the editor */
highlight(lineColumnPos: HighlightPosition, name: string, color: string) {
return this.client.call('editor', 'highlight', lineColumnPos, name, color)
async highlight(lineColumnPos: HighlightPosition, name: string, message: string) {
await this.client.call('editor', 'highlight', lineColumnPos, name)
/*
column: -1
row: -1
text: "browser/Untitled1.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.↵"
type: "warning"
*/
const annotation = {
column: 0,
row: lineColumnPos.start.line,
type: 'error',
text: message
}
await this.client.call('editor', 'addAnnotation', annotation, name)
}
/** Remove current Hightlight */
discardHighlight() {
return this.client.call('editor', 'discardHighlight')
async discardHighlight() {
await this.client.call('editor', 'discardHighlight')
await this.client.call('editor', 'clearAnnotations')
}
/** Get the name of the current contract */

@ -0,0 +1,4 @@
const nxPreset = require('@nrwl/jest/preset');
module.exports = { ...nxPreset }

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.24",
"version": "0.5.25",
"description": "Tool to perform static analysis on Solidity smart contracts",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -22,8 +22,8 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.45",
"@remix-project/remix-lib": "^0.5.15",
"@remix-project/remix-astwalker": "^0.0.46",
"@remix-project/remix-lib": "^0.5.16",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2",
@ -52,5 +52,5 @@
"typescript": "^3.7.5"
},
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -168,7 +168,7 @@ export interface ContractDefinitionAstNode {
nodeType: 'ContractDefinition'
src: string
name: string
documentation: string | null
documentation: string | null | StructuredDocumentationAstNode
contractKind: 'interface' | 'contract' | 'library'
abstract: boolean
fullyImplemented: boolean
@ -241,7 +241,7 @@ export interface FunctionDefinitionAstNode {
nodeType: 'FunctionDefinition'
src: string
name: string
documentation: string | null
documentation: string | null | StructuredDocumentationAstNode
kind: string
stateMutability: 'pure' | 'view' | 'nonpayable' | 'payable'
visibility: string
@ -281,7 +281,7 @@ export interface ModifierDefinitionAstNode {
nodeType: 'ModifierDefinition'
src: string
name: string
documentation: Record<string, unknown> | null
documentation: Record<string, unknown> | null | StructuredDocumentationAstNode
visibility: string
parameters: ParameterListAstNode
virtual: boolean
@ -303,7 +303,7 @@ export interface EventDefinitionAstNode {
nodeType: 'EventDefinition'
src: string
name: string
documentation: Record<string, unknown> | null
documentation: Record<string, unknown> | null | StructuredDocumentationAstNode
parameters: ParameterListAstNode
anonymous: boolean
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.45",
"version": "0.0.46",
"description": "Tool to walk through Solidity AST",
"main": "src/index.js",
"scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.15",
"@remix-project/remix-lib": "^0.5.16",
"@types/tape": "^4.2.33",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
@ -54,5 +54,5 @@
"tap-spec": "^5.0.0"
},
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -3,7 +3,6 @@ export { CompilerMetadata } from './lib/compiler-metadata'
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts'
export { EditorContextListener } from './lib/editor-context-listener'
export { GistHandler } from './lib/gist-handler'
export * from './types/contract'
export { LinkLibraries, DeployLibraries } from './lib/link-libraries'

@ -1,244 +0,0 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const profile = {
name: 'contextualListener',
methods: ['referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf'],
events: [],
version: '0.0.1'
}
/*
trigger contextChanged(nodes)
*/
export class EditorContextListener extends Plugin {
_index: any
_activeHighlights: Array<any>
astWalker: any
currentPosition: any
currentFile: string
nodes: Array<any>
results: any
estimationObj: any
creationCost: any
codeDepositCost: any
contract: any
activated: boolean
constructor (astWalker) {
super(profile)
this.activated = false
this._index = {
Declarations: {},
FlatReferences: {}
}
this._activeHighlights = []
this.astWalker = astWalker
}
onActivation () {
this.on('editor', 'contentChanged', () => { this._stopHighlighting() })
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data, input, version) => {
if (languageVersion.indexOf('soljson') !== 0) return
this._stopHighlighting()
this._index = {
Declarations: {},
FlatReferences: {}
}
this._buildIndex(data, source)
})
setInterval(async () => {
const compilationResult = await this.call('compilerArtefacts', 'getLastCompilationResult')
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
let currentFile
try {
currentFile = await this.call('fileManager', 'file')
} catch (error) {
if (error.message !== 'Error: No such file or directory No file selected') throw error
}
this._highlightItems(
await this.call('editor', 'getCursorPosition'),
compilationResult,
currentFile
)
}
}, 1000)
this.activated = true
}
getActiveHighlights () {
return [...this._activeHighlights]
}
declarationOf (node) {
if (node && node.referencedDeclaration) {
return this._index.FlatReferences[node.referencedDeclaration]
}
return null
}
referencesOf (node) {
return this._index.Declarations[node.id]
}
async _highlightItems (cursorPosition, compilationResult, file) {
if (this.currentPosition === cursorPosition) return
this._stopHighlighting()
this.currentPosition = cursorPosition
this.currentFile = file
if (compilationResult && compilationResult.data && compilationResult.data.sources && compilationResult.data.sources[file]) {
const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file])
this.nodes = nodes
if (nodes && nodes.length && nodes[nodes.length - 1]) {
await this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
}
this.emit('contextChanged', nodes)
}
}
_buildIndex (compilationResult, source) {
if (compilationResult && compilationResult.sources) {
const callback = (node) => {
if (node && node.referencedDeclaration) {
if (!this._index.Declarations[node.referencedDeclaration]) {
this._index.Declarations[node.referencedDeclaration] = []
}
this._index.Declarations[node.referencedDeclaration].push(node)
}
this._index.FlatReferences[node.id] = node
}
for (const s in compilationResult.sources) {
this.astWalker.walkFull(compilationResult.sources[s].ast, callback)
}
}
}
async _highlight (node, compilationResult) {
if (!node) return
const position = sourceMappingDecoder.decode(node.src)
const fileTarget = compilationResult.getSourceName(position.file)
const nodeFound = this._activeHighlights.find((el) => el.fileTarget === fileTarget && el.position.file === position.file && el.position.length === position.length && el.position.start === position.start)
if (nodeFound) return // if the content is already highlighted, do nothing.
await this._highlightInternal(position, node, compilationResult)
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
this._activeHighlights.push({ position, fileTarget, nodeId: node.id })
}
}
async _highlightInternal (position, node, compilationResult) {
if (node.nodeType === 'Block') return
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
let lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, position.file, compilationResult.getSourceCode().sources, compilationResult.getAsts())
if (node.nodes && node.nodes.length) {
// If node has children, highlight the entire line. if not, just highlight the current source position of the node.
lineColumn = {
start: {
line: lineColumn.start.line,
column: 0
},
end: {
line: lineColumn.start.line + 1,
column: 0
}
}
}
const fileName = compilationResult.getSourceName(position.file)
if (fileName) {
return await this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false })
}
}
return null
}
async _highlightExpressions (node, compilationResult) {
const highlights = async (id) => {
if (this._index.Declarations && this._index.Declarations[id]) {
const refs = this._index.Declarations[id]
for (const ref in refs) {
const node = refs[ref]
await this._highlight(node, compilationResult)
}
}
}
if (node && node.referencedDeclaration) {
await highlights(node.referencedDeclaration)
const current = this._index.FlatReferences[node.referencedDeclaration]
await this._highlight(current, compilationResult)
} else {
await highlights(node.id)
await this._highlight(node, compilationResult)
}
this.results = compilationResult
}
_stopHighlighting () {
this.call('editor', 'discardHighlight')
this.emit('stopHighlighting')
this._activeHighlights = []
}
gasEstimation (node) {
this._loadContractInfos(node)
let executionCost, codeDepositCost
if (node.nodeType === 'FunctionDefinition') {
const visibility = node.visibility
if (node.kind !== 'constructor') {
const fnName = node.name
const fn = fnName + this._getInputParams(node)
if (visibility === 'public' || visibility === 'external') {
executionCost = this.estimationObj === null ? '-' : this.estimationObj.external[fn]
} else if (visibility === 'private' || visibility === 'internal') {
executionCost = this.estimationObj === null ? '-' : this.estimationObj.internal[fn]
}
} else {
executionCost = this.creationCost
codeDepositCost = this.codeDepositCost
}
} else {
executionCost = '-'
}
return { executionCost, codeDepositCost }
}
_loadContractInfos (node) {
const path = (this.nodes.length && this.nodes[0].absolutePath) || this.results.source.target
for (const i in this.nodes) {
if (this.nodes[i].id === node.scope) {
const contract = this.nodes[i]
this.contract = this.results.data.contracts[path][contract.name]
if (contract) {
this.estimationObj = this.contract.evm.gasEstimates
this.creationCost = this.estimationObj === null ? '-' : this.estimationObj.creation.totalCost
this.codeDepositCost = this.estimationObj === null ? '-' : this.estimationObj.creation.codeDepositCost
}
}
}
}
_getInputParams (node) {
const params = []
const target = node.parameters
// for (const i in node.children) {
// if (node.children[i].name === 'ParameterList') {
// target = node.children[i]
// break
// }
// }
if (target) {
const children = target.parameters
for (const j in children) {
if (children[j].nodeType === 'VariableDeclaration') {
params.push(children[j].typeDescriptions.typeString)
}
}
}
return '(' + params.toString() + ')'
}
}

@ -3,10 +3,14 @@
"compilerOptions": {
"module": "commonjs",
"outDir": "../../dist/out-tsc",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"declaration": true,
"rootDir": "./src",
"types": ["node"]
},
"exclude": ["**/*.spec.ts"],
"exclude": [
"**/*.spec.ts",
"tests/"
],
"include": ["**/*.ts"]
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.5.15",
"version": "0.5.16",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -22,9 +22,9 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.45",
"@remix-project/remix-lib": "^0.5.15",
"@remix-project/remix-simulator": "^0.2.15",
"@remix-project/remix-astwalker": "^0.0.46",
"@remix-project/remix-lib": "^0.5.16",
"@remix-project/remix-simulator": "^0.2.16",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"color-support": "^1.1.3",
@ -68,5 +68,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -15,7 +15,7 @@ export class DebuggerStepManager {
this.event = new EventManager()
this.debugger = _debugger
this.traceManager = traceManager
this.currentStepIndex = 0
this.currentStepIndex = -1
this.traceLength = 0
this.codeTraceLength = 0
this.revertionPoint = null
@ -34,13 +34,18 @@ export class DebuggerStepManager {
this.traceLength = newLength
this.codeTraceLength = this.calculateCodeLength()
}
this.jumpTo(0)
setTimeout(() => {
this.jumpTo(0) // wait for the ui to render
}, 500)
})
})
this.debugger.callTree.event.register('callTreeReady', () => {
if (this.debugger.callTree.functionCallStack.length) {
this.jumpTo(this.debugger.callTree.functionCallStack[0])
setTimeout(() => {
this.jumpTo(this.debugger.callTree.functionCallStack[0]) // wait for the ui to be render
}, 500)
}
})

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.5.15",
"version": "0.5.16",
"description": "Library to various Remix tools",
"contributors": [
{
@ -54,5 +54,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -1,9 +1,8 @@
'use strict'
import { each } from 'async'
import { ethers } from 'ethers'
import { toBuffer, addHexPrefix } from 'ethereumjs-util'
import { EventManager } from '../eventManager'
import { compareByteCode } from '../util'
import { compareByteCode, getinputParameters } from '../util'
import { decodeResponse } from './txFormat'
import { getFunction, getReceiveInterface, getConstructorInterface, visitContracts, makeFullTypeDefinition } from './txHelper'
@ -34,7 +33,6 @@ export class TxListener {
_listenOnNetwork:boolean
_loopId
blocks
lastBlock
constructor (opt, executionContext) {
this.event = new EventManager()
@ -107,8 +105,7 @@ export class TxListener {
addExecutionCosts(txResult, tx, execResult)
tx.envMode = this.executionContext.getProvider()
tx.status = txResult.receipt.status // 0x0 or 0x1
this._resolve([tx], () => {
})
this._resolve([tx])
})
})
}
@ -123,9 +120,7 @@ export class TxListener {
if (this._loopId) {
clearInterval(this._loopId)
}
if (this._listenOnNetwork) {
this._startListenOnNetwork()
}
this._listenOnNetwork ? this.startListening() : this.stopListening()
}
/**
@ -133,7 +128,6 @@ export class TxListener {
*/
init () {
this.blocks = []
this.lastBlock = -1
}
/**
@ -164,34 +158,54 @@ export class TxListener {
this._isListening = false
}
_startListenOnNetwork () {
this._loopId = setInterval(() => {
async _startListenOnNetwork () {
let lastSeenBlock = this.executionContext.lastBlock?.number - 1
let processingBlock = false
const processBlocks = async () => {
if (!this._isListening) return
if (processingBlock) return
processingBlock = true
const currentLoopId = this._loopId
this.executionContext.web3().eth.getBlockNumber((error, blockNumber) => {
if (this._loopId === null) return
if (error) return console.log(error)
if (currentLoopId === this._loopId && blockNumber > this.lastBlock) {
let current = this.lastBlock + 1
this.lastBlock = blockNumber
while (blockNumber >= current) {
if (this._loopId === null) {
processingBlock = false
return
}
if (!lastSeenBlock) {
lastSeenBlock = this.executionContext.lastBlock?.number // trying to resynchronize
console.log('listen on blocks, resynchronising')
processingBlock = false
return
}
const current = this.executionContext.lastBlock?.number
if (!current) {
console.log(new Error('no last block found'))
processingBlock = false
return
}
if (currentLoopId === this._loopId && lastSeenBlock < current) {
while (lastSeenBlock <= current) {
try {
this._manageBlock(current)
if (!this._isListening) break
await this._manageBlock(lastSeenBlock)
} catch (e) {
console.log(e)
}
current++
lastSeenBlock++
}
lastSeenBlock = current
}
})
}, 2000)
processingBlock = false
}
_manageBlock (blockNumber) {
this.executionContext.web3().eth.getBlock(blockNumber, true, (error, result) => {
if (!error) {
this._newBlock(Object.assign({ type: 'web3' }, result))
this._loopId = setInterval(processBlocks, 20000)
processBlocks()
}
})
async _manageBlock (blockNumber) {
try {
const result = await this.executionContext.web3().eth.getBlock(blockNumber, true)
return await this._newBlock(Object.assign({ type: 'web3' }, result))
} catch (e) {}
}
/**
@ -215,31 +229,37 @@ export class TxListener {
return this._resolvedTransactions[txHash]
}
_newBlock (block) {
async _newBlock (block) {
this.blocks.push(block)
this._resolve(block.transactions, () => {
await this._resolve(block.transactions)
this.event.trigger('newBlock', [block])
})
}
_resolve (transactions, callback) {
each(transactions, (tx, cb) => {
_resolveAsync (tx) {
return new Promise((resolve, reject) => {
this._api.resolveReceipt(tx, (error, receipt) => {
if (error) return cb(error)
if (error) return reject(error)
this._resolveTx(tx, receipt, (error, resolvedData) => {
if (error) cb(error)
if (error) return reject(error)
if (resolvedData) {
this.event.trigger('txResolved', [tx, receipt, resolvedData])
}
this.event.trigger('newTransaction', [tx, receipt])
cb()
resolve({})
})
})
}, () => {
callback()
})
}
async _resolve (transactions) {
for (const tx of transactions) {
try {
if (!this._isListening) break
await this._resolveAsync(tx)
} catch (e) {}
}
}
_resolveTx (tx, receipt, cb) {
const contracts = this._api.contracts()
if (!contracts) return cb()
@ -332,7 +352,7 @@ export class TxListener {
const bytecode = contract.object.evm.bytecode.object
let params = null
if (bytecode && bytecode.length) {
params = this._decodeInputParams(inputData.substring(bytecode.length), getConstructorInterface(abi))
params = this._decodeInputParams(getinputParameters(inputData), getConstructorInterface(abi))
}
this._resolvedTransactions[tx.hash] = {
contractName: contract.name,

@ -375,5 +375,7 @@ export const ConsoleLogs = {
3982404743: '(address,address,address,uint)',
4161329696: '(address,address,address,string)',
238520724: '(address,address,address,bool)',
1717301556: '(address,address,address,address)'
1717301556: '(address,address,address,address)',
4133908826: '(uint,uint)',
3054400204: '(string,uint)'
}

@ -184,6 +184,15 @@ export function cborEncodedValueExtraction () {
return /64697066735822([0-9a-f]{68})64736f6c6343([0-9a-f]{6})0033$/
}
/**
* return a regex which extract the input parameters from the bytecode
*
* @return {RegEx}
*/
export function inputParametersExtraction () {
return /64697066735822[0-9a-f]{68}64736f6c6343[0-9a-f]{6}0033(.*)$/
}
export function extractcborMetadata (value) {
return value.replace(cborEncodedValueExtraction(), '')
}
@ -195,6 +204,18 @@ export function extractSwarmHash (value) {
return value
}
export function extractinputParameters (value) {
return value.replace(inputParametersExtraction(), '')
}
export function getinputParameters (value) {
const regex = value.match(inputParametersExtraction())
if (regex && regex[1]) {
return regex[1]
} else
return ''
}
/**
* Compare bytecode. return true if the code is equal (handle swarm hash and library references)
* @param {String} code1 - the bytecode that is actually deployed (contains resolved library reference and a potentially different swarmhash)
@ -218,14 +239,16 @@ export function compareByteCode (code1, code2) {
code2 = replaceLibReference(code2, pos)
code1 = replaceLibReference(code1, pos)
}
code1 = extractinputParameters(code1)
code1 = extractSwarmHash(code1)
code1 = extractcborMetadata(code1)
code2 = extractinputParameters(code2)
code2 = extractSwarmHash(code2)
code2 = extractcborMetadata(code2)
if (code1 && code2) {
const compare = stringSimilarity.compareTwoStrings(code1, code2)
return compare > 0.93
return compare == 1
}
return false

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.2.15",
"version": "0.2.16",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -18,7 +18,7 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.15",
"@remix-project/remix-lib": "^0.5.16",
"ansi-gray": "^0.1.1",
"async": "^3.1.0",
"body-parser": "^1.18.2",
@ -67,5 +67,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.5.1",
"version": "0.5.2",
"description": "Tool to load and run Solidity compiler",
"main": "src/index.js",
"types": "src/index.d.ts",
@ -18,7 +18,7 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.15",
"@remix-project/remix-lib": "^0.5.16",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10",
@ -61,5 +61,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -33,7 +33,8 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli
}
self.postMessage({
cmd: 'versionLoaded',
data: compiler.version()
data: compiler.version(),
license: compiler.license()
})
break
}
@ -44,6 +45,7 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli
self.postMessage({
cmd: 'compiled',
job: data.job,
timestamp: data.timestamp,
data: compileJSON(data.input),
input: data.input,
missingInputs: missingInputs

@ -9,7 +9,7 @@ import {
Source, SourceWithTarget, MessageFromWorker, CompilerState, CompilationResult,
visitContractsCallbackParam, visitContractsCallbackInterface, CompilationError,
gatherImportsCallbackInterface,
isFunctionDescription
isFunctionDescription, CompilerRetriggerMode
} from './types'
/*
@ -25,6 +25,7 @@ export class Compiler {
compileJSON: null,
worker: null,
currentVersion: null,
compilerLicense: null,
optimize: false,
runs: 200,
evmVersion: null,
@ -33,6 +34,7 @@ export class Compiler {
target: null,
useFileConfiguration: false,
configFileContent: '',
compilerRetriggerMode: CompilerRetriggerMode.none,
lastCompilationResult: {
data: null,
source: null
@ -47,7 +49,6 @@ export class Compiler {
})
this.event.register('compilationStarted', () => {
this.state.compilationStartTime = new Date().getTime()
})
}
@ -85,6 +86,7 @@ export class Compiler {
compile(files: Source, target: string): void {
this.state.target = target
this.state.compilationStartTime = new Date().getTime()
this.event.trigger('compilationStarted', [])
this.internalCompile(files)
}
@ -94,9 +96,10 @@ export class Compiler {
* @param version compiler version
*/
onCompilerLoaded (version: string): void {
onCompilerLoaded (version: string, license: string): void {
this.state.currentVersion = version
this.event.trigger('compilerLoaded', [version])
this.state.compilerLicense = license
this.event.trigger('compilerLoaded', [version, license])
}
/**
@ -131,7 +134,7 @@ export class Compiler {
}
this.onCompilationFinished(result, missingInputs, source, input, this.state.currentVersion)
}
this.onCompilerLoaded(compiler.version())
this.onCompilerLoaded(compiler.version(), compiler.license())
}
}
@ -184,6 +187,7 @@ export class Compiler {
if (err) {
console.error('Error in loading remote solc compiler: ', err)
} else {
let license
this.state.compileJSON = (source: SourceWithTarget) => {
const missingInputs: string[] = []
const missingInputsCallback = (path: string) => {
@ -203,13 +207,14 @@ export class Compiler {
}
result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback }))
license = remoteCompiler.license()
}
} catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
}
this.onCompilationFinished(result, missingInputs, source, input, version)
}
this.onCompilerLoaded(version)
this.onCompilerLoaded(version, license)
}
})
}
@ -271,9 +276,12 @@ export class Compiler {
this.state.worker.addEventListener('message', (msg: Record<'data', MessageFromWorker>) => {
const data: MessageFromWorker = msg.data
if (this.state.compilerRetriggerMode == CompilerRetriggerMode.retrigger && data.timestamp !== this.state.compilationStartTime) {
return
}
switch (data.cmd) {
case 'versionLoaded':
if (data.data) this.onCompilerLoaded(data.data)
if (data.data && data.license) this.onCompilerLoaded(data.data, data.license)
break
case 'compiled':
{
@ -322,7 +330,8 @@ export class Compiler {
this.state.worker.postMessage({
cmd: 'compile',
job: jobs.length - 1,
input: input
input: input,
timestamp: this.state.compilationStartTime
})
}
}

@ -154,10 +154,16 @@ export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | '
export type Language = 'Solidity' | 'Yul'
export enum CompilerRetriggerMode {
'none' ,
'retrigger'
}
export interface CompilerState {
compileJSON: ((input: SourceWithTarget) => void) | null,
worker: any,
currentVersion: string| null| undefined,
compilerLicense: string| null
optimize: boolean,
runs: number
evmVersion: EVMVersion| null,
@ -166,6 +172,7 @@ export interface CompilerState {
target: string | null,
useFileConfiguration: boolean,
configFileContent: string,
compilerRetriggerMode: CompilerRetriggerMode,
lastCompilationResult: {
data: CompilationResult | null,
source: SourceWithTarget | null | undefined
@ -182,14 +189,17 @@ export interface MessageToWorker {
job?: number,
input?: CompilerInput,
data?: string
timestamp?: number
}
export interface MessageFromWorker {
cmd: string,
license?: string,
job?: number,
missingInputs?: string[],
input?: any,
data?: string
timestamp?: number
}
export interface visitContractsCallbackParam {

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-tests",
"version": "0.2.15",
"version": "0.2.16",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
@ -35,14 +35,15 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-tests#readme",
"dependencies": {
"@erebos/bzz-node": "^0.13.0",
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.15",
"@remix-project/remix-simulator": "^0.2.15",
"@remix-project/remix-solidity": "^0.5.1",
"@remix-project/remix-url-resolver": "^0.0.36",
"@remix-project/remix-lib": "^0.5.16",
"@remix-project/remix-simulator": "^0.2.16",
"@remix-project/remix-solidity": "^0.5.2",
"@remix-project/remix-url-resolver": "^0.0.37",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": ">=0.21.1",
@ -80,5 +81,5 @@
"typescript": "^3.3.1"
},
"typings": "src/index.d.ts",
"gitHead": "714a13ef29c4c5b018e25b49270809f2ea456b08"
"gitHead": "0c1957c9b2f890076a5062309bc81b41f93af57c"
}

@ -134,7 +134,7 @@ export function compileFileOrFiles (filename: string, isDirectory: boolean, opts
if (runs) compiler.set('runs', runs)
if (currentCompilerUrl) {
compiler.loadRemoteVersion(currentCompilerUrl)
compiler.event.register('compilerLoaded', this, function (version) {
compiler.event.register('compilerLoaded', this, function (version, license) {
next()
})
} else {
@ -198,7 +198,7 @@ export function compileContractSources (sources: SrcIfc, newCompConfig: any, imp
compiler.set('runs', runs)
compiler.loadVersion(usingWorker, currentCompilerUrl)
// @ts-ignore
compiler.event.register('compilerLoaded', this, (version) => {
compiler.event.register('compilerLoaded', this, (version, license) => {
next()
})
} else {

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useEffect, useState, useRef } from 'react'
import Draggable from 'react-draggable'
import './dragbar.css'
@ -7,18 +7,43 @@ interface IRemixDragBarUi {
setHideStatus: (hide: boolean) => void;
hidden: boolean
minWidth: number
maximiseTrigger: number
resetTrigger: number
}
const DragBar = (props: IRemixDragBarUi) => {
const [dragState, setDragState] = useState<boolean>(false)
const [dragBarPosX, setDragBarPosX] = useState<number>(0)
const [offset, setOffSet] = useState<number>(0)
const initialWidth = useRef<number>(props.minWidth)
const nodeRef = React.useRef(null) // fix for strictmode
useEffect(() => {
setDragBarPosX(offset + (props.hidden ? 0 : props.refObject.current.offsetWidth))
}, [props.hidden, offset])
useEffect(() => {
initialWidth.current = props.refObject.current.clientWidth
if (props.maximiseTrigger > 0) {
const width = 0.4 * window.innerWidth
if (width > props.refObject.current.offsetWidth) {
props.refObject.current.style.width = width + 'px'
setTimeout(() => {
setDragBarPosX(offset + width)
}, 300)
}
}
}, [props.maximiseTrigger])
useEffect(() => {
if (props.maximiseTrigger > 0) {
props.refObject.current.style.width = initialWidth.current + 'px'
setTimeout(() => {
setDragBarPosX(offset + initialWidth.current)
}, 300)
}
}, [props.resetTrigger])
const handleResize = () => {
setOffSet(props.refObject.current.offsetLeft)
setDragBarPosX(props.refObject.current.offsetLeft + props.refObject.current.offsetWidth)
@ -39,9 +64,12 @@ const DragBar = (props: IRemixDragBarUi) => {
props.setHideStatus(true)
} else {
props.refObject.current.style.width = (data.x - offset) + 'px'
setTimeout(() => {
props.setHideStatus(false)
setDragBarPosX(offset + props.refObject.current.offsetWidth)
}, 300)
}
}
function startDrag() {

@ -18,6 +18,8 @@ interface IRemixAppUi {
const RemixApp = (props: IRemixAppUi) => {
const [appReady, setAppReady] = useState<boolean>(false)
const [hideSidePanel, setHideSidePanel] = useState<boolean>(false)
const [maximiseTrigger, setMaximiseTrigger] = useState<number>(0)
const [resetTrigger, setResetTrigger] = useState<number>(0)
const [locale, setLocale] = useState<{ name:string; messages:any }>({ name:'', messages:{} });
const sidePanelRef = useRef(null)
@ -51,6 +53,18 @@ const RemixApp = (props: IRemixAppUi) => {
setHideSidePanel(true)
}, 1000)
})
props.app.layout.event.on('maximisesidepanel', () => {
setMaximiseTrigger(prev => {
return prev + 1
})
})
props.app.layout.event.on('resetsidepanel', () => {
setResetTrigger(prev => {
return prev + 1
})
})
props.app.localeModule.events.on('localeChanged', (nextLocale) => {
setLocale(nextLocale)
})
@ -73,7 +87,7 @@ const RemixApp = (props: IRemixAppUi) => {
<div className={`remixIDE ${appReady ? '' : 'd-none'}`} data-id="remixIDE">
<div id="icon-panel" data-id="remixIdeIconPanel" className="iconpanel bg-light">{props.app.menuicons.render()}</div>
<div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}>{props.app.sidePanel.render()}</div>
<DragBar minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
<DragBar resetTrigger={resetTrigger} maximiseTrigger={maximiseTrigger} minWidth={250} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
<div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel'>
<RemixUIMainPanel Context={AppContext}></RemixUIMainPanel>
</div>

@ -32,6 +32,7 @@ pre {
display : flex;
flex-direction : row-reverse;
width : 320px;
transition : width 0.25s;
}
.highlightcode {

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

Loading…
Cancel
Save