Merge branch 'master' of https://github.com/ethereum/remix-project into nxwebpack

pull/3233/head
filip mertens 2 years ago
commit 6d1f0680c1
  1. 80
      CONTRIBUTING.md
  2. 13
      apps/debugger/src/app/debugger-api.ts
  3. 4
      apps/debugger/src/app/debugger.ts
  4. 1
      apps/remix-ide-e2e/src/local-plugin/src/app/app.tsx
  5. 141
      apps/remix-ide-e2e/src/tests/code_format.test.ts
  6. 8
      apps/remix-ide-e2e/src/tests/generalSettings.test.ts
  7. 1
      apps/remix-ide-e2e/src/tests/libraryDeployment.test.ts
  8. 14
      apps/remix-ide-e2e/src/tests/plugin_api.ts
  9. 12
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  10. 2
      apps/remix-ide-e2e/src/tests/workspace.test.ts
  11. 10
      apps/remix-ide/src/app/editor/editor.js
  12. 4
      apps/remix-ide/src/app/files/fileManager.ts
  13. 211
      apps/remix-ide/src/app/plugins/code-format.ts
  14. 2
      apps/remix-ide/src/app/plugins/file-decorator.ts
  15. 20
      apps/remix-ide/src/app/plugins/parser/code-parser.tsx
  16. 53
      apps/remix-ide/src/app/tabs/debugger-tab.js
  17. 26
      apps/remix-ide/src/app/tabs/locale-module.js
  18. 196
      apps/remix-ide/src/app/tabs/locales/en-US.js
  19. 8
      apps/remix-ide/src/app/tabs/locales/en/debugger.json
  20. 38
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  21. 59
      apps/remix-ide/src/app/tabs/locales/en/home.json
  22. 23
      apps/remix-ide/src/app/tabs/locales/en/index.js
  23. 6
      apps/remix-ide/src/app/tabs/locales/en/panel.json
  24. 31
      apps/remix-ide/src/app/tabs/locales/en/pluginManager.json
  25. 14
      apps/remix-ide/src/app/tabs/locales/en/search.json
  26. 26
      apps/remix-ide/src/app/tabs/locales/en/settings.json
  27. 37
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  28. 15
      apps/remix-ide/src/app/tabs/locales/en/terminal.json
  29. 24
      apps/remix-ide/src/app/tabs/locales/en/udapp.json
  30. 196
      apps/remix-ide/src/app/tabs/locales/zh-CN.js
  31. 8
      apps/remix-ide/src/app/tabs/locales/zh/debugger.json
  32. 38
      apps/remix-ide/src/app/tabs/locales/zh/filePanel.json
  33. 59
      apps/remix-ide/src/app/tabs/locales/zh/home.json
  34. 26
      apps/remix-ide/src/app/tabs/locales/zh/index.js
  35. 6
      apps/remix-ide/src/app/tabs/locales/zh/panel.json
  36. 31
      apps/remix-ide/src/app/tabs/locales/zh/pluginManager.json
  37. 14
      apps/remix-ide/src/app/tabs/locales/zh/search.json
  38. 26
      apps/remix-ide/src/app/tabs/locales/zh/settings.json
  39. 37
      apps/remix-ide/src/app/tabs/locales/zh/solidity.json
  40. 15
      apps/remix-ide/src/app/tabs/locales/zh/terminal.json
  41. 24
      apps/remix-ide/src/app/tabs/locales/zh/udapp.json
  42. 18
      apps/remix-ide/src/app/tabs/settings-tab.tsx
  43. BIN
      apps/remix-ide/src/assets/audio/remiGuitar-single-power-chord-A-minor.mp3
  44. BIN
      apps/remix-ide/src/assets/img/bgRemi_small.webp
  45. BIN
      apps/remix-ide/src/assets/img/remixRewardBetaTester_small.webp
  46. BIN
      apps/remix-ide/src/assets/img/remixRewardUser_small.webp
  47. BIN
      apps/remix-ide/src/assets/img/solidity-logo.webp
  48. 41
      apps/remix-ide/src/blockchain/blockchain.js
  49. 8
      libs/remix-analyzer/package.json
  50. 6
      libs/remix-astwalker/package.json
  51. 13
      libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts
  52. 4
      libs/remix-core-plugin/src/lib/constants/uups.ts
  53. 4
      libs/remix-core-plugin/src/lib/openzeppelin-proxy.ts
  54. 11
      libs/remix-debug/package.json
  55. 55
      libs/remix-debug/src/Ethdebugger.ts
  56. 24
      libs/remix-debug/src/code/breakpointManager.ts
  57. 7
      libs/remix-debug/src/debugger/debugger.ts
  58. 5
      libs/remix-debug/src/debugger/solidityState.ts
  59. 28
      libs/remix-debug/src/debugger/stepManager.ts
  60. 3
      libs/remix-debug/src/solidity-decoder/decodeInfo.ts
  61. 86
      libs/remix-debug/src/solidity-decoder/internalCallTree.ts
  62. 80
      libs/remix-debug/src/solidity-decoder/solidityProxy.ts
  63. 21
      libs/remix-debug/test/debugger.ts
  64. 44
      libs/remix-debug/test/decoder/localDecoder.ts
  65. 20
      libs/remix-debug/test/decoder/localsTests/calldata.ts
  66. 7
      libs/remix-debug/test/decoder/localsTests/int.ts
  67. 20
      libs/remix-debug/test/decoder/localsTests/misc.ts
  68. 21
      libs/remix-debug/test/decoder/localsTests/misc2.ts
  69. 25
      libs/remix-debug/test/decoder/localsTests/structArray.ts
  70. 40
      libs/remix-debug/test/decoder/stateTests/mapping.ts
  71. 4
      libs/remix-debug/test/decoder/storageDecoder.ts
  72. 4
      libs/remix-lib/package.json
  73. 6
      libs/remix-simulator/package.json
  74. 6
      libs/remix-solidity/package.json
  75. 2
      libs/remix-solidity/src/compiler/compiler-input.ts
  76. 23
      libs/remix-tests/jest.config.js
  77. 19
      libs/remix-tests/package.json
  78. 154
      libs/remix-tests/tests/testRunner.cli.spec.ts
  79. 5
      libs/remix-tests/tsconfig.json
  80. 4
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  81. 36
      libs/remix-ui/checkbox/src/lib/remix-ui-checkbox.tsx
  82. 1
      libs/remix-ui/debugger-ui/src/lib/button-navigator/button-navigator.tsx
  83. 29
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  84. 9
      libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts
  85. 1
      libs/remix-ui/debugger-ui/src/lib/slider/slider.tsx
  86. 2
      libs/remix-ui/debugger-ui/src/lib/step-manager/step-manager.tsx
  87. 6
      libs/remix-ui/debugger-ui/src/lib/tx-browser/tx-browser.tsx
  88. 40
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  89. 14
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  90. 3
      libs/remix-ui/helper/src/lib/components/custom-tooltip.tsx
  91. 3
      libs/remix-ui/helper/src/types/customtooltip.ts
  92. 50
      libs/remix-ui/home-tab/src/lib/components/homeTabFeatured.tsx
  93. 15
      libs/remix-ui/home-tab/src/lib/components/homeTabFeaturedPlugins.tsx
  94. 10
      libs/remix-ui/home-tab/src/lib/components/homeTabFile.tsx
  95. 20
      libs/remix-ui/home-tab/src/lib/components/homeTabGetStarted.tsx
  96. 41
      libs/remix-ui/home-tab/src/lib/components/homeTabLearn.tsx
  97. 14
      libs/remix-ui/home-tab/src/lib/components/homeTabScamAlert.tsx
  98. 40
      libs/remix-ui/home-tab/src/lib/components/homeTabTitle.tsx
  99. 4
      libs/remix-ui/home-tab/src/lib/remix-ui-home-tab.css
  100. 26
      libs/remix-ui/locale-module/src/lib/remix-ui-locale-module.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

@ -11,7 +11,85 @@ When you add a code in any library, please ensure you add related tests. You can
Please conform to [standard](https://standardjs.com/) for code styles.
## Submitting Pull Request
## Submitting Pull Request
Please follow GitHub's standard model of making changes & submitting pull request which is very well explained [here](https://guides.github.com/activities/forking/). Make sure your code works fine locally before submitting a pull request.
## Internationalization
Remix now supports Internationalization. Everyone is welcome to contribute to this feature.
### How to make a string support intl?
First, put the string in the locale file located under `apps/remix-ide/src/app/tabs/locales/en`.
Each json file corresponds to a module. If the module does not exist, then create a new json and import it in the `index.js`.
Then you can replace the string with a intl component. The `id` prop will be the key of this string.
```jsx
<label className="py-2 align-self-center m-0" style={{fontSize: "1.2rem"}}>
- Learn
+ <FormattedMessage id="home.learn" />
</label>
```
In some cases, jsx maybe not acceptable, you can use `intl.formatMessage` .
```jsx
<input
ref={searchInputRef}
type="text"
className="border form-control border-right-0"
id="searchInput"
- placeholder="Search Documentation"
+ placeholder={intl.formatMessage({ id: "home.searchDocumentation" })}
data-id="terminalInputSearch"
/>
```
### How to add another language support?
Let's say you want to add French.
First, create a folder named by the language code which is `fr`.
Then, create a json file, let's say `panel.json`,
```json
{
"panel.author": "Auteur",
"panel.maintainedBy": "Entretenu par",
"panel.documentation": "Documentation",
"panel.description": "La description"
}
```
Then, create a `index.js` file like this,
```js
import panelJson from './panel.json';
import enJson from '../en';
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...panelJson,
})
```
Then, import `index.js` in `apps/remix-ide/src/app/tabs/locale-module.js`
```js
import enJson from './locales/en'
import zhJson from './locales/zh'
+import frJson from './locales/fr'
const locales = [
{ code: 'en', name: 'English', localeName: 'English', messages: enJson },
{ code: 'zh', name: 'Chinese Simplified', localeName: '简体中文', messages: zhJson },
+ { code: 'fr', name: 'French', localeName: 'Français', messages: frJson },
]
```
You can find the language's `code, name, localeName` in this link
https://github.com/ethereum/ethereum-org-website/blob/dev/i18n/config.json
### Whether or not to use `defaultMessage`?
If you search `FormattedMessage` or `intl.formatMessage` in this project, you will notice that most of them only have a `id` prop, but a few of them have a `defaultMessage` prop.
**Why?**
Each non-english language will be filled in the gaps with english. Even there maybe some un-translated content, it will always use english as defaultMessage. That why we don't need to provide a `defaultMessage` prop each time we render a `FormattedMessage` component.
But in some cases, the `id` prop may not be static. For example,
```jsx
<h6 className="pt-0 mb-1" data-id='sidePanelSwapitTitle'>
<FormattedMessage id={plugin?.profile.name + '.displayName'} defaultMessage={plugin?.profile.displayName || plugin?.profile.name} />
</h6>
```
You can't be sure there is a match key in locale file or not. So it will be better to provide a `defaultMessage` prop.

@ -6,6 +6,7 @@ import { lineText } from '@remix-ui/editor'
export const DebuggerApiMixin = (Base) => class extends Base {
initialWeb3
debuggerBackend
initDebuggerApi () {
const self = this
@ -148,7 +149,11 @@ export const DebuggerApiMixin = (Base) => class extends Base {
if (web3) this._web3 = web3
else this._web3 = this.initialWeb3
init.extendWeb3(this._web3)
if (this.onDebugRequestedListener) this.onDebugRequestedListener(hash, web3)
if (this.onDebugRequestedListener) {
this.onDebugRequestedListener(hash, web3).then((debuggerBackend) => {
this.debuggerBackend = debuggerBackend
})
}
}
onActivation () {
@ -167,12 +172,16 @@ export const DebuggerApiMixin = (Base) => class extends Base {
showMessage (title: string, message: string) {}
onStartDebugging () {
onStartDebugging (debuggerBackend: any) {
this.call('layout', 'maximiseSidePanel')
this.emit('startDebugging')
this.debuggerBackend = debuggerBackend
}
onStopDebugging () {
this.call('layout', 'resetSidePanel')
this.emit('stopDebugging')
this.debuggerBackend = null
}
}

@ -4,7 +4,7 @@ import { IDebuggerApi, LineColumnLocation,
onBreakpointClearedListener, onBreakpointAddedListener, onEditorContentChanged, onEnvChangedListener, TransactionReceipt } from '@remix-ui/debugger-ui'
import { DebuggerApiMixin } from './debugger-api'
import { CompilerAbstract } from '@remix-project/remix-solidity'
export class DebuggerClientApi extends DebuggerApiMixin(PluginClient) {
constructor () {
super()
@ -25,7 +25,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
onStartDebugging: (debuggerBackend: any) => void // called when debug starts
onStopDebugging: () => void // called when debug stops
}

@ -110,6 +110,7 @@ function App () {
placeholder="Enter payload here..."
value={payload}
onChange={handleChange}
data-id="payload-input"
/>
{profiles.map((profile: Profile) => {
const methods = profile.methods.map((method: string) => {

@ -0,0 +1,141 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
},
'open default prettier config and set the content': function (browser: NightwatchBrowser) {
browser.openFile('.prettierrc.json').pause(1000).setEditorValue(
JSON.stringify(defaultPrettierOptions, null, '\t')
)
},
'Add Ballot': function (browser: NightwatchBrowser) {
browser
.addFile('Untitled.sol', { content: unformattedContract })
},
'Should put cursor at "unfomattedContract"': function (browser: NightwatchBrowser) {
const path = "//*[@class='view-line' and contains(.,'unfomattedContract')]"
browser.waitForElementVisible('#editorView')
.click({
locateStrategy: 'xpath',
selector: path
})
},
'Format code': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('f')
}).pause(2000)
.getEditorValue(function (result: string) {
browser.assert.equal(result.trim(), formattedContract)
})
},
'open default prettier config and change the value of tabWdith': function (browser: NightwatchBrowser) {
browser.openFile('.prettierrc.json').pause(1000)
.getEditorValue(function (result: string) {
result = result.replace(/4/g, '2')
browser.setEditorValue(result)
}).pause(4000)
},
'Should put cursor at "unfomattedContract" again': function (browser: NightwatchBrowser) {
browser.openFile('Untitled.sol')
const path = "//*[@class='view-line' and contains(.,'unfomattedContract')]"
browser.waitForElementVisible('#editorView')
.click({
locateStrategy: 'xpath',
selector: path
})
},
'Format code with 2 tabWidth': function (browser: NightwatchBrowser) {
browser
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('f')
}).pause(2000)
.getEditorValue(function (result: string) {
browser.assert.equal(result.trim(), formattedWithTabWidth2)
})
}
}
const unformattedContract = `pragma solidity >=0.4.22 <0.9.0;
contract unfomattedContract {
bytes32[] proposalNames;
function beforeAll () public {
proposalNames.push(bytes32("candidate1"));
ballotToTest = new Ballot(proposalNames);
}
}`
const formattedContract = `pragma solidity >=0.4.22 <0.9.0;
contract unfomattedContract {
bytes32[] proposalNames;
function beforeAll() public {
proposalNames.push(bytes32("candidate1"));
ballotToTest = new Ballot(proposalNames);
}
}`
const formattedWithTabWidth2 = `pragma solidity >=0.4.22 <0.9.0;
contract unfomattedContract {
bytes32[] proposalNames;
function beforeAll() public {
proposalNames.push(bytes32("candidate1"));
ballotToTest = new Ballot(proposalNames);
}
}`
const defaultPrettierOptions = {
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false
}
},
{
"files": "*.yml",
"options": {}
},
{
"files": "*.yaml",
"options": {}
},
{
"files": "*.toml",
"options": {}
},
{
"files": "*.json",
"options": {}
},
{
"files": "*.js",
"options": {}
},
{
"files": "*.ts",
"options": {}
}
]
}

@ -135,9 +135,9 @@ module.exports = {
.checkElementStyle(':root', '--danger', remixIdeThemes.cyborg.danger)
},
'Should load zh-CN locale ': function (browser) {
'Should load zh locale ': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.scrollAndClick('*[data-id="settingsTabLocaleLabelzh-CN"]')
.scrollAndClick('*[data-id="settingsTabLocaleLabelzh"]')
.pause(2000)
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', '设置')
.assert.containsText('*[data-id="listenNetworkCheckInput"]', '监听所有交易')
@ -147,9 +147,9 @@ module.exports = {
.assert.containsText('*[data-id="displayErrorsLabel"]', '编辑代码时展示错误提示')
},
'Should load en-US locale ': function (browser) {
'Should load en locale ': function (browser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]', 5000)
.scrollAndClick('*[data-id="settingsTabLocaleLabelen-US"]')
.scrollAndClick('*[data-id="settingsTabLocaleLabelen"]')
.pause(2000)
.assert.containsText('*[data-id="sidePanelSwapitTitle"]', 'SETTINGS')
.assert.containsText('*[data-id="listenNetworkCheckInput"]', 'listen on all transactions')

@ -21,6 +21,7 @@ module.exports = {
let addressRef: string
browser.verifyContracts(['test'])
.clickLaunchIcon('udapp')
.click('#selectExEnv')
.selectContract('test')
.createContract('')
.getAddressAtPosition(0, (address) => {

@ -50,7 +50,7 @@ const debugValues = async function (browser: NightwatchBrowser, field: string, e
const setPayload = async (browser: NightwatchBrowser, payload: any) => {
return new Promise((resolve) => {
if (typeof payload !== 'string') payload = JSON.stringify(payload)
browser.clearValue('//*[@id="payload"]').setValue('//*[@id="payload"]', payload, (result) => {
browser.clearValue('//*[@id="payload"]').pause(500).setValue('//*[@id="payload"]', payload, (result) => {
resolve(result)
})
})
@ -234,7 +234,8 @@ module.exports = {
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
'README.txt': { isDirectory: false },
'.prettierrc.json': { isDirectory: false },
}, null, '/')
},
'Should throw error on current file #group7': async function (browser: NightwatchBrowser) {
@ -294,7 +295,8 @@ module.exports = {
contracts: { isDirectory: true },
scripts: { isDirectory: true },
tests: { isDirectory: true },
'README.txt': { isDirectory: false }
'README.txt': { isDirectory: false },
'.prettierrc.json': { isDirectory: false }
}, null, '/')
},
'Should get all workspaces #group2': async function (browser: NightwatchBrowser) {
@ -321,14 +323,14 @@ module.exports = {
// DGIT
'Should have changes on new workspace #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'filePanel:createWorkspace', null, null, 'dgit')
await clickAndCheckLog(browser, 'dGitProvider:status', [["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,0],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
await clickAndCheckLog(browser, 'dGitProvider:status', [[".prettierrc.json",0,2,0], ["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,0],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
},
'Should stage contract #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:add', null, null, {
filepath: 'contracts/1_Storage.sol'
})
await clickAndCheckLog(browser, 'dGitProvider:status', [["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,2],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
await clickAndCheckLog(browser, 'dGitProvider:status', [[".prettierrc.json",0,2,0],["README.txt",0,2,0],["contracts/1_Storage.sol",0,2,2],["contracts/2_Owner.sol",0,2,0],["contracts/3_Ballot.sol",0,2,0],["scripts/deploy_with_ethers.ts",0,2,0],["scripts/deploy_with_web3.ts",0,2,0],["scripts/ethers-lib.ts",0,2,0],["scripts/web3-lib.ts",0,2,0],["tests/Ballot_test.sol",0,2,0],["tests/storage.test.js",0,2,0]], null, null)
},
'Should commit changes #group3': async function (browser: NightwatchBrowser) {
await clickAndCheckLog(browser, 'dGitProvider:commit', null, null, { author: { name: 'Remix', email: 'Remix' }, message: 'commit-message' })
@ -419,7 +421,7 @@ module.exports = {
.addFile('test_modal.js', { content: testModalToasterApi })
.executeScriptInTerminal('remix.execute(\'test_modal.js\')')
.useCss()
.waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]', 60000)
.waitForElementVisible('*[data-id="test_id_1_ModalDialogModalBody-react"]', 65000)
.assert.containsText('*[data-id="test_id_1_ModalDialogModalBody-react"]', 'message 1')
.modalFooterOKClick('test_id_1_')
// check the script runner notifications

@ -308,16 +308,22 @@ module.exports = {
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalPassed()', 60000)
// remix_test.sol should be opened in editor
.getEditorValue((content) => browser.assert.ok(content.indexOf('library Assert {') !== -1))
.clickLaunchIcon('solidityUnitTesting')
.click('*[id="debuggerTransactionStartButtonContainer"]') // stop debugging
.openFile('tests/ballotFailedDebug_test.sol')
.pause(2000)
.clickLaunchIcon('solidityUnitTesting').pause(2000)
.waitForElementPresent('#Check_winning_proposal_again')
.scrollAndClick('#Check_winning_proposal_again')
.pause(5000)
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000)
.goToVMTraceStep(1151)
.waitForElementContainsText('*[data-id="functionPanel"]', 'equal(a, b, message)', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalAgain()', 60000)
//.pause(5000)
.clickLaunchIcon('solidityUnitTesting')
.click('*[id="debuggerTransactionStartButtonContainer"]') // stop debugging
.openFile('tests/ballotFailedDebug_test.sol')
.pause(2000)
.clickLaunchIcon('solidityUnitTesting').pause(2000)
.pause(5000)
.scrollAndClick('#Check_winnin_proposal_with_return_value')
.pause(5000)

@ -122,7 +122,7 @@ module.exports = {
const fileList = document.querySelector('*[data-id="treeViewUltreeViewMenu"]')
return fileList.getElementsByTagName('li').length;
}, [], function (result) {
browser.assert.equal(result.value, 0, 'Incorrect number of files');
browser.assert.equal(result.value, 1, 'Incorrect number of files');
});
},

@ -220,7 +220,7 @@ class Editor extends Plugin {
if (pathExists) {
contentDep = await readFile(pathDep)
if (contentDep !== '') {
this.emit('addModel', contentDep, 'typescript', pathDep, false)
this.emit('addModel', contentDep, 'typescript', pathDep, this.readOnlySessions[path])
}
} else {
console.log("The file ", pathDep, " can't be found.")
@ -241,7 +241,7 @@ class Editor extends Plugin {
async _createSession (path, content, mode) {
if (!this.activated) return
this.emit('addModel', content, mode, path, false)
this.emit('addModel', content, mode, path, this.readOnlySessions[path])
return {
path,
language: mode,
@ -266,7 +266,7 @@ class Editor extends Plugin {
}
addModel(path, content) {
this.emit('addModel', content, this._getMode(path), path, false)
this.emit('addModel', content, this._getMode(path), path, this.readOnlySessions[path])
}
/**
@ -301,9 +301,9 @@ 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]) {
this.readOnlySessions[path] = false
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) {
this.sessions[path].setValue(content)
}
@ -317,9 +317,9 @@ class Editor extends Plugin {
*/
async openReadOnly (path, content) {
if (!this.sessions[path]) {
this.readOnlySessions[path] = true
const session = await this._createSession(path, content, this._getMode(path))
this.sessions[path] = session
this.readOnlySessions[path] = true
}
this._switchSession(path)
}

@ -18,7 +18,9 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh', 'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'],
methods: ['closeAllFiles', 'closeFile', 'file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir',
'readdir', 'dirList', 'fileList', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh',
'getProviderOf', 'getProviderByName', 'getPathFromUrl', 'getUrlFromPath', 'saveCurrentFile', 'setBatchFiles', 'isGitRepo'],
kind: 'file-system'
}
const errorMsg = {

@ -6,7 +6,11 @@ import sol from './code-format/index'
import * as ts from 'prettier/parser-typescript'
import * as babel from 'prettier/parser-babel'
import * as espree from 'prettier/parser-espree'
import * as yml from 'prettier/parser-yaml'
import path from 'path'
import yaml from 'js-yaml'
import toml from 'toml'
import { filePathFilter, AnyFilter } from '@jsdevtools/file-path-filter'
const profile = {
name: 'codeFormatter',
@ -16,6 +20,51 @@ const profile = {
version: '0.0.1'
}
const defaultOptions = {
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
}
},
{
"files": "*.yml",
"options": {
}
},
{
"files": "*.yaml",
"options": {
}
},
{
"files": "*.toml",
"options": {
}
},
{
"files": "*.json",
"options": {
}
},
{
"files": "*.js",
"options": {
}
},
{
"files": "*.ts",
"options": {
}
}
]
}
export class CodeFormat extends Plugin {
constructor() {
@ -57,9 +106,143 @@ export class CodeFormat extends Plugin {
case '.json':
parserName = 'json'
break
case '.yml':
parserName = 'yaml'
break
case '.yaml':
parserName = 'yaml'
break
}
if (file === '.prettierrc') {
parserName = 'json'
}
const possibleFileNames = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.toml',
'.prettierrc.js',
'.prettierrc.cjs',
'prettier.config.js',
'prettier.config.cjs',
'.prettierrc.json5',
]
const prettierConfigFile = await findAsync(possibleFileNames, async (fileName) => {
const exists = await this.call('fileManager', 'exists', fileName)
return exists
})
let parsed = null
if (prettierConfigFile) {
let prettierConfig = await this.call('fileManager', 'readFile', prettierConfigFile)
if (prettierConfig) {
if (prettierConfigFile.endsWith('.yaml') || prettierConfigFile.endsWith('.yml')) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.toml')) {
try {
parsed = toml.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile.endsWith('.json') || prettierConfigFile.endsWith('.json5')) {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
} else if (prettierConfigFile === '.prettierrc') {
try {
parsed = JSON.parse(prettierConfig)
} catch (e) {
// do nothing
}
if (!parsed) {
try {
parsed = yaml.load(prettierConfig)
} catch (e) {
// do nothing
}
}
} else if (prettierConfigFile.endsWith('.js') || prettierConfigFile.endsWith('.cjs')) {
// remove any comments
prettierConfig = prettierConfig.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '')
// add quotes to keys
prettierConfig = prettierConfig.replace(/([a-zA-Z0-9_]+)(\s*):/g, '"$1"$2:')
// remove comma from last key
prettierConfig = prettierConfig.replace(/,(\s*})/g, '$1')
// remove any semi-colons
prettierConfig = prettierConfig.replace(/;/g, '')
// convert single quotes to double quotes
prettierConfig = prettierConfig.replace(/'/g, '"')
try {
parsed = JSON.parse(prettierConfig.replace('module.exports = ', '').replace('module.exports=', ''))
} catch (e) {
// do nothing
}
}
}
} else {
parsed = defaultOptions
await this.call('fileManager', 'writeFile', '.prettierrc.json', JSON.stringify(parsed, null, 2))
await this.call('notification', 'toast', 'A prettier config file has been created in the workspace.')
}
if (!parsed && prettierConfigFile) {
this.call('notification', 'toast', `Error parsing prettier config file: ${prettierConfigFile}`)
}
// merge options
if (parsed) {
options = {
...options,
...parsed,
}
}
// search for overrides
if (parsed && parsed.overrides) {
const override = parsed.overrides.find((override) => {
if (override.files) {
const pathFilter: AnyFilter = {}
pathFilter.include = setGlobalExpression(override.files)
const filteredFiles = [file]
.filter(filePathFilter(pathFilter))
if (filteredFiles.length) {
return true
}
}
})
const validParsers = ['typescript', 'babel', 'espree', 'solidity-parse', 'json', 'yaml', 'solidity-parse']
if (override && override.options && override.options.parser) {
if (validParsers.includes(override.options.parser)) {
parserName = override.options.parser
} else {
this.call('notification', 'toast', `Invalid parser: ${override.options.parser}! Valid options are ${validParsers.join(', ')}`)
}
delete override.options.parser
}
if (override) {
options = {
...options,
...override.options,
}
}
}
const result = prettier.format(content, {
plugins: [sol as any, ts, babel, espree],
plugins: [sol as any, ts, babel, espree, yml],
parser: parserName,
...options
})
@ -71,12 +254,22 @@ export class CodeFormat extends Plugin {
}
function getRange(index, node) {
if (node.range) {
return node.range[index];
}
if (node.expression && node.expression.range) {
return node.expression.range[index];
}
return null;
//*.sol, **/*.txt, contracts/*
const setGlobalExpression = (paths: string) => {
const results = []
paths.split(',').forEach(path => {
path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
}
async function findAsync(arr, asyncCallback) {
const promises = arr.map(asyncCallback);
const results = await Promise.all(promises);
const index = results.findIndex(result => result);
return arr[index];
}

@ -12,7 +12,6 @@ const profile = {
events: ['fileDecoratorsChanged'],
version: '0.0.1'
}
export class FileDecorator extends Plugin {
private _fileStates: fileDecoration[] = []
constructor() {
@ -26,7 +25,6 @@ export class FileDecorator extends Plugin {
}
/**
*
* @param fileStates Array of file states
*/
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) {

@ -78,6 +78,8 @@ export class CodeParser extends Plugin {
setCurrentFileAST: (text?: string) => Promise<ParseResult>
getImports: () => Promise<CodeParserImportsData[]>
debuggerIsOn: boolean = false
constructor(astWalker: any) {
super(profile)
@ -98,7 +100,7 @@ export class CodeParser extends Plugin {
}
const showGasSettings = await this.call('config', 'getAppParameter', 'show-gas')
const showErrorSettings = await this.call('config', 'getAppParameter', 'display-errors')
if (showGasSettings || showErrorSettings || completionSettings) {
if (showGasSettings || showErrorSettings || completionSettings || this.debuggerIsOn) {
await this.compilerService.compile()
}
}
@ -159,6 +161,16 @@ export class CodeParser extends Plugin {
this.on('solidity', 'compilerLoaded', async () => {
await this.reload()
})
this.on('debugger', 'startDebugging', async () => {
this.debuggerIsOn = true
await this.reload()
})
this.on('debugger', 'stopDebugging', async () => {
this.debuggerIsOn = false
await this.reload()
})
}
async reload(){
@ -338,7 +350,11 @@ export class CodeParser extends Plugin {
* @returns
*/
async nodesAtPosition(position: number, type = ''): Promise<genericASTNode[]> {
const lastCompilationResult = this.compilerAbstract
let lastCompilationResult = this.compilerAbstract
if(this.debuggerIsOn) {
lastCompilationResult = await this.call('compilerArtefacts', 'get', '__last')
this.currentFile = await this.call('fileManager', 'file')
}
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]) {

@ -1,3 +1,4 @@
import Web3 from 'web3'
import { DebuggerUI } from '@remix-ui/debugger-ui' // eslint-disable-line
import { DebuggerApiMixin } from '@remixproject/debugger-plugin' // eslint-disable-line
import { ViewPlugin } from '@remixproject/engine-web'
@ -10,7 +11,7 @@ const css = require('./styles/debugger-tab-styles')
const profile = {
name: 'debugger',
displayName: 'Debugger',
methods: ['debug', 'getTrace'],
methods: ['debug', 'getTrace', 'decodeLocalVariable', 'decodeStateVariable', 'globalContext'],
events: [],
icon: 'assets/img/debuggerLogo.webp',
description: 'Debug transactions',
@ -51,7 +52,8 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
this.on('fetchAndCompile', 'sourceVerificationNotAvailable', () => {
this.call('notification', 'toast', sourceVerificationNotAvailableToastMsg())
})
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} /></div>
const onReady = (api) => { this.api = api }
return <div className="overflow-hidden px-1" id='debugView'><DebuggerUI debuggerAPI={this} onReady={onReady} /></div>
}
showMessage (title, message) {
@ -66,4 +68,51 @@ export class DebuggerTab extends DebuggerApiMixin(ViewPlugin) {
}
}
async decodeLocalVariable (variableId) {
if (!this.debuggerBackend) return null
return await this.debuggerBackend.debugger.decodeLocalVariableByIdAtCurrentStep(this.debuggerBackend.step_manager.currentStepIndex, variableId)
}
async decodeStateVariable (variableId) {
if (!this.debuggerBackend) return null
return await this.debuggerBackend.debugger.decodeStateVariableByIdAtCurrentStep(this.debuggerBackend.step_manager.currentStepIndex, variableId)
}
async globalContext () {
if (this.api?.globalContext) {
const { tx, block } = await this.api.globalContext()
const blockContext = {
'chainid': tx.chainId,
'coinbase': block.miner,
'difficulty': block.difficulty,
'gaslimit': block.gasLimit,
'number': block.number,
'timestamp': block.timestamp,
}
if (block.baseFeePerGas) {
blockContext['basefee'] = Web3.utils.toBN(block.baseFeePerGas).toString(10) + ` Wei (${block.baseFeePerGas})`
}
const msg = {
'sender': tx.from,
'sig': tx.input.substring(0, 10),
'value': tx.value + ' Wei'
}
const txOrigin = {
'origin': tx.from
}
return {
block: blockContext,
msg,
tx: txOrigin
}
} else {
return {
block: null,
msg: null,
tx: null
}
}
}
}

@ -3,13 +3,13 @@ import { EventEmitter } from 'events'
import { QueryParams } from '@remix-project/remix-lib'
import * as packageJson from '../../../../../package.json'
import Registry from '../state/registry'
import enUS from './locales/en-US'
import zhCN from './locales/zh-CN'
import enJson from './locales/en'
import zhJson from './locales/zh'
const _paq = window._paq = window._paq || []
const locales = [
{ name: 'en-US', messages: enUS },
{ name: 'zh-CN', messages: zhCN },
{ code: 'en', name: 'English', localeName: 'English', messages: enJson },
{ code: 'zh', name: 'Chinese Simplified', localeName: '简体中文', messages: zhJson },
]
const profile = {
@ -29,7 +29,7 @@ export class LocaleModule extends Plugin {
}
this.locales = {}
locales.map((locale) => {
this.locales[locale.name.toLocaleLowerCase()] = locale
this.locales[locale.code.toLocaleLowerCase()] = locale
})
this._paq = _paq
let queryLocale = (new QueryParams()).get().locale
@ -39,7 +39,7 @@ export class LocaleModule extends Plugin {
currentLocale = currentLocale && currentLocale.toLocaleLowerCase()
currentLocale = this.locales[currentLocale] ? currentLocale : null
this.currentLocaleState = { queryLocale, currentLocale }
this.active = queryLocale || currentLocale || 'en-us'
this.active = queryLocale || currentLocale || 'en'
this.forced = !!queryLocale
}
@ -55,20 +55,20 @@ export class LocaleModule extends Plugin {
/**
* Change the current locale
* @param {string} [localeName] - The name of the locale
* @param {string} [localeCode] - The code of the locale
*/
switchLocale (localeName) {
localeName = localeName && localeName.toLocaleLowerCase()
if (localeName && !Object.keys(this.locales).includes(localeName)) {
throw new Error(`Locale ${localeName} doesn't exist`)
switchLocale (localeCode) {
localeCode = localeCode && localeCode.toLocaleLowerCase()
if (localeCode && !Object.keys(this.locales).includes(localeCode)) {
throw new Error(`Locale ${localeCode} doesn't exist`)
}
const next = localeName || this.active // Name
const next = localeCode || this.active // Name
if (next === this.active) return // --> exit out of this method
_paq.push(['trackEvent', 'localeModule', 'switchTo', next])
const nextLocale = this.locales[next] // Locale
if (!this.forced) this._deps.config.set('settings/locale', next)
if (localeName) this.active = localeName
if (localeCode) this.active = localeCode
this.emit('localeChanged', nextLocale)
this.events.emit('localeChanged', nextLocale)
}

@ -1,196 +0,0 @@
export default {
'panel.author': 'Author',
'panel.maintainedBy': 'Maintained By',
'panel.documentation': 'Documentation',
'panel.description': 'Description',
'settings.displayName': 'Settings',
'settings.reset': 'Reset to Default settings',
'settings.general': 'General settings',
'settings.generateContractMetadataText': 'Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.',
'settings.ethereunVMText': 'Always use Javascript VM at load',
'settings.wordWrapText': 'Word wrap in editor',
'settings.useAutoCompleteText': 'Enable code completion in editor.',
'settings.useShowGasInEditorText': 'Display gas estimates in editor.',
'settings.displayErrorsText': 'Display errors in editor while typing.',
'settings.matomoAnalytics': 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about ',
'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.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.',
'settings.save': 'Save',
'settings.remove': 'Remove',
'settings.themes': 'Themes',
'settings.locales': 'Lanaguage',
'settings.swarm': 'Swarm Settings',
'settings.ipfs': 'IPFS Settings',
'filePanel.displayName': 'File explorer',
'filePanel.workspace': 'WORKSPACES',
'filePanel.create': 'Create',
'filePanel.clone': 'Clone',
'filePanel.download': 'Download',
'filePanel.restore': 'Restore',
'filePanel.workspace.create': 'Create Workspace',
'filePanel.workspace.rename': 'Rename Workspace',
'filePanel.workspace.delete': 'Delete Workspace',
'filePanel.workspace.deleteConfirm': 'Are you sure to delete the current workspace?',
'filePanel.workspace.name': 'Workspace name',
'filePanel.workspace.chooseTemplate': 'Choose a template',
'filePanel.workspace.download': 'Download Workspace',
'filePanel.workspace.restore': 'Restore Workspace Backup',
'filePanel.workspace.clone': 'Clone Git Repository',
'filePanel.workspace.enterGitUrl': 'Enter git repository url',
'filePanel.newFile': 'New File',
'filePanel.newFolder': 'New Folder',
'filePanel.rename': 'Rename',
'filePanel.delete': 'Delete',
'filePanel.deleteAll': 'Delete All',
'filePanel.run': 'Run',
'filePanel.pushChangesToGist': 'Push changes to gist',
'filePanel.publishFolderToGist': 'Publish folder to gist',
'filePanel.publishFileToGist': 'Publish file to gist',
'filePanel.copy': 'Copy',
'filePanel.paste': 'Paste',
'filePanel.compile': 'Compile',
'filePanel.compileForNahmii': 'Compile for Nahmii',
'filePanel.createNewFile': 'Create New File',
'filePanel.createNewFolder': 'Create New Folder',
'filePanel.publishToGist': 'Publish all the current workspace files to a github gist',
'filePanel.uploadFile': 'Load a local file into current workspace',
'filePanel.updateGist': 'Update the current [gist] explorer',
'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.files': 'Files',
'home.newFile': 'New File',
'home.openFile': 'Open File',
'home.connectToLocalhost': 'Connect to Localhost',
'home.loadFrom': 'LOAD FROM',
'home.resources': 'Resources',
'terminal.listen': 'listen on all transactions',
'terminal.search': 'Search with transaction hash or address',
'terminal.used': 'used',
'terminal.welcomeText1': 'Welcome to',
'terminal.welcomeText2': 'Your files are stored in',
'terminal.welcomeText3': 'You can use this terminal to',
'terminal.welcomeText4': 'Check transactions details and start debugging',
'terminal.welcomeText5': 'Execute JavaScript scripts',
'terminal.welcomeText6': 'Input a script directly in the command line interface',
'terminal.welcomeText7': 'Select a Javascript file in the file explorer and then run `remix.execute()` or `remix.exeCurrent()` in the command line interface',
'terminal.welcomeText8': 'Right click on a JavaScript file in the file explorer and then click `Run`',
'terminal.welcomeText9': 'The following libraries are accessible',
'terminal.welcomeText10': 'Type the library name to see available commands',
'debugger.displayName': 'Debugger',
'debugger.debuggerConfiguration': 'Debugger Configuration',
'debugger.stopDebugging': 'Stop debugging',
'debugger.startDebugging': 'Start debugging',
'debugger.placeholder': 'Transaction hash, should start with 0x',
'debugger.introduction': `When Debugging with a transaction hash,
if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings.
For supported networks, please see`,
'udapp.displayName': 'Deploy & run transactions',
'udapp.gasLimit': 'Gas limit',
'udapp.account': 'Account',
'udapp.value': 'Value',
'udapp.contract': 'Contract',
'udapp.signAMessage': 'Sign a message',
'udapp.enterAMessageToSign': 'Enter a message to sign',
'udapp.hash': 'hash',
'udapp.signature': 'signature',
'udapp.signedMessage': 'Signed Message',
'udapp.environment': 'Environment',
'udapp.environmentDocs': 'Click for docs about Environment',
'udapp.deploy': 'Deploy',
'udapp.publishTo': 'Publish to',
'udapp.or': 'or',
'udapp.atAddress': 'At Address',
'udapp.noCompiledContracts': 'No compiled contracts',
'udapp.loadContractFromAddress': 'Load contract from Address',
'udapp.deployedContracts': 'Deployed Contracts',
'udapp.deployAndRunClearInstances': 'Clear instances list and reset recorder',
'udapp.deployAndRunNoInstanceText': 'Currently you have no contract instances to interact with.',
'udapp.transactionsRecorded': 'Transactions recorded',
'search.displayName': 'Search in files',
'search.replace': 'Replace',
'search.replaceAll': 'Replace All',
'search.placeholder1': 'Search ( Enter to search )',
'search.placeholder2': 'Include ie *.sol ( Enter to include )',
'search.placeholder3': 'Exclude ie .git/**/* ( Enter to exclude )',
'search.matchCase': 'Match Case',
'search.matchWholeWord': 'Match Whole Word',
'search.useRegularExpression': 'Use Regular Expression',
'search.replaceWithoutConfirmation': 'replace without confirmation',
'search.filesToInclude': 'Files to include',
'search.filesToExclude': 'Files to exclude',
'solidity.displayName': 'Solidity compiler',
'solidity.compiler': 'Compiler',
'solidity.addACustomCompiler': 'Add a custom compiler',
'solidity.addACustomCompilerWithURL': 'Add a custom compiler with URL',
'solidity.includeNightlyBuilds': 'Include nightly builds',
'solidity.autoCompile': 'Auto compile',
'solidity.hideWarnings': 'Hide warnings',
'solidity.enableHardhat': 'Enable Hardhat Compilation',
'solidity.learnHardhat': 'Learn how to use Hardhat Compilation',
'solidity.enableTruffle': 'Enable Truffle Compilation',
'solidity.learnTruffle': 'Learn how to use Truffle Compilation',
'solidity.advancedConfigurations': 'Advanced Configurations',
'solidity.compilerConfiguration': 'Compiler configuration',
'solidity.compilationDetails': 'Compilation Details',
'solidity.language': 'Language',
'solidity.evmVersion': 'EVM Version',
'solidity.enableOptimization': 'Enable optimization',
'solidity.useConfigurationFile': 'Use configuration file',
'solidity.change': 'Change',
'solidity.compile': 'Compile',
'solidity.noFileSelected': 'no file selected',
'solidity.compileAndRunScript': 'Compile and Run script',
'solidity.publishOn': 'Publish on',
'solidity.Assembly': 'Assembly opcodes describing the contract including corresponding solidity source code',
'solidity.Opcodes': 'Assembly opcodes describing the contract',
'solidity.name': 'Name of the compiled contract',
'solidity.metadata': 'Contains all informations related to the compilation',
'solidity.bytecode': 'Bytecode being executed during contract creation',
'solidity.abi': 'ABI: describing all the functions (input/output params, scope, ...)',
'solidity.web3Deploy': 'Copy/paste this code to any JavaScript/Web3 console to deploy this contract',
'solidity.metadataHash': 'Hash representing all metadata information',
'solidity.functionHashes': 'List of declared function and their corresponding hash',
'solidity.gasEstimates': 'Gas estimation for each function call',
'solidity.Runtime Bytecode': 'Bytecode storing the state and being executed during normal contract call',
'solidity.swarmLocation': 'Swarm url where all metadata information can be found (contract needs to be published first)',
'pluginManager.displayName': 'Plugin manager',
'pluginManager.activate': 'Activate',
'pluginManager.deactivate': 'Deactivate',
'pluginManager.activeModules': 'Active Modules',
'pluginManager.inactiveModules': 'Inactive Modules',
'pluginManager.connectLocal': 'Connect to a Local Plugin',
'pluginManager.localForm.title': 'Local Plugin',
'pluginManager.localForm.pluginName': 'Plugin Name',
'pluginManager.localForm.shouldBeCamelCase': 'Should be camelCase',
'pluginManager.localForm.displayName': 'Display Name',
'pluginManager.localForm.nameInTheHeader': 'Name in the header',
'pluginManager.localForm.required': 'required',
'pluginManager.localForm.commaSeparatedMethod': 'comma separated list of method names',
'pluginManager.localForm.commaSeparatedPlugin': 'comma separated list of plugin names',
'pluginManager.localForm.pluginsItCanActivate': 'Plugins it can activate',
'pluginManager.localForm.typeOfConnection': 'Type of connection',
'pluginManager.localForm.locationInRemix': 'Location in remix',
'pluginManager.localForm.sidePanel': 'Side Panel',
'pluginManager.localForm.mainPanel': 'Main Panel',
'pluginManager.localForm.none': 'None',
}

@ -0,0 +1,8 @@
{
"debugger.displayName": "Debugger",
"debugger.debuggerConfiguration": "Debugger Configuration",
"debugger.stopDebugging": "Stop debugging",
"debugger.startDebugging": "Start debugging",
"debugger.placeholder": "Transaction hash, should start with 0x",
"debugger.introduction": "When Debugging with a transaction hash, if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings. For supported networks, please see"
}

@ -0,0 +1,38 @@
{
"filePanel.displayName": "File explorer",
"filePanel.workspace": "WORKSPACES",
"filePanel.create": "Create",
"filePanel.clone": "Clone",
"filePanel.download": "Download",
"filePanel.restore": "Restore",
"filePanel.workspace.create": "Create Workspace",
"filePanel.workspace.rename": "Rename Workspace",
"filePanel.workspace.delete": "Delete Workspace",
"filePanel.workspace.deleteConfirm": "Are you sure to delete the current workspace?",
"filePanel.workspace.name": "Workspace name",
"filePanel.workspace.chooseTemplate": "Choose a template",
"filePanel.workspace.download": "Download Workspace",
"filePanel.workspace.restore": "Restore Workspace Backup",
"filePanel.workspace.clone": "Clone Git Repository",
"filePanel.workspace.enterGitUrl": "Enter git repository url",
"filePanel.workspace.solghaction": "Add the Solidity GitHub Action file. Push to a repository to start running it in the GitHub CI.",
"filePanel.solghaction": "Add Solidity GitHub Action",
"filePanel.newFile": "New File",
"filePanel.newFolder": "New Folder",
"filePanel.rename": "Rename",
"filePanel.delete": "Delete",
"filePanel.deleteAll": "Delete All",
"filePanel.run": "Run",
"filePanel.pushChangesToGist": "Push changes to gist",
"filePanel.publishFolderToGist": "Publish folder to gist",
"filePanel.publishFileToGist": "Publish file to gist",
"filePanel.copy": "Copy",
"filePanel.paste": "Paste",
"filePanel.compile": "Compile",
"filePanel.compileForNahmii": "Compile for Nahmii",
"filePanel.createNewFile": "Create New File",
"filePanel.createNewFolder": "Create New Folder",
"filePanel.publishToGist": "Publish all the current workspace files to a github gist",
"filePanel.uploadFile": "Load a local file into current workspace",
"filePanel.updateGist": "Update the current [gist] explorer"
}

@ -0,0 +1,59 @@
{
"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.more": "More",
"home.featured": "Featured",
"home.jumpIntoWeb3": "JUMP INTO WEB3",
"home.jumpIntoWeb3Text": "The Remix Project is a rich toolset which can be used for the entire journey of contract development by users of any knowledge level, and as a learning lab for teaching and experimenting with Ethereum.",
"home.remixRewards": "REMIX REWARDS",
"home.remixRewardsText1": "NFTs for our users!",
"home.remixRewardsText2": "Remix Project rewards contributors, beta testers, and UX research participants with NFTs deployed on Optimism.",
"home.solidityDevSurveyHeader": "SOLIDITY DEVELOPER SURVEY 2022",
"home.solidityDevSurvey": "Help to collect feedback for the Solidity team. The survey takes roughly 10 minutes",
"home.solidityDevSurvey1": "Annual poll",
"home.surveyLink": "Take the Survey",
"home.betaTesting": "BETA TESTING",
"home.betaTestingText1": "Our community supports us.",
"home.betaTestingText2": "You can join Beta Testing before each release of Remix IDE. Help us test now and get a handle on new features!",
"home.featuredPlugins": "Featured Plugins",
"home.solidityPluginDesc": "Compile, test and analyse smart contract.",
"home.starkNetPluginDesc": "Compile and deploy contracts with Cairo, a native language for StarkNet.",
"home.solhintPluginDesc": "Solhint is an open source project for linting Solidity code.",
"home.sourcifyPluginDesc": "Solidity contract and metadata verification service.",
"home.unitTestPluginDesc": "Write and run unit tests for your contracts in Solidity.",
"home.getStarted": "Get Started",
"home.projectTemplates": "Project Templates",
"home.blankTemplateDesc": "Create an empty workspace.",
"home.remixDefaultTemplateDesc": "Create a workspace with sample files.",
"home.ozerc20TemplateDesc": "Create an ERC20 token by importing OpenZeppelin library.",
"home.ozerc721TemplateDesc": "Create an NFT token by importing OpenZeppelin library.",
"home.zeroxErc20TemplateDesc": "Create an ERC20 token by importing 0xProject contract.",
"home.learn": "Learn",
"home.remixBasics": "Remix Basics",
"home.remixBasicsDesc":"Introduction to Remix's interface and concepts used in Ethereum, as well as the basics of Solidity.",
"home.remixIntermediate": "Remix Intermediate",
"home.remixIntermediateDesc": "Using the web3.js to interact with a contract. Using Recorder tool.",
"home.remixAdvanced": "Remix Advanced",
"home.remixAdvancedDesc": "Learn the Proxy Pattern and working with Libraries in Remix. Learn to use the Debugger.",
"home.remixYoutubePlaylist": "Remix Youtube Playlist",
"home.remixTwitterProfile": "Remix Twitter Profile",
"home.remixLinkedinProfile": "Remix Linkedin Profile",
"home.remixMediumPosts": "Remix Medium Posts",
"home.remixGitterChannel": "Remix Gitter Channel",
"home.nativeIDE": "The Native IDE for Web3 Development.",
"home.website": "Website",
"home.documentation": "Documentation",
"home.remixPlugin": "Remix Plugin",
"home.remixDesktop": "Remix Desktop",
"home.searchDocumentation": "Search Documentation",
"home.files": "Files",
"home.newFile": "New File",
"home.openFile": "Open File",
"home.connectToLocalhost": "Connect to Localhost",
"home.loadFrom": "LOAD FROM",
"home.resources": "Resources"
}

@ -0,0 +1,23 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
export default {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
}

@ -0,0 +1,6 @@
{
"panel.author": "Author",
"panel.maintainedBy": "Maintained By",
"panel.documentation": "Documentation",
"panel.description": "Description"
}

@ -0,0 +1,31 @@
{
"pluginManager.displayName": "Plugin manager",
"pluginManager.activate": "Activate",
"pluginManager.deactivate": "Deactivate",
"pluginManager.activeModules": "Active Modules",
"pluginManager.inactiveModules": "Inactive Modules",
"pluginManager.connectLocal": "Connect to a Local Plugin",
"pluginManager.localForm.title": "Local Plugin",
"pluginManager.localForm.pluginName": "Plugin Name",
"pluginManager.localForm.shouldBeCamelCase": "Should be camelCase",
"pluginManager.localForm.displayName": "Display Name",
"pluginManager.localForm.nameInTheHeader": "Name in the header",
"pluginManager.localForm.required": "required",
"pluginManager.localForm.commaSeparatedMethod": "comma separated list of method names",
"pluginManager.localForm.commaSeparatedPlugin": "comma separated list of plugin names",
"pluginManager.localForm.pluginsItCanActivate": "Plugins it can activate",
"pluginManager.localForm.typeOfConnection": "Type of connection",
"pluginManager.localForm.locationInRemix": "Location in remix",
"pluginManager.localForm.sidePanel": "Side Panel",
"pluginManager.localForm.mainPanel": "Main Panel",
"pluginManager.localForm.none": "None",
"pluginManager.Permissions": "Permissions",
"pluginManager.permissions": "permissions",
"pluginManager.pluginManagerPermissions": "Plugin Manager Permissions",
"pluginManager.currentPermissionSettings": "Current Permission Settings",
"pluginManager.noPermissionRequestedYet": "No Permission requested yet.",
"pluginManager.allow": "Allow",
"pluginManager.toCall": "to call",
"pluginManager.ok": "OK",
"pluginManager.cancel": "Cancel"
}

@ -0,0 +1,14 @@
{
"search.displayName": "Search in files",
"search.replace": "Replace",
"search.replaceAll": "Replace All",
"search.placeholder1": "Search ( Enter to search )",
"search.placeholder2": "Include ie *.sol ( Enter to include )",
"search.placeholder3": "Exclude ie .git/**/* ( Enter to exclude )",
"search.matchCase": "Match Case",
"search.matchWholeWord": "Match Whole Word",
"search.useRegularExpression": "Use Regular Expression",
"search.replaceWithoutConfirmation": "replace without confirmation",
"search.filesToInclude": "Files to include",
"search.filesToExclude": "Files to exclude"
}

@ -0,0 +1,26 @@
{
"settings.displayName": "Settings",
"settings.reset": "Reset to Default settings",
"settings.general": "General settings",
"settings.generateContractMetadataText": "Generate contract metadata. Generate a JSON file in the contract folder. Allows to specify library addresses the contract depends on. If nothing is specified, Remix deploys libraries automatically.",
"settings.ethereunVMText": "Always use Javascript VM at load",
"settings.wordWrapText": "Word wrap in editor",
"settings.useAutoCompleteText": "Enable code completion in editor.",
"settings.useShowGasInEditorText": "Display gas estimates in editor.",
"settings.displayErrorsText": "Display errors in editor while typing.",
"settings.matomoAnalytics": "Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about ",
"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. This 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",
"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.",
"settings.save": "Save",
"settings.remove": "Remove",
"settings.themes": "Themes",
"settings.locales": "Lanaguage",
"settings.swarm": "Swarm Settings",
"settings.ipfs": "IPFS Settings"
}

@ -0,0 +1,37 @@
{
"solidity.displayName": "Solidity compiler",
"solidity.compiler": "Compiler",
"solidity.addACustomCompiler": "Add a custom compiler",
"solidity.addACustomCompilerWithURL": "Add a custom compiler with URL",
"solidity.includeNightlyBuilds": "Include nightly builds",
"solidity.autoCompile": "Auto compile",
"solidity.hideWarnings": "Hide warnings",
"solidity.enableHardhat": "Enable Hardhat Compilation",
"solidity.learnHardhat": "Learn how to use Hardhat Compilation",
"solidity.enableTruffle": "Enable Truffle Compilation",
"solidity.learnTruffle": "Learn how to use Truffle Compilation",
"solidity.advancedConfigurations": "Advanced Configurations",
"solidity.compilerConfiguration": "Compiler configuration",
"solidity.compilationDetails": "Compilation Details",
"solidity.language": "Language",
"solidity.evmVersion": "EVM Version",
"solidity.enableOptimization": "Enable optimization",
"solidity.useConfigurationFile": "Use configuration file",
"solidity.change": "Change",
"solidity.compile": "Compile",
"solidity.noFileSelected": "no file selected",
"solidity.compileAndRunScript": "Compile and Run script",
"solidity.publishOn": "Publish on",
"solidity.Assembly": "Assembly opcodes describing the contract including corresponding solidity source code",
"solidity.Opcodes": "Assembly opcodes describing the contract",
"solidity.name": "Name of the compiled contract",
"solidity.metadata": "Contains all informations related to the compilation",
"solidity.bytecode": "Bytecode being executed during contract creation",
"solidity.abi": "ABI: describing all the functions (input/output params, scope, ...)",
"solidity.web3Deploy": "Copy/paste this code to any JavaScript/Web3 console to deploy this contract",
"solidity.metadataHash": "Hash representing all metadata information",
"solidity.functionHashes": "List of declared function and their corresponding hash",
"solidity.gasEstimates": "Gas estimation for each function call",
"solidity.Runtime Bytecode": "Bytecode storing the state and being executed during normal contract call",
"solidity.swarmLocation": "Swarm url where all metadata information can be found (contract needs to be published first)"
}

@ -0,0 +1,15 @@
{
"terminal.listen": "listen on all transactions",
"terminal.search": "Search with transaction hash or address",
"terminal.used": "used",
"terminal.welcomeText1": "Welcome to",
"terminal.welcomeText2": "Your files are stored in",
"terminal.welcomeText3": "You can use this terminal to",
"terminal.welcomeText4": "Check transactions details and start debugging",
"terminal.welcomeText5": "Execute JavaScript scripts",
"terminal.welcomeText6": "Input a script directly in the command line interface",
"terminal.welcomeText7": "Select a Javascript file in the file explorer and then run `remix.execute()` or `remix.exeCurrent()` in the command line interface",
"terminal.welcomeText8": "Right click on a JavaScript file in the file explorer and then click `Run`",
"terminal.welcomeText9": "The following libraries are accessible",
"terminal.welcomeText10": "Type the library name to see available commands"
}

@ -0,0 +1,24 @@
{
"udapp.displayName": "Deploy & run transactions",
"udapp.gasLimit": "Gas limit",
"udapp.account": "Account",
"udapp.value": "Value",
"udapp.contract": "Contract",
"udapp.signAMessage": "Sign a message",
"udapp.enterAMessageToSign": "Enter a message to sign",
"udapp.hash": "hash",
"udapp.signature": "signature",
"udapp.signedMessage": "Signed Message",
"udapp.environment": "Environment",
"udapp.environmentDocs": "Click for docs about Environment",
"udapp.deploy": "Deploy",
"udapp.publishTo": "Publish to",
"udapp.or": "or",
"udapp.atAddress": "At Address",
"udapp.noCompiledContracts": "No compiled contracts",
"udapp.loadContractFromAddress": "Load contract from Address",
"udapp.deployedContracts": "Deployed Contracts",
"udapp.deployAndRunClearInstances": "Clear instances list and reset recorder",
"udapp.deployAndRunNoInstanceText": "Currently you have no contract instances to interact with.",
"udapp.transactionsRecorded": "Transactions recorded"
}

@ -1,196 +0,0 @@
export default {
'panel.author': '作者',
'panel.maintainedBy': '维护者',
'panel.documentation': '文档',
'panel.description': '描述',
'settings.displayName': '设置',
'settings.reset': '恢复默认设置',
'settings.general': '常规设置',
'settings.generateContractMetadataText': '生成合约元数据. 在contract文件夹中生成JSON文件. 允许指定合约依赖的库地址. 如果未指定任何内容,Remix将自动部署库.',
'settings.ethereunVMText': '加载时始终使用以太坊虚拟机',
'settings.wordWrapText': '文本换行',
'settings.useAutoCompleteText': '在编辑器中启用代码自动补全.',
'settings.useShowGasInEditorText': '在编辑器中展示 gas 预算.',
'settings.displayErrorsText': '编辑代码时展示错误提示.',
'settings.matomoAnalytics': '启用 Matomo 分析. 我们不会收集个人身份信息 (PII). 收集的信息只会用于提升 UX & UI. 查看更多关于 ',
'settings.enablePersonalModeText': '为web3提供器启用私有模式. 通过Web3发送的交易将使用web3.personal API.\n',
'settings.warnText': '在启用之前请确认访问端结点已经开放. \n此模式允许在Remix界面中提供密码而无需解锁账号. 虽然这很方便,但你应当完全信任所连接的后端节点 (Geth, Parity, ...). Remix不会持久化保存任何密码.'.split('\n').map(s => s.trim()).join(' '),
'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中.',
'settings.save': '保存',
'settings.remove': '删除',
'settings.themes': '主题',
'settings.locales': '语言',
'settings.swarm': 'Swarm 设置',
'settings.ipfs': 'IPFS 设置',
'filePanel.displayName': '文件浏览器',
'filePanel.workspace': '工作空间',
'filePanel.create': '新建',
'filePanel.clone': '克隆',
'filePanel.download': '下载',
'filePanel.restore': '恢复',
'filePanel.workspace.create': '新建工作空间',
'filePanel.workspace.rename': '重命名工作空间',
'filePanel.workspace.delete': '删除工作空间',
'filePanel.workspace.deleteConfirm': '确定要删除当前工作空间?',
'filePanel.workspace.name': '工作空间名称',
'filePanel.workspace.chooseTemplate': '选择一个工作空间模板',
'filePanel.workspace.download': '下载工作空间',
'filePanel.workspace.restore': '恢复工作空间',
'filePanel.workspace.clone': '克隆 Git 仓库',
'filePanel.workspace.enterGitUrl': '输入 Git 仓库地址',
'filePanel.newFile': '新建文件',
'filePanel.newFolder': '新建文件夹',
'filePanel.rename': '重命名',
'filePanel.delete': '删除',
'filePanel.deleteAll': '删除所有',
'filePanel.run': '运行',
'filePanel.pushChangesToGist': '将修改推送到gist',
'filePanel.publishFolderToGist': '将当前目录发布到gist',
'filePanel.publishFileToGist': '将当前文件发布到gist',
'filePanel.copy': '复制',
'filePanel.paste': '黏贴',
'filePanel.compile': '编译',
'filePanel.compileForNahmii': 'Nahmii编译',
'filePanel.createNewFile': '新建文件',
'filePanel.createNewFolder': '新建文件夹',
'filePanel.publishToGist': '将当前工作空间下所有文件发布到github gist',
'filePanel.uploadFile': '加载本地文件到当前工作空间',
'filePanel.updateGist': '更新当前 [gist] 浏览',
'home.scamAlert': '诈骗警报',
'home.scamAlertText': 'Remix 唯一使用的 URL 是 remix.ethereum.org',
'home.scamAlertText2': '注意那些宣传 "liquidity front runner bots" 的在线视频',
'home.scamAlertText3': '其他安全提示',
'home.learnMore': '了解更多',
'home.here': '这里',
'home.featuredPlugins': '精选插件',
'home.files': '文件',
'home.newFile': '新建文件',
'home.openFile': '打开本地文件',
'home.connectToLocalhost': '连接本地主机',
'home.loadFrom': '从以下来源导入',
'home.resources': '资源',
'terminal.listen': '监听所有交易',
'terminal.search': '按交易哈希或地址搜索',
'terminal.used': '已使用',
'terminal.welcomeText1': '欢迎使用',
'terminal.welcomeText2': '您的文件储存在',
'terminal.welcomeText3': '您可使用此终端',
'terminal.welcomeText4': '查看交易详情并启动调试',
'terminal.welcomeText5': '执行 JavaScript 脚本',
'terminal.welcomeText6': '直接在命令行界面输入脚本',
'terminal.welcomeText7': '在文件浏览器中选择一个 Javascript 文件,然后在命令行界面运行 `remix.execute()` 或 `remix.exeCurrent()` ',
'terminal.welcomeText8': '在文件浏览器中右键点击一个 JavaScript 文件,然后点击 `Run`',
'terminal.welcomeText9': '可以访问以下库',
'terminal.welcomeText10': '输入库名查看可用的指令',
'debugger.displayName': '调试器',
'debugger.debuggerConfiguration': '调试器配置',
'debugger.stopDebugging': '停止调试',
'debugger.startDebugging': '开始调试',
'debugger.placeholder': '交易哈希, 应该以 0x 开头',
'debugger.introduction': `当使用交易哈希调试时,
如果该合约已被验证, Remix 会试图从 Sourcify Etherscan 获取源码. Remix 中设置您的 Etherscan API key.
有关受支持的网络请参阅`,
'udapp.displayName': '部署 & 发交易',
'udapp.gasLimit': 'Gas 上限',
'udapp.account': '账户',
'udapp.value': '以太币数量',
'udapp.contract': '合约',
'udapp.signAMessage': '给一个消息签名',
'udapp.enterAMessageToSign': '输入一个需要签名的消息',
'udapp.hash': '哈希',
'udapp.signature': '签名',
'udapp.signedMessage': '已签名的消息',
'udapp.environment': '环境',
'udapp.environmentDocs': '点击查看环境文档',
'udapp.deploy': '部署',
'udapp.publishTo': '发布到',
'udapp.or': '或',
'udapp.atAddress': 'At Address',
'udapp.noCompiledContracts': '没有已编译的合约',
'udapp.loadContractFromAddress': '加载此地址的合约',
'udapp.deployedContracts': '已部署的合约',
'udapp.deployAndRunClearInstances': '清空合约实例并重置交易记录',
'udapp.deployAndRunNoInstanceText': '当前您没有可交互的合约实例.',
'udapp.transactionsRecorded': '已记录的交易',
'search.displayName': '全文搜索',
'search.replace': '替换',
'search.replaceAll': '替换所有',
'search.placeholder1': '搜索 ( 回车搜索 )',
'search.placeholder2': '包含 ie *.sol ( 回车包含 )',
'search.placeholder3': '排除 ie .git/**/* ( 回车排除 )',
'search.matchCase': '大小写匹配',
'search.matchWholeWord': '全字匹配',
'search.useRegularExpression': '使用正则表达式',
'search.replaceWithoutConfirmation': '替换无需确认',
'search.filesToInclude': '文件包含',
'search.filesToExclude': '文件排除',
'solidity.displayName': 'Solidity 编译器',
'solidity.compiler': '编译器',
'solidity.addACustomCompiler': '添加一个自定义编译器',
'solidity.addACustomCompilerWithURL': '通过URL添加一个自定义编译器',
'solidity.includeNightlyBuilds': '包含每日构造版本',
'solidity.autoCompile': '自动编译',
'solidity.hideWarnings': '隐藏警告',
'solidity.enableHardhat': '启用 Hardhat 编译',
'solidity.learnHardhat': '学习怎么使用 Hardhat 编译',
'solidity.enableTruffle': '启用 Truffle 编译',
'solidity.learnTruffle': '学习怎么使用 Truffle 编译',
'solidity.advancedConfigurations': '高级配置',
'solidity.compilerConfiguration': '编译器配置',
'solidity.compilationDetails': '编译详情',
'solidity.language': '语言',
'solidity.evmVersion': 'EVM 版本',
'solidity.enableOptimization': '启用优化',
'solidity.useConfigurationFile': '使用配置文件',
'solidity.change': '修改',
'solidity.compile': '编译',
'solidity.noFileSelected': '未选中文件',
'solidity.compileAndRunScript': '编译且执行脚本',
'solidity.publishOn': '发布到',
'solidity.Assembly': '合约的汇编操作码,包含对应的solidity源程序',
'solidity.Opcodes': '合约的汇编操作码',
'solidity.name': '已编译合约的名称',
'solidity.metadata': '包含编译相关的全部信息',
'solidity.bytecode': '合约创建时执行的字节码',
'solidity.abi': 'ABI: 全部合约函数的描述 (输入/输出 参数, 作用域, ...)',
'solidity.web3Deploy': '拷贝/粘贴这部分代码到任何 JavaScript/Web3 控制台都可以部署此合约',
'solidity.metadataHash': '元数据的哈希值',
'solidity.functionHashes': '合约定义的函数清单,包含对应的哈希',
'solidity.gasEstimates': '每个函数调用的Gas估算值',
'solidity.Runtime Bytecode': '用于保存状态并在合约调用期执行的字节码',
'solidity.swarmLocation': '可以找到所有元数据信息的Swarm url (首先需要发布合约) ',
'pluginManager.displayName': '插件管理',
'pluginManager.activate': '启用',
'pluginManager.deactivate': '停用',
'pluginManager.activeModules': '启用的模块',
'pluginManager.inactiveModules': '停用的模块',
'pluginManager.connectLocal': '连接本地插件',
'pluginManager.localForm.title': '本地插件',
'pluginManager.localForm.pluginName': '插件名称',
'pluginManager.localForm.shouldBeCamelCase': '应当采用驼峰命名法',
'pluginManager.localForm.displayName': '展示名称',
'pluginManager.localForm.nameInTheHeader': '标题中展示的名称',
'pluginManager.localForm.required': '必填',
'pluginManager.localForm.commaSeparatedMethod': '英文逗号分隔方法名称',
'pluginManager.localForm.commaSeparatedPlugin': '英文逗号分隔插件名称',
'pluginManager.localForm.pluginsItCanActivate': '能激活该插件的插件',
'pluginManager.localForm.typeOfConnection': '连接类型',
'pluginManager.localForm.locationInRemix': '在Remix中的位置',
'pluginManager.localForm.sidePanel': '侧面板',
'pluginManager.localForm.mainPanel': '主面板',
'pluginManager.localForm.none': '无',
}

@ -0,0 +1,8 @@
{
"debugger.displayName": "调试器",
"debugger.debuggerConfiguration": "调试器配置",
"debugger.stopDebugging": "停止调试",
"debugger.startDebugging": "开始调试",
"debugger.placeholder": "交易哈希, 应该以 0x 开头",
"debugger.introduction": "当使用交易哈希调试时, 如果该合约已被验证, Remix 会试图从 Sourcify 或 Etherscan 获取源码. 在 Remix 中设置您的 Etherscan API key. 有关受支持的网络,请参阅"
}

@ -0,0 +1,38 @@
{
"filePanel.displayName": "文件浏览器",
"filePanel.workspace": "工作空间",
"filePanel.create": "新建",
"filePanel.clone": "克隆",
"filePanel.download": "下载",
"filePanel.restore": "恢复",
"filePanel.workspace.create": "新建工作空间",
"filePanel.workspace.rename": "重命名工作空间",
"filePanel.workspace.delete": "删除工作空间",
"filePanel.workspace.deleteConfirm": "确定要删除当前工作空间?",
"filePanel.workspace.name": "工作空间名称",
"filePanel.workspace.chooseTemplate": "选择一个工作空间模板",
"filePanel.workspace.download": "下载工作空间",
"filePanel.workspace.restore": "恢复工作空间",
"filePanel.workspace.clone": "克隆 Git 仓库",
"filePanel.workspace.enterGitUrl": "输入 Git 仓库地址",
"filePanel.workspace.solghaction": "添加 Solidity GitHub Action 文件。推送到一个代码仓库,然后在 GitHub CI 中运行它。",
"filePanel.solghaction": "添加 Solidity GitHub Action",
"filePanel.newFile": "新建文件",
"filePanel.newFolder": "新建文件夹",
"filePanel.rename": "重命名",
"filePanel.delete": "删除",
"filePanel.deleteAll": "删除所有",
"filePanel.run": "运行",
"filePanel.pushChangesToGist": "将修改推送到gist",
"filePanel.publishFolderToGist": "将当前目录发布到gist",
"filePanel.publishFileToGist": "将当前文件发布到gist",
"filePanel.copy": "复制",
"filePanel.paste": "黏贴",
"filePanel.compile": "编译",
"filePanel.compileForNahmii": "Nahmii编译",
"filePanel.createNewFile": "新建文件",
"filePanel.createNewFolder": "新建文件夹",
"filePanel.publishToGist": "将当前工作空间下所有文件发布到github gist",
"filePanel.uploadFile": "加载本地文件到当前工作空间",
"filePanel.updateGist": "更新当前 [gist] 浏览"
}

@ -0,0 +1,59 @@
{
"home.scamAlert": "诈骗警报",
"home.scamAlertText": "Remix 唯一使用的 URL 是 remix.ethereum.org",
"home.scamAlertText2": "注意那些宣传 \"liquidity front runner bots\" 的在线视频",
"home.scamAlertText3": "其他安全提示",
"home.learnMore": "了解更多",
"home.here": "这里",
"home.more": "更多",
"home.featured": "精选",
"home.jumpIntoWeb3": "迎接 WEB3",
"home.jumpIntoWeb3Text": "Remix 项目是一个丰富的工具集,任何知识水平的用户都可以在这上面进行全周期的合约开发,并且可作为以太坊教学和实验的学习实验室。",
"home.remixRewards": "REMIX 奖励",
"home.remixRewardsText1": "给我们的用户提供的 NFT!",
"home.remixRewardsText2": "Remix 会用部署在 Optimism 链上的 NFT 奖励贡献者、beta 测试者和用户体验研究参与者。Remix 奖励的持有者可以铸造第二个“Remixer”用户 NFT 徽章,以赠予他们选择的任何其他用户。",
"home.solidityDevSurveyHeader": "",
"home.solidityDevSurvey": "",
"home.solidityDevSurvey1": "",
"home.surveyLink": "",
"home.betaTesting": "BETA 测试",
"home.betaTestingText1": "我们的社区支持我们",
"home.betaTestingText2": "每次 Remix IDE 发布版本之前,你都可以参与 Beta 测试。现在就来帮我们测试并且尝鲜新功能吧!",
"home.featuredPlugins": "精选插件",
"home.solidityPluginDesc": "编译、测试和分析智能合约。",
"home.starkNetPluginDesc": "用 Cairo 来编译且部署合约,这是 StarkNet 的原生语言",
"home.solhintPluginDesc": "Solhint 是一个用于检查 Solidity 代码的开源项目",
"home.sourcifyPluginDesc": "Solidity 合约和元数据验证服务。",
"home.unitTestPluginDesc": "在 Solidity 中为你的合约编写和运行单元测试。",
"home.getStarted": "开始使用",
"home.projectTemplates": "项目模板",
"home.blankTemplateDesc": "创建一个空的工作空间。",
"home.remixDefaultTemplateDesc": "创建一个带有样本文件的工作空间",
"home.ozerc20TemplateDesc": "通过引入 OpenZeppelin 库来创建一个 ERC20 代币。",
"home.ozerc721TemplateDesc": "通过引入 OpenZeppelin 库来创建一个 NFT 代币。",
"home.zeroxErc20TemplateDesc": "通过引入 0xProject 合约来创建一个 ERC20 代币。",
"home.learn": "学习",
"home.remixBasics": "Remix 基础",
"home.remixBasicsDesc": "介绍 Remix 的界面和在以太坊中使用的概念,以及 Solidity 的基础知识。",
"home.remixIntermediate": "Remix 中级",
"home.remixIntermediateDesc": "使用 web3.js 与合约交互。使用记录器工具。",
"home.remixAdvanced": "Remix 高级",
"home.remixAdvancedDesc": "学习代理模式并使用 Remix 中的库。学习使用调试器。",
"home.remixYoutubePlaylist": "Remix Youtube 播放列表",
"home.remixTwitterProfile": "Remix 推特档案",
"home.remixLinkedinProfile": "Remix 领英档案",
"home.remixMediumPosts": "Remix Medium 文章",
"home.remixGitterChannel": "Remix Gitter 频道",
"home.nativeIDE": "用于 Web3 开发的原生 IDE。",
"home.website": "网站",
"home.documentation": "文档",
"home.remixPlugin": "Remix 插件",
"home.remixDesktop": "Remix 桌面版",
"home.searchDocumentation": "搜索文档",
"home.files": "文件",
"home.newFile": "新建文件",
"home.openFile": "打开本地文件",
"home.connectToLocalhost": "连接本地主机",
"home.loadFrom": "从以下来源导入",
"home.resources": "资源"
}

@ -0,0 +1,26 @@
import debuggerJson from './debugger.json';
import filePanelJson from './filePanel.json';
import homeJson from './home.json';
import panelJson from './panel.json';
import pluginManagerJson from './pluginManager.json';
import searchJson from './search.json';
import settingsJson from './settings.json';
import solidityJson from './solidity.json';
import terminalJson from './terminal.json';
import udappJson from './udapp.json';
import enJson from '../en';
// There may have some un-translated content. Always fill in the gaps with EN JSON.
// No need for a defaultMessage prop when render a FormattedMessage component.
export default Object.assign({}, enJson, {
...debuggerJson,
...filePanelJson,
...homeJson,
...panelJson,
...pluginManagerJson,
...searchJson,
...settingsJson,
...solidityJson,
...terminalJson,
...udappJson,
})

@ -0,0 +1,6 @@
{
"panel.author": "作者",
"panel.maintainedBy": "维护者",
"panel.documentation": "文档",
"panel.description": "描述"
}

@ -0,0 +1,31 @@
{
"pluginManager.displayName": "插件管理",
"pluginManager.activate": "启用",
"pluginManager.deactivate": "停用",
"pluginManager.activeModules": "启用的模块",
"pluginManager.inactiveModules": "停用的模块",
"pluginManager.connectLocal": "连接本地插件",
"pluginManager.localForm.title": "本地插件",
"pluginManager.localForm.pluginName": "插件名称",
"pluginManager.localForm.shouldBeCamelCase": "应当采用驼峰命名法",
"pluginManager.localForm.displayName": "展示名称",
"pluginManager.localForm.nameInTheHeader": "标题中展示的名称",
"pluginManager.localForm.required": "必填",
"pluginManager.localForm.commaSeparatedMethod": "英文逗号分隔方法名称",
"pluginManager.localForm.commaSeparatedPlugin": "英文逗号分隔插件名称",
"pluginManager.localForm.pluginsItCanActivate": "它能激活的插件",
"pluginManager.localForm.typeOfConnection": "连接类型",
"pluginManager.localForm.locationInRemix": "在Remix中的位置",
"pluginManager.localForm.sidePanel": "侧面板",
"pluginManager.localForm.mainPanel": "主面板",
"pluginManager.localForm.none": "无",
"pluginManager.Permissions": "权限",
"pluginManager.permissions": "权限",
"pluginManager.pluginManagerPermissions": "插件管理权限",
"pluginManager.currentPermissionSettings": "当前权限设置",
"pluginManager.noPermissionRequestedYet": "目前还没有权限请求。",
"pluginManager.allow": "允许",
"pluginManager.toCall": "调用",
"pluginManager.ok": "确认",
"pluginManager.cancel": "取消"
}

@ -0,0 +1,14 @@
{
"search.displayName": "全文搜索",
"search.replace": "替换",
"search.replaceAll": "替换所有",
"search.placeholder1": "搜索 ( 回车搜索 )",
"search.placeholder2": "包含 ie *.sol ( 回车包含 )",
"search.placeholder3": "排除 ie .git/**/* ( 回车排除 )",
"search.matchCase": "大小写匹配",
"search.matchWholeWord": "全字匹配",
"search.useRegularExpression": "使用正则表达式",
"search.replaceWithoutConfirmation": "替换无需确认",
"search.filesToInclude": "文件包含",
"search.filesToExclude": "文件排除"
}

@ -0,0 +1,26 @@
{
"settings.displayName": "设置",
"settings.reset": "恢复默认设置",
"settings.general": "常规设置",
"settings.generateContractMetadataText": "生成合约元数据. 在contract文件夹中生成JSON文件. 允许指定合约依赖的库地址. 如果未指定任何内容,Remix将自动部署库.",
"settings.ethereunVMText": "加载时始终使用以太坊虚拟机",
"settings.wordWrapText": "文本换行",
"settings.useAutoCompleteText": "在编辑器中启用代码自动补全.",
"settings.useShowGasInEditorText": "在编辑器中展示 gas 预算.",
"settings.displayErrorsText": "编辑代码时展示错误提示.",
"settings.matomoAnalytics": "启用 Matomo 分析. 我们不会收集个人身份信息 (PII). 收集的信息只会用于提升 UX & UI. 查看更多关于 ",
"settings.enablePersonalModeText": "为web3提供器启用私有模式. 通过Web3发送的交易将使用web3.personal API.\n",
"settings.warnText": "在启用之前请确认访问端结点已经开放. 此模式允许在Remix界面中提供密码而无需解锁账号. 虽然这很方便,但你应当完全信任所连接的后端节点 (Geth, Parity, ...). Remix不会持久化保存任何密码.",
"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中.",
"settings.save": "保存",
"settings.remove": "删除",
"settings.themes": "主题",
"settings.locales": "语言",
"settings.swarm": "Swarm 设置",
"settings.ipfs": "IPFS 设置"
}

@ -0,0 +1,37 @@
{
"solidity.displayName": "Solidity 编译器",
"solidity.compiler": "编译器",
"solidity.addACustomCompiler": "添加一个自定义编译器",
"solidity.addACustomCompilerWithURL": "通过URL添加一个自定义编译器",
"solidity.includeNightlyBuilds": "包含每日构造版本",
"solidity.autoCompile": "自动编译",
"solidity.hideWarnings": "隐藏警告",
"solidity.enableHardhat": "启用 Hardhat 编译",
"solidity.learnHardhat": "学习怎么使用 Hardhat 编译",
"solidity.enableTruffle": "启用 Truffle 编译",
"solidity.learnTruffle": "学习怎么使用 Truffle 编译",
"solidity.advancedConfigurations": "高级配置",
"solidity.compilerConfiguration": "编译器配置",
"solidity.compilationDetails": "编译详情",
"solidity.language": "语言",
"solidity.evmVersion": "EVM 版本",
"solidity.enableOptimization": "启用优化",
"solidity.useConfigurationFile": "使用配置文件",
"solidity.change": "修改",
"solidity.compile": "编译",
"solidity.noFileSelected": "未选中文件",
"solidity.compileAndRunScript": "编译且执行脚本",
"solidity.publishOn": "发布到",
"solidity.Assembly": "合约的汇编操作码,包含对应的solidity源程序",
"solidity.Opcodes": "合约的汇编操作码",
"solidity.name": "已编译合约的名称",
"solidity.metadata": "包含编译相关的全部信息",
"solidity.bytecode": "合约创建时执行的字节码",
"solidity.abi": "ABI: 全部合约函数的描述 (输入/输出 参数, 作用域, ...)",
"solidity.web3Deploy": "拷贝/粘贴这部分代码到任何 JavaScript/Web3 控制台都可以部署此合约",
"solidity.metadataHash": "元数据的哈希值",
"solidity.functionHashes": "合约定义的函数清单,包含对应的哈希",
"solidity.gasEstimates": "每个函数调用的Gas估算值",
"solidity.Runtime Bytecode": "用于保存状态并在合约调用期执行的字节码",
"solidity.swarmLocation": "可以找到所有元数据信息的Swarm url (首先需要发布合约) "
}

@ -0,0 +1,15 @@
{
"terminal.listen": "监听所有交易",
"terminal.search": "按交易哈希或地址搜索",
"terminal.used": "已使用",
"terminal.welcomeText1": "欢迎使用",
"terminal.welcomeText2": "您的文件储存在",
"terminal.welcomeText3": "您可使用此终端",
"terminal.welcomeText4": "查看交易详情并启动调试",
"terminal.welcomeText5": "执行 JavaScript 脚本",
"terminal.welcomeText6": "直接在命令行界面输入脚本",
"terminal.welcomeText7": "在文件浏览器中选择一个 Javascript 文件,然后在命令行界面运行 `remix.execute()` 或 `remix.exeCurrent()` ",
"terminal.welcomeText8": "在文件浏览器中右键点击一个 JavaScript 文件,然后点击 `Run`",
"terminal.welcomeText9": "可以访问以下库",
"terminal.welcomeText10": "输入库名查看可用的指令"
}

@ -0,0 +1,24 @@
{
"udapp.displayName": "部署 & 发交易",
"udapp.gasLimit": "Gas 上限",
"udapp.account": "账户",
"udapp.value": "以太币数量",
"udapp.contract": "合约",
"udapp.signAMessage": "给一个消息签名",
"udapp.enterAMessageToSign": "输入一个需要签名的消息",
"udapp.hash": "哈希",
"udapp.signature": "签名",
"udapp.signedMessage": "已签名的消息",
"udapp.environment": "环境",
"udapp.environmentDocs": "点击查看环境文档",
"udapp.deploy": "部署",
"udapp.publishTo": "发布到",
"udapp.or": "或",
"udapp.atAddress": "At Address",
"udapp.noCompiledContracts": "没有已编译的合约",
"udapp.loadContractFromAddress": "加载此地址的合约",
"udapp.deployedContracts": "已部署的合约",
"udapp.deployAndRunClearInstances": "清空合约实例并重置交易记录",
"udapp.deployAndRunNoInstanceText": "当前您没有可交互的合约实例.",
"udapp.transactionsRecorded": "已记录的交易"
}

@ -53,19 +53,19 @@ module.exports = class SettingsTab extends ViewPlugin {
render() {
return <div id='settingsTab'>
<PluginViewWrapper plugin={this} />
</div>
<PluginViewWrapper plugin={this} />
</div>
}
updateComponent(state: any){
return <RemixUiSettings
config={state.config}
editor={state.editor}
_deps={state._deps}
useMatomoAnalytics={state.useMatomoAnalytics}
themeModule = {state._deps.themeModule}
localeModule={state._deps.localeModule}
/>
config={state.config}
editor={state.editor}
_deps={state._deps}
useMatomoAnalytics={state.useMatomoAnalytics}
themeModule = {state._deps.themeModule}
localeModule={state._deps.localeModule}
/>
}
renderComponent () {

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@ -170,7 +170,7 @@ export class Blockchain extends Plugin {
}
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() }
const promptCb = (okCb, cancelCb) => { okCb() }
const finalCb = (error, txResult, address, returnValue) => {
const finalCb = async (error, txResult, address, returnValue) => {
if (error) {
const log = logBuilder(error)
@ -179,6 +179,7 @@ export class Blockchain extends Plugin {
}
if (networkInfo.name === 'VM') this.config.set('vm/proxy', address)
else this.config.set(`${networkInfo.name}/${networkInfo.currentFork}/${networkInfo.id}/proxy`, address)
await this.saveDeployedContractStorageLayout(implementationContractObject, address, networkInfo)
_paq.push(['trackEvent', 'blockchain', 'Deploy With Proxy', 'Proxy deployment successful'])
this.call('udapp', 'addInstance', addressToString(address), implementationContractObject.abi, implementationContractObject.name)
}
@ -209,25 +210,61 @@ export class Blockchain extends Plugin {
async runUpgradeTx (proxyAddress, data, newImplementationContractObject) {
const args = { useCall: false, data, to: proxyAddress }
let networkInfo
const confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
// continue using original authorization given by user
networkInfo = network
continueTxExecution(null)
}
const continueCb = (error, continueTxExecution, cancelCb) => { continueTxExecution() }
const promptCb = (okCb, cancelCb) => { okCb() }
const finalCb = (error, txResult, address, returnValue) => {
const finalCb = async (error, txResult, address, returnValue) => {
if (error) {
const log = logBuilder(error)
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade failed'])
return this.call('terminal', 'logHtml', log)
}
await this.saveDeployedContractStorageLayout(newImplementationContractObject, proxyAddress, networkInfo)
_paq.push(['trackEvent', 'blockchain', 'Upgrade With Proxy', 'Upgrade Successful'])
this.call('udapp', 'addInstance', addressToString(proxyAddress), newImplementationContractObject.abi, newImplementationContractObject.name)
}
this.runTx(args, confirmationCb, continueCb, promptCb, finalCb)
}
async saveDeployedContractStorageLayout (contractObject, proxyAddress, networkInfo) {
const { contractName, implementationAddress, contract } = contractObject
const hasPreviousDeploys = await this.call('fileManager', 'exists', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`)
// TODO: make deploys folder read only.
if (hasPreviousDeploys) {
const deployments = await this.call('fileManager', 'readFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`)
const parsedDeployments = JSON.parse(deployments)
parsedDeployments.deployments[proxyAddress] = {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress,
layout: contract.object.storageLayout
}
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`, JSON.stringify(parsedDeployments, null, 2))
} else {
await this.call('fileManager', 'writeFile', `.deploys/upgradeable-contracts/${networkInfo.name}/UUPS.json`, JSON.stringify({
id: networkInfo.id,
network: networkInfo.name,
deployments: {
[proxyAddress]: {
date: new Date().toISOString(),
contractName: contractName,
fork: networkInfo.currentFork,
implementationAddress: implementationAddress,
layout: contract.object.storageLayout
}
}
}, null, 2))
}
}
async getEncodedFunctionHex (args, funABI) {
return new Promise((resolve, reject) => {
txFormat.encodeFunctionCall(args, funABI, (error, data) => {

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-analyzer",
"version": "0.5.29",
"version": "0.5.30",
"description": "Tool to perform static analysis on Solidity smart contracts",
"scripts": {
"test": "./../../node_modules/.bin/ts-node --project ../../tsconfig.base.json --require tsconfig-paths/register ./../../node_modules/.bin/tape ./test/tests.ts"
@ -24,8 +24,8 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.50",
"@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-astwalker": "^0.0.51",
"@remix-project/remix-lib": "^0.5.21",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2",
@ -50,5 +50,5 @@
"typescript": "^3.7.5"
},
"typings": "src/index.d.ts",
"gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-astwalker",
"version": "0.0.50",
"version": "0.0.51",
"description": "Tool to walk through Solidity AST",
"main": "src/index.js",
"scripts": {
@ -36,7 +36,7 @@
"@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-lib": "^0.5.21",
"@types/tape": "^4.2.33",
"async": "^2.6.2",
"ethereumjs-util": "^7.0.10",
@ -53,5 +53,5 @@
"tap-spec": "^5.0.0"
},
"typings": "src/index.d.ts",
"gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -4,7 +4,7 @@ import { util } from '@remix-project/remix-lib'
import { toChecksumAddress } from 'ethereumjs-util'
import { fetchContractFromEtherscan } from './helpers/fetch-etherscan'
import { fetchContractFromSourcify } from './helpers/fetch-sourcify'
import { UUPSDeployedByteCode, UUPSCompilerVersion } from './constants/uups'
import { UUPSDeployedByteCode, UUPSCompilerVersion, UUPSOptimize, UUPSRuns, UUPSEvmVersion, UUPSLanguage } from './constants/uups'
const profile = {
name: 'fetchAndCompile',
@ -52,13 +52,14 @@ export class FetchAndCompile extends Plugin {
if (codeAtAddress === '0x' + UUPSDeployedByteCode) { // proxy
const settings = {
version: UUPSCompilerVersion,
language: 'Solidity',
evmVersion: null,
optimize: false,
runs: 0
language: UUPSLanguage,
evmVersion: UUPSEvmVersion,
optimize: UUPSOptimize,
runs: UUPSRuns
}
const proxyUrl = 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/proxy/ERC1967/ERC1967Proxy.sol'
const compilationTargets = {
'proxy.sol': { content: 'import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.0/contracts/proxy/ERC1967/ERC1967Proxy.sol";' }
'proxy.sol': { content: `import "${proxyUrl}";` }
}
const compData = await compile(
compilationTargets,

File diff suppressed because one or more lines are too long

@ -100,6 +100,8 @@ export class OpenZeppelinProxy extends Plugin {
}
// re-use implementation contract's ABI for UI display in udapp and change name to proxy name.
implementationContractObject.contractName = implementationContractObject.name
implementationContractObject.implementationAddress = implAddress
implementationContractObject.name = proxyName
this.blockchain.deployProxy(data, implementationContractObject)
}
@ -116,6 +118,8 @@ export class OpenZeppelinProxy extends Plugin {
dataHex: fnData.replace('0x', '')
}
// re-use implementation contract's ABI for UI display in udapp and change name to proxy name.
newImplementationContractObject.contractName = newImplementationContractObject.name
newImplementationContractObject.implementationAddress = newImplAddress
newImplementationContractObject.name = proxyName
this.blockchain.upgradeProxy(proxyAddress, newImplAddress, data, newImplementationContractObject)
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-debug",
"version": "0.5.20",
"version": "0.5.21",
"description": "Tool to debug Ethereum transactions",
"contributors": [
{
@ -25,9 +25,10 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.50",
"@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-simulator": "^0.2.20",
"@remix-project/remix-astwalker": "^0.0.51",
"@remix-project/remix-lib": "^0.5.21",
"@remix-project/remix-simulator": "^0.2.21",
"@remix-project/remix-solidity": "^0.5.7",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"color-support": "^1.1.3",
@ -68,5 +69,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts",
"gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -7,6 +7,7 @@ import { CodeManager } from './code/codeManager'
import { contractCreationToken } from './trace/traceHelper'
import { EventManager } from './eventManager'
import { SolidityProxy, stateDecoder, localDecoder, InternalCallTree } from './solidity-decoder'
import { extractStateVariables } from './solidity-decoder/stateDecoder'
/**
* Ethdebugger is a wrapper around a few classes that helps debugging a transaction
@ -43,7 +44,11 @@ export class Ethdebugger {
this.event = new EventManager()
this.traceManager = new TraceManager({ web3: this.web3 })
this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) })
this.solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager),
getCode: this.codeManager.getCode.bind(this.codeManager),
compilationResult: this.compilationResult
})
this.storageResolver = null
const includeLocalVariables = true
@ -58,7 +63,11 @@ export class Ethdebugger {
setManagers () {
this.traceManager = new TraceManager({ web3: this.web3 })
this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager) })
this.solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager),
getCode: this.codeManager.getCode.bind(this.codeManager),
compilationResult: this.compilationResult
})
this.storageResolver = null
const includeLocalVariables = true
@ -74,20 +83,19 @@ export class Ethdebugger {
this.codeManager.resolveStep(index, this.tx)
}
setCompilationResult (compilationResult) {
this.solidityProxy.reset((compilationResult && compilationResult.data) || {}, (compilationResult && compilationResult.source && compilationResult.source.sources) || {})
}
async sourceLocationFromVMTraceIndex (address, stepIndex) {
return this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts)
const compilationResult = await this.compilationResult(address)
return this.callTree.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, stepIndex, compilationResult.data.contracts)
}
async getValidSourceLocationFromVMTraceIndex (address, stepIndex) {
return this.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, stepIndex, this.solidityProxy.contracts)
const compilationResult = await this.compilationResult(address)
return this.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, stepIndex, compilationResult.data.contracts)
}
async sourceLocationFromInstructionIndex (address, instIndex, callback) {
return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, this.solidityProxy.contracts)
const compilationResult = await this.compilationResult(address)
return this.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, instIndex, compilationResult.data.contracts)
}
/* breakpoint */
@ -100,6 +108,27 @@ export class Ethdebugger {
return this.callTree.findScope(step)
}
async decodeLocalVariableByIdAtCurrentStep (step: number, id: number) {
const variable = this.callTree.getLocalVariableById(id)
if (!variable) return null
const stack = this.traceManager.getStackAt(step)
const memory = this.traceManager.getMemoryAt(step)
const address = this.traceManager.getCurrentCalledAddressAt(step)
const calldata = this.traceManager.getCallDataAt(step)
const storageViewer = new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
return await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageViewer, calldata, null, variable)
}
async decodeStateVariableByIdAtCurrentStep (step: number, id: number) {
const stateVars = await this.solidityProxy.extractStateVariablesAt(step)
const variable = stateVars.filter((el) => el.variable.id === id)
if (variable && variable.length) {
const state = await this.decodeStateAt(step, variable)
return state[variable[0].name]
}
return null
}
async decodeLocalsAt (step, sourceLocation, callback) {
try {
const stack = this.traceManager.getStackAt(step)
@ -123,14 +152,16 @@ export class Ethdebugger {
/* decode state */
async extractStateAt (step) {
return this.solidityProxy.extractStateVariablesAt(step)
return await this.solidityProxy.extractStateVariablesAt(step)
}
async decodeStateAt (step, stateVars, callback) {
async decodeStateAt (step, stateVars, callback?) {
try {
callback = callback || (() => {})
const address = this.traceManager.getCurrentCalledAddressAt(step)
const storageViewer = new StorageViewer({ stepIndex: step, tx: this.tx, address: address }, this.storageResolver, this.traceManager)
const result = await stateDecoder.decodeState(stateVars, storageViewer)
callback(result)
return result
} catch (error) {
callback(error)
@ -149,6 +180,7 @@ export class Ethdebugger {
unLoad () {
this.traceManager.init()
this.codeManager.clear()
this.solidityProxy.reset()
this.event.trigger('traceUnloaded')
}
@ -160,7 +192,6 @@ export class Ethdebugger {
this.tx = tx
await this.traceManager.resolveTrace(tx)
this.setCompilationResult(await this.compilationResult(tx.to))
this.event.trigger('newTraceLoaded', [this.traceManager.trace])
if (this.breakpointManager && this.breakpointManager.hasBreakpoint()) {
this.breakpointManager.jumpNextBreakpoint(false)

@ -14,7 +14,6 @@ export class BreakpointManager {
callTree
solidityProxy
breakpoints
locationToRowConverter
/**
* constructor
@ -22,13 +21,12 @@ export class BreakpointManager {
* @param {Object} _debugger - type of EthDebugger
* @return {Function} _locationToRowConverter - function implemented by editor which return a column/line position for a char source location
*/
constructor ({ traceManager, callTree, solidityProxy, locationToRowConverter }) {
constructor ({ traceManager, callTree, solidityProxy }) {
this.event = new EventManager()
this.traceManager = traceManager
this.callTree = callTree
this.solidityProxy = solidityProxy
this.breakpoints = {}
this.locationToRowConverter = locationToRowConverter
}
setManagers ({ traceManager, callTree, solidityProxy }) {
@ -43,7 +41,7 @@ export class BreakpointManager {
*
*/
async jumpNextBreakpoint (fromStep, defaultToLimit) {
if (!this.locationToRowConverter) {
if (!this.callTree.locationAndOpcodePerVMTraceIndex[fromStep]) {
return console.log('row converter not provided')
}
this.jump(fromStep || 0, 1, defaultToLimit, this.traceManager.trace)
@ -55,7 +53,7 @@ export class BreakpointManager {
*
*/
async jumpPreviousBreakpoint (fromStep, defaultToLimit) {
if (!this.locationToRowConverter) {
if (!this.callTree.locationAndOpcodePerVMTraceIndex[fromStep]) {
return console.log('row converter not provided')
}
this.jump(fromStep || 0, -1, defaultToLimit, this.traceManager.trace)
@ -89,6 +87,8 @@ export class BreakpointManager {
async jump (fromStep, direction, defaultToLimit, trace) {
this.event.trigger('locatingBreakpoint', [])
let sourceLocation
let lineColumn
let contractAddress
let previousSourceLocation
let currentStep = fromStep + direction
let lineHadBreakpoint = false
@ -96,13 +96,15 @@ export class BreakpointManager {
while (currentStep > 0 && currentStep < trace.length) {
try {
previousSourceLocation = sourceLocation
sourceLocation = await this.callTree.extractValidSourceLocation(currentStep)
const stepInfo = this.callTree.locationAndOpcodePerVMTraceIndex[currentStep]
sourceLocation = stepInfo.sourceLocation
lineColumn = stepInfo.lineColumnPos
contractAddress = stepInfo.contractAddress
} catch (e) {
console.log('cannot jump to breakpoint ' + e)
currentStep += direction
continue
}
const lineColumn = await this.locationToRowConverter(sourceLocation)
if (!initialLine) initialLine = lineColumn
if (initialLine.start.line !== lineColumn.start.line) {
@ -112,7 +114,7 @@ export class BreakpointManager {
return
}
}
if (this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line)) {
if (await this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line, contractAddress)) {
lineHadBreakpoint = true
if (this.hitLine(currentStep, sourceLocation, previousSourceLocation, trace)) {
return
@ -137,10 +139,12 @@ export class BreakpointManager {
*
* @param {Int} fileIndex - index of the file content (from the compilation result)
* @param {Int} line - line number where looking for breakpoint
* @param {String} contractAddress - address of the contract being executed
* @return {Bool} return true if the given @arg fileIndex @arg line refers to a breakpoint
*/
hasBreakpointAtLine (fileIndex, line) {
const filename = this.solidityProxy.fileNameFromIndex(fileIndex)
async hasBreakpointAtLine (fileIndex, line, contractAddress) {
const compResult = await this.solidityProxy.compilationResult(contractAddress)
const filename = Object.keys(compResult.data.contracts)[fileIndex]
if (!(filename && this.breakpoints[filename])) {
return false
}

@ -36,12 +36,7 @@ export class Debugger {
this.breakPointManager = new BreakpointManager({
traceManager,
callTree,
solidityProxy,
locationToRowConverter: async (sourceLocation) => {
const compilationResult = await this.compilationResult()
if (!compilationResult) return { start: null, end: null }
return await this.offsetToLineColumnConverter.offsetToLineColumn(sourceLocation, sourceLocation.file, compilationResult.source.sources, compilationResult.data.sources)
}
solidityProxy
})
this.breakPointManager.event.register('breakpointStep', (step) => {

@ -32,10 +32,7 @@ export class DebuggerSolidityState {
}
if (this.stepManager.currentStepIndex !== index) return
if (!this.solidityProxy.loaded()) {
return this.event.trigger('solidityStateMessage', ['invalid step index'])
}
if (!this.storageResolver) {
return
}

@ -34,17 +34,35 @@ export class DebuggerStepManager {
this.traceLength = newLength
this.codeTraceLength = this.calculateCodeLength()
}
setTimeout(() => {
this.jumpTo(0) // wait for the ui to render
}, 500)
})
})
this.debugger.callTree.event.register('callTreeBuildFailed', () => {
setTimeout(() => {
this.jumpTo(0)
}, 500)
})
this.debugger.callTree.event.register('callTreeNotReady', () => {
setTimeout(() => {
this.jumpTo(0)
}, 500)
})
this.debugger.callTree.event.register('noCallTreeAvailable', () => {
setTimeout(() => {
this.jumpTo(0)
}, 500)
})
this.debugger.callTree.event.register('callTreeReady', () => {
if (this.debugger.callTree.functionCallStack.length) {
setTimeout(() => {
this.jumpTo(this.debugger.callTree.functionCallStack[0]) // wait for the ui to be render
this.jumpTo(this.debugger.callTree.functionCallStack[0])
}, 500)
} else {
setTimeout(() => {
this.jumpTo(0)
}, 500)
}
})

@ -354,7 +354,8 @@ function computeOffsets (types, stateDefinitions, contractName, location) {
storagelocation: {
offset: !hasStorageSlots ? 0 : storagelocation.offset,
slot: !hasStorageSlots ? 0 : storagelocation.slot
}
},
variable
})
if (hasStorageSlots) {
if (type.storageSlots === 1 && storagelocation.offset + type.storageBytes <= 32) {

@ -46,6 +46,9 @@ export class InternalCallTree {
pendingConstructorId: number
pendingConstructor
constructorsStartExecution
variables: {
[Key: number]: any
}
/**
* constructor
@ -64,20 +67,21 @@ export class InternalCallTree {
this.traceManager = traceManager
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources })
debuggerEvent.register('newTraceLoaded', (trace) => {
debuggerEvent.register('newTraceLoaded', async (trace) => {
const time = Date.now()
this.reset()
if (!this.solidityProxy.loaded()) {
this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree'])
} else {
// each recursive call to buildTree represent a new context (either call, delegatecall, internal function)
const calledAddress = traceManager.getCurrentCalledAddressAt(0)
const isCreation = isContractCreation(calledAddress)
// each recursive call to buildTree represent a new context (either call, delegatecall, internal function)
const calledAddress = traceManager.getCurrentCalledAddressAt(0)
const isCreation = isContractCreation(calledAddress)
const scopeId = '1'
this.scopeStarts[0] = scopeId
this.scopes[scopeId] = { firstStep: 0, locals: {}, isCreation, gasCost: 0 }
const scopeId = '1'
this.scopeStarts[0] = scopeId
this.scopes[scopeId] = { firstStep: 0, locals: {}, isCreation, gasCost: 0 }
const compResult = await this.solidityProxy.compilationResult(calledAddress)
if (!compResult) {
this.event.trigger('noCallTreeAvailable', [])
} else {
buildTree(this, 0, scopeId, isCreation).then((result) => {
if (result.error) {
this.event.trigger('callTreeBuildFailed', [result.error])
@ -122,6 +126,7 @@ export class InternalCallTree {
this.pendingConstructorId = -1
this.constructorsStartExecution = {}
this.pendingConstructor = null
this.variables = {}
}
/**
@ -178,7 +183,8 @@ export class InternalCallTree {
async extractSourceLocation (step: number, address?: string) {
try {
if (!address) address = this.traceManager.getCurrentCalledAddressAt(step)
return await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts)
const compilationResult = await this.solidityProxy.compilationResult(address)
return await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, compilationResult.data.contracts)
} catch (error) {
throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error)
}
@ -187,7 +193,8 @@ export class InternalCallTree {
async extractValidSourceLocation (step: number, address?: string) {
try {
if (!address) address = this.traceManager.getCurrentCalledAddressAt(step)
return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts)
const compilationResult = await this.solidityProxy.compilationResult(address)
return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, compilationResult.data.contracts)
} catch (error) {
throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error)
}
@ -203,12 +210,17 @@ export class InternalCallTree {
}
throw new Error('Could not find gas cost per line')
}
getLocalVariableById (id: number) {
return this.variables[id]
}
}
async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, contractObj?, sourceLocation?, validSourceLocation?) {
let subScope = 1
if (functionDefinition) {
await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, validSourceLocation)
const address = tree.traceManager.getCurrentCalledAddressAt(step)
await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, validSourceLocation, address)
}
function callDepthChange (step, trace) {
@ -230,10 +242,13 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?,
let currentSourceLocation = sourceLocation || { start: -1, length: -1, file: -1, jump: '-' }
let previousSourceLocation = currentSourceLocation
let previousValidSourceLocation = validSourceLocation || currentSourceLocation
let compilationResult
let currentAddress = ''
while (step < tree.traceManager.trace.length) {
let sourceLocation
let validSourceLocation
let address
try {
address = tree.traceManager.getCurrentCalledAddressAt(step)
sourceLocation = await tree.extractSourceLocation(step, address)
@ -242,8 +257,11 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?,
tree.reducedTrace.push(step)
currentSourceLocation = sourceLocation
}
const amountOfSources = tree.sourceLocationTracker.getTotalAmountOfSources(address, tree.solidityProxy.contracts)
if (currentAddress !== address) {
compilationResult = await tree.solidityProxy.compilationResult(address)
currentAddress = address
}
const amountOfSources = tree.sourceLocationTracker.getTotalAmountOfSources(address, compilationResult.data.contracts)
if (tree.sourceLocationTracker.isInvalidSourceLocation(currentSourceLocation, amountOfSources)) { // file is -1 or greater than amount of sources
validSourceLocation = previousValidSourceLocation
} else
@ -266,8 +284,8 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?,
if (tree.offsetToLineColumnConverter) {
try {
const generatedSources = tree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
const astSources = Object.assign({}, tree.solidityProxy.sources)
const sources = Object.assign({}, tree.solidityProxy.sourcesCode)
const astSources = Object.assign({}, compilationResult.data.sources)
const sources = Object.assign({}, compilationResult.source.sources)
if (generatedSources) {
for (const genSource of generatedSources) {
astSources[genSource.name] = { id: genSource.id, ast: genSource.ast }
@ -290,12 +308,12 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?,
}
}
tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail, lineColumnPos }
tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail, lineColumnPos, contractAddress: address }
tree.scopes[scopeId].gasCost += stepDetail.gasCost
const contractObj = await tree.solidityProxy.contractObjectAtAddress(address)
const generatedSources = getGeneratedSources(tree, scopeId, contractObj)
const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources)
const functionDefinition = await resolveFunctionDefinition(tree, sourceLocation, generatedSources, address)
const isInternalTxInstrn = isCallInstruction(stepDetail)
const isCreateInstrn = isCreateInstruction(stepDetail)
@ -321,7 +339,7 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?,
tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt
tree.pendingConstructorExecutionAt = -1
tree.pendingConstructorId = -1
await registerFunctionParameters(tree, tree.pendingConstructor, step, newScopeId, contractObj, previousValidSourceLocation)
await registerFunctionParameters(tree, tree.pendingConstructor, step, newScopeId, contractObj, previousValidSourceLocation, address)
tree.pendingConstructor = null
}
const externalCallResult = await buildTree(tree, nextStep, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation)
@ -342,7 +360,7 @@ async function buildTree (tree, step, scopeId, isCreation, functionDefinition?,
// if not, we are in the current scope.
// We check in `includeVariableDeclaration` if there is a new local variable in scope for this specific `step`
if (tree.includeLocalVariables) {
await includeVariableDeclaration(tree, step, sourceLocation, scopeId, contractObj, generatedSources)
await includeVariableDeclaration(tree, step, sourceLocation, scopeId, contractObj, generatedSources, address)
}
previousSourceLocation = sourceLocation
previousValidSourceLocation = validSourceLocation
@ -364,14 +382,14 @@ function getGeneratedSources (tree, scopeId, contractObj) {
return null
}
async function registerFunctionParameters (tree, functionDefinition, step, scopeId, contractObj, sourceLocation) {
async function registerFunctionParameters (tree, functionDefinition, step, scopeId, contractObj, sourceLocation, address) {
tree.functionCallStack.push(step)
const functionDefinitionAndInputs = { functionDefinition, inputs: [] }
// means: the previous location was a function definition && JUMPDEST
// => we are at the beginning of the function and input/output are setup
try {
const stack = tree.traceManager.getStackAt(step)
const states = tree.solidityProxy.extractStatesDefinitions()
const states = await tree.solidityProxy.extractStatesDefinitions(address)
if (functionDefinition.parameters) {
const inputs = functionDefinition.parameters
const outputs = functionDefinition.returnParameters
@ -389,9 +407,9 @@ async function registerFunctionParameters (tree, functionDefinition, step, scope
tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs
}
async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, contractObj, generatedSources) {
async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, contractObj, generatedSources, address) {
let states = null
const variableDeclarations = resolveVariableDeclaration(tree, sourceLocation, generatedSources)
const variableDeclarations = await resolveVariableDeclaration(tree, sourceLocation, generatedSources, address)
// using the vm trace step, the current source location and the ast,
// we check if the current vm trace step target a new ast node of type VariableDeclaration
// that way we know that there is a new local variable from here.
@ -403,16 +421,18 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId,
// the stack length at this point is where the value of the new local variable will be stored.
// so, either this is the direct value, or the offset in memory. That depends on the type.
if (variableDeclaration.name !== '') {
states = tree.solidityProxy.extractStatesDefinitions()
states = await tree.solidityProxy.extractStatesDefinitions(address)
let location = extractLocationFromAstVariable(variableDeclaration)
location = location === 'default' ? 'storage' : location
// we push the new local variable in our tree
tree.scopes[scopeId].locals[variableDeclaration.name] = {
const newVar = {
name: variableDeclaration.name,
type: parseType(variableDeclaration.typeDescriptions.typeString, states, contractObj.name, location),
stackDepth: stack.length,
sourceLocation: sourceLocation
}
tree.scopes[scopeId].locals[variableDeclaration.name] = newVar
tree.variables[variableDeclaration.id] = newVar
}
} catch (error) {
console.log(error)
@ -424,9 +444,9 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId,
// this extract all the variable declaration for a given ast and file
// and keep this in a cache
function resolveVariableDeclaration (tree, sourceLocation, generatedSources) {
async function resolveVariableDeclaration (tree, sourceLocation, generatedSources, address) {
if (!tree.variableDeclarationByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
const ast = await tree.solidityProxy.ast(sourceLocation, generatedSources, address)
if (ast) {
tree.variableDeclarationByFile[sourceLocation.file] = extractVariableDeclarations(ast, tree.astWalker)
} else {
@ -438,9 +458,9 @@ function resolveVariableDeclaration (tree, sourceLocation, generatedSources) {
// this extract all the function definition for a given ast and file
// and keep this in a cache
function resolveFunctionDefinition (tree, sourceLocation, generatedSources) {
async function resolveFunctionDefinition (tree, sourceLocation, generatedSources, address) {
if (!tree.functionDefinitionByFile[sourceLocation.file]) {
const ast = tree.solidityProxy.ast(sourceLocation, generatedSources)
const ast = await tree.solidityProxy.ast(sourceLocation, generatedSources, address)
if (ast) {
tree.functionDefinitionByFile[sourceLocation.file] = extractFunctionDefinitions(ast, tree.astWalker)
} else {
@ -482,7 +502,7 @@ function addParams (parameterList, tree, scopeId, states, contractObj, sourceLoc
let location = extractLocationFromAstVariable(param)
location = location === 'default' ? 'memory' : location
const attributesName = param.name === '' ? `$${inputParam}` : param.name
tree.scopes[scopeId].locals[attributesName] = {
const newParam = {
name: attributesName,
type: parseType(param.typeDescriptions.typeString, states, contractName, location),
stackDepth: stackDepth,
@ -490,7 +510,9 @@ function addParams (parameterList, tree, scopeId, states, contractObj, sourceLoc
abi: contractObj.contract.abi,
isParameter: true
}
tree.scopes[scopeId].locals[attributesName] = newParam
params.push(attributesName)
if (!tree.variables[param.id]) tree.variables[param.id] = newParam
}
stackPosition += dir
}

@ -13,39 +13,26 @@ export class SolidityProxy {
compilationResult
sourcesCode
constructor ({ getCurrentCalledAddressAt, getCode }) {
constructor ({ getCurrentCalledAddressAt, getCode, compilationResult }) {
this.cache = new Cache()
this.reset({})
this.getCurrentCalledAddressAt = getCurrentCalledAddressAt
this.getCode = getCode
this.compilationResult = compilationResult
}
/**
* reset the cache and apply a new @arg compilationResult
*
* @param {Object} compilationResult - result os a compilatiion (diectly returned by the compiler)
*/
reset (compilationResult, sources?) {
this.sources = compilationResult.sources // ast
this.contracts = compilationResult.contracts
if (sources) this.sourcesCode = sources
reset () {
this.cache.reset()
}
/**
* check if the object has been properly loaded
*
* @return {Bool} - returns true if a compilation result has been applied
*/
loaded () {
return this.contracts !== undefined
}
/**
* retrieve the compiled contract name at the @arg vmTraceIndex (cached)
*
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName)
* @return {Object} a contract object
*/
async contractObjectAt (vmTraceIndex: number) {
const address = this.getCurrentCalledAddressAt(vmTraceIndex)
@ -53,46 +40,50 @@ export class SolidityProxy {
}
/**
* retrieve the compiled contract name at the @arg vmTraceIndex (cached)
* retrieve the compiled contract name at the @arg address (cached)
*
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName)
* @param {String} address - address of a contract
* @return {Object} a contract object
*/
async contractObjectAtAddress (address: string) {
if (this.cache.contractObjectByAddress[address]) {
return this.cache.contractObjectByAddress[address]
}
const code = await this.getCode(address)
const contract = contractObjectFromCode(this.contracts, code.bytecode, address)
const compilationResult = await this.compilationResult(address)
const contract = contractObjectFromCode(compilationResult.data.contracts, code.bytecode, address)
this.cache.contractObjectByAddress[address] = contract
return contract
}
/**
* extract the state variables of the given compiled @arg contractName (cached)
* extract the state variables of the given compiled @arg address (cached)
*
* @param {String} contractName - name of the contract to retrieve state variables from
* @return {Object} - returns state variables of @args contractName
* @param {String} address - address of the contract to retrieve state variables from
* @return {Object} - returns state variables of @args address
*/
extractStatesDefinitions () {
if (!this.cache.contractDeclarations) {
this.cache.contractDeclarations = extractContractDefinitions(this.sources)
async extractStatesDefinitions (address: string) {
const compilationResult = await this.compilationResult(address)
if (!this.cache.contractDeclarations[address]) {
this.cache.contractDeclarations[address] = extractContractDefinitions(compilationResult.data.sources)
}
if (!this.cache.statesDefinitions) {
this.cache.statesDefinitions = extractStatesDefinitions(this.sources, this.cache.contractDeclarations)
if (!this.cache.statesDefinitions[address]) {
this.cache.statesDefinitions[address] = extractStatesDefinitions(compilationResult.data.sources, this.cache.contractDeclarations[address])
}
return this.cache.statesDefinitions
return this.cache.statesDefinitions[address]
}
/**
* extract the state variables of the given compiled @arg contractName (cached)
*
* @param {String} contractName - name of the contract to retrieve state variables from
* @param {String} address - contract address
* @return {Object} - returns state variables of @args contractName
*/
extractStateVariables (contractName) {
async extractStateVariables (contractName, address) {
if (!this.cache.stateVariablesByContractName[contractName]) {
this.cache.stateVariablesByContractName[contractName] = extractStateVariables(contractName, this.sources)
const compilationResult = await this.compilationResult(address)
this.cache.stateVariablesByContractName[contractName] = extractStateVariables(contractName, compilationResult.data.sources)
}
return this.cache.stateVariablesByContractName[contractName]
}
@ -101,27 +92,31 @@ export class SolidityProxy {
* extract the state variables of the given compiled @arg vmtraceIndex (cached)
*
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the state variables
* @param {String} address - contract address
* @return {Object} - returns state variables of @args vmTraceIndex
*/
async extractStateVariablesAt (vmtraceIndex) {
async extractStateVariablesAt (vmtraceIndex, address) {
const contract = await this.contractObjectAt(vmtraceIndex)
return this.extractStateVariables(contract.name)
return await this.extractStateVariables(contract.name, address)
}
/**
* get the AST of the file declare in the @arg sourceLocation
*
* @param {Object} sourceLocation - source location containing the 'file' to retrieve the AST from
* @param {Object} generatedSources - compiler generated sources
* @param {String} address - contract address
* @return {Object} - AST of the current file
*/
ast (sourceLocation, generatedSources) {
const file = this.fileNameFromIndex(sourceLocation.file)
async ast (sourceLocation, generatedSources, address) {
const compilationResult = await this.compilationResult(address)
const file = this.fileNameFromIndex(sourceLocation.file, compilationResult.data)
if (!file && generatedSources && generatedSources.length) {
for (const source of generatedSources) {
if (source.id === sourceLocation.file) return source.ast
}
} else if (this.sources[file]) {
return this.sources[file].ast
} else if (compilationResult.data.sources[file]) {
return compilationResult.data.sources[file].ast
}
return null
}
@ -130,10 +125,11 @@ export class SolidityProxy {
* get the filename refering to the index from the compilation result
*
* @param {Int} index - index of the filename
* @param {Object} compilationResult - current compilation result
* @return {String} - filename
*/
fileNameFromIndex (index) {
return Object.keys(this.contracts)[index]
fileNameFromIndex (index, compilationResult) {
return Object.keys(compilationResult.contracts)[index]
}
}
@ -163,7 +159,7 @@ class Cache {
reset () {
this.contractObjectByAddress = {}
this.stateVariablesByContractName = {}
this.contractDeclarations = null
this.statesDefinitions = null
this.contractDeclarations = {}
this.statesDefinitions = {}
}
}

@ -1,4 +1,5 @@
import tape from 'tape'
import { CompilerAbstract } from '@remix-project/remix-solidity'
import deepequal from 'deep-equal'
import { compilerInput } from './helpers/compilerHelper'
import * as sourceMappingDecoder from '../src/source/sourceMappingDecoder'
@ -164,11 +165,27 @@ contract Ballot {
if (error) {
throw error
} else {
<<<<<<< HEAD
const debugManager = new Debugger({
compilationResult: function () {
return { data: output }
},
web3: web3
=======
const sources = {
target: 'test.sol',
sources: { 'test.sol': { content: ballot } }
}
const compilationResults = new CompilerAbstract('json', output, sources)
var debugManager = new Debugger({
compilationResult: () => compilationResults,
web3: web3,
offsetToLineColumnConverter: {
offsetToLineColumn: async (rawLocation) => {
return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, sourceMappingDecoder.getLinebreakPositions(ballot))
}
}
>>>>>>> 840f59824e8e6710503b2ed7eb8869e390562207
})
debugManager.callTree.event.register('callTreeReady', () => {
@ -273,9 +290,13 @@ function testDebugging (debugManager) {
tape('breakPointManager', (t) => {
t.plan(2)
const {traceManager, callTree, solidityProxy} = debugManager
<<<<<<< HEAD
const breakPointManager = new BreakpointManager({traceManager, callTree, solidityProxy, locationToRowConverter: async (rawLocation) => {
return sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, sourceMappingDecoder.getLinebreakPositions(ballot))
}})
=======
var breakPointManager = new BreakpointManager({traceManager, callTree, solidityProxy})
>>>>>>> 840f59824e8e6710503b2ed7eb8869e390562207
breakPointManager.add({fileName: 'test.sol', row: 39})

@ -1,5 +1,6 @@
'use strict'
import tape from 'tape'
import { CompilerAbstract } from '@remix-project/remix-solidity'
const compiler = require('solc')
const intLocal = require('./contracts/intLocal')
const miscLocal = require('./contracts/miscLocal')
@ -23,20 +24,53 @@ tape('solidity', function (t) {
async function test (st, privateKey) {
let output = compiler.compile(compilerInput(intLocal.contract))
output = JSON.parse(output)
await intLocalTest(st, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output, intLocal.contract)
let sources = {
target: 'test.sol',
sources: { 'test.sol': { content: intLocal.contract } }
}
let compilationResults = new CompilerAbstract('json', output, sources)
console.log('intLocalTest')
await intLocalTest(st, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, compilationResults, intLocal.contract)
output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output)
await miscLocalTest(st, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, output)
sources = {
target: 'test.sol',
sources: { 'test.sol': { content: miscLocal.contract } }
}
compilationResults = new CompilerAbstract('json', output, sources)
console.log('miscLocalTest')
await miscLocalTest(st, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, compilationResults, miscLocal.contract)
output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output)
await misc2LocalTest(st, privateKey, output.contracts['test.sol']['miscLocal2'].evm.bytecode.object, output)
sources = {
target: 'test.sol',
sources: { 'test.sol': { content: miscLocal.contract } }
}
compilationResults = new CompilerAbstract('json', output, sources)
console.log('misc2LocalTest')
await misc2LocalTest(st, privateKey, output.contracts['test.sol']['miscLocal2'].evm.bytecode.object, compilationResults, miscLocal.contract)
output = compiler.compile(compilerInput(structArrayLocal.contract))
output = JSON.parse(output)
await structArrayLocalTest(st, privateKey, output.contracts['test.sol']['structArrayLocal'].evm.bytecode.object, output)
sources = {
target: 'test.sol',
sources: { 'test.sol': { content: structArrayLocal.contract } }
}
compilationResults = new CompilerAbstract('json', output, sources)
console.log('structArrayLocalTest')
await structArrayLocalTest(st, privateKey, output.contracts['test.sol']['structArrayLocal'].evm.bytecode.object, compilationResults, structArrayLocal.contract)
output = compiler.compile(compilerInput(calldataLocal.contract))
output = JSON.parse(output)
await calldataLocalTest(st, privateKey, output.contracts['test.sol']['calldataLocal'].evm.bytecode.object, output)
sources = {
target: 'test.sol',
sources: { 'test.sol': { content: calldataLocal.contract } }
}
compilationResults = new CompilerAbstract('json', output, sources)
console.log('calldataLocalTest')
await calldataLocalTest(st, privateKey, output.contracts['test.sol']['calldataLocal'].evm.bytecode.object, compilationResults, calldataLocal.contract)
st.end()
}

@ -1,6 +1,7 @@
'use strict'
import deepequal from 'deep-equal'
import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder'
import * as vmCall from '../../vmCall'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
@ -9,7 +10,7 @@ import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree
import { EventManager } from '../../../src/eventManager'
import * as helper from './helper'
module.exports = async function (st, privateKey, contractBytecode, compilationResult) {
module.exports = async function (st, privateKey, contractBytecode, compilationResult, contractCode) {
let txHash
let web3
try {
@ -31,10 +32,21 @@ module.exports = async function (st, privateKey, contractBytecode, compilationRe
const traceManager = new TraceManager({ web3 })
const codeManager = new CodeManager(traceManager)
codeManager.clear()
const solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
const solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
getCode: codeManager.getCode.bind(codeManager),
compilationResult: () => compilationResult
})
const debuggerEvent = new EventManager()
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
const offsetToLineColumnConverter = {
offsetToLineColumn: (rawLocation) => {
return new Promise((resolve) => {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode)
resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks))
})
}
}
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter)
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})

@ -26,8 +26,11 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult,
const traceManager = new TraceManager({ web3 })
const codeManager = new CodeManager(traceManager)
codeManager.clear()
const solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
const solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
getCode: codeManager.getCode.bind(codeManager),
compilationResult: () => compilationResult
})
const debuggerEvent = new EventManager()
const offsetToLineColumnConverter = {
offsetToLineColumn: (rawLocation) => {

@ -7,8 +7,9 @@ import { EventManager } from '../../../src/eventManager'
import * as helper from './helper'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder'
module.exports = function (st, privateKey, contractBytecode, compilationResult) {
module.exports = function (st, privateKey, contractBytecode, compilationResult, contractCode) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve) => {
const web3 = await (vmCall as any).getWeb3();
@ -24,10 +25,21 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult)
const traceManager = new TraceManager({ web3 })
const codeManager = new CodeManager(traceManager)
codeManager.clear()
const solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
const solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
getCode: codeManager.getCode.bind(codeManager),
compilationResult: () => compilationResult
})
const debuggerEvent = new EventManager()
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
const offsetToLineColumnConverter = {
offsetToLineColumn: (rawLocation) => {
return new Promise((resolve) => {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode)
resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks))
})
}
}
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter)
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})

@ -7,9 +7,9 @@ import { EventManager } from '../../../src/eventManager'
import * as helper from './helper'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder'
module.exports = function (st, privateKey, contractBytecode, compilationResult) {
// eslint-disable-next-line no-async-promise-executor
module.exports = function (st, privateKey, contractBytecode, compilationResult, contractCode) {
return new Promise(async (resolve) => {
const web3 = await (vmCall as any).getWeb3();
(vmCall as any).sendTx(web3, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, hash) {
@ -24,10 +24,21 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult)
const traceManager = new TraceManager({ web3 })
const codeManager = new CodeManager(traceManager)
codeManager.clear()
const solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
const solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
getCode: codeManager.getCode.bind(codeManager),
compilationResult: () => compilationResult
})
const debuggerEvent = new EventManager()
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
const offsetToLineColumnConverter = {
offsetToLineColumn: (rawLocation) => {
return new Promise((resolve) => {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode)
resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks))
})
}
}
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter)
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})

@ -7,9 +7,9 @@ import { EventManager } from '../../../src/eventManager'
import * as helper from './helper'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder'
module.exports = function (st, privateKey, contractBytecode, compilationResult) {
// eslint-disable-next-line no-async-promise-executor
module.exports = function (st, privateKey, contractBytecode, compilationResult,contractCode) {
return new Promise(async (resolve) => {
const web3 = await (vmCall as any).getWeb3();
(vmCall as any).sendTx(web3, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, hash) {
@ -24,16 +24,28 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult)
const traceManager = new TraceManager({ web3 })
const codeManager = new CodeManager(traceManager)
codeManager.clear()
const solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
const solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
getCode: codeManager.getCode.bind(codeManager),
compilationResult: () => compilationResult
})
const debuggerEvent = new EventManager()
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
const offsetToLineColumnConverter = {
offsetToLineColumn: (rawLocation) => {
return new Promise((resolve) => {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode)
resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks))
})
}
}
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter)
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
helper.decodeLocals(st, 1622, traceManager, callTree, function (locals) {
try {
console.log('at 1622', locals)
st.equals(locals['bytesSimple'].length, '0x14')
st.equals(locals['bytesSimple'].value, '0x746573745f7375706572')
st.equals(locals['e'].value['a'].value, 'test')
@ -99,9 +111,10 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult)
st.fail(e.message)
}
})
helper.decodeLocals(st, 7, traceManager, callTree, function (locals) {
try {
console.log('at 7', locals)
st.equals(0, 0)
// st.equals(Object.keys(locals).length, 0)
} catch (e) {

@ -1,7 +1,12 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { EventManager } from '../../../src/eventManager'
import { compilerInput } from '../../helpers/compilerHelper'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
import { compile } from 'solc'
import * as stateDecoder from '../../../src/solidity-decoder/stateDecoder'
import { SolidityProxy } from '../../../src/solidity-decoder/solidityProxy'
import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree'
import * as vmCall from '../../vmCall'
import { StorageResolver } from '../../../src/storage/storageResolver'
import { StorageViewer } from '../../../src/storage/storageViewer'
@ -12,6 +17,11 @@ module.exports = async function testMappingStorage (st, cb) {
const privateKey = Buffer.from('503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb', 'hex')
let output = compile(compilerInput(mappingStorage.contract))
output = JSON.parse(output);
const sources = {
target: 'test.sol',
sources: { 'test.sol': { content: mappingStorage.contract } }
}
const compilationResults = new CompilerAbstract('json', output, sources)
const web3 = await (vmCall as any).getWeb3();
(vmCall as any).sendTx(web3, {nonce: 0, privateKey: privateKey}, null, 0, output.contracts['test.sol']['SimpleMappingState'].evm.bytecode.object, function (error, hash) {
if (error) {
@ -26,7 +36,7 @@ module.exports = async function testMappingStorage (st, cb) {
// const storage = await this.vm.stateManager.dumpStorage(data.to)
// (vmCall as any).web3().eth.getCode(tx.contractAddress).then((code) => console.log('code:', code))
// (vmCall as any).web3().debug.traceTransaction(hash).then((code) => console.log('trace:', code))
testMapping(st, privateKey, tx.contractAddress, output, web3, cb)
testMapping(st, privateKey, tx.contractAddress, output, compilationResults, web3, cb)
// st.end()
}
})
@ -34,7 +44,7 @@ module.exports = async function testMappingStorage (st, cb) {
})
}
function testMapping (st, privateKey, contractAddress, output, web3, cb) {
function testMapping (st, privateKey, contractAddress, output, compilationResults, web3, cb) {
(vmCall as any).sendTx(web3, {nonce: 1, privateKey: privateKey}, contractAddress, 0, '2fd0a83a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001074686973206973206120737472696e6700000000000000000000000000000000',
function (error, hash) {
if (error) {
@ -46,8 +56,24 @@ function testMapping (st, privateKey, contractAddress, output, web3, cb) {
console.log(error)
st.end(error)
} else {
const traceManager = new TraceManager({web3})
traceManager.resolveTrace(tx).then(() => {
const traceManager = new TraceManager({ web3 })
const codeManager = new CodeManager(traceManager)
codeManager.clear()
console.log(compilationResults)
const solidityProxy = new SolidityProxy({
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
getCode: codeManager.getCode.bind(codeManager),
compilationResult: () => compilationResults
})
const debuggerEvent = new EventManager()
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
callTree.event.register('callTreeNotReady', (reason) => {
st.fail(reason)
})
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
const storageViewer = new StorageViewer({
stepIndex: 268,
tx: tx,
@ -69,6 +95,12 @@ function testMapping (st, privateKey, contractAddress, output, web3, cb) {
st.end(reason)
})
})
traceManager.resolveTrace(tx).then(() => {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}).catch((error) => {
st.fail(error)
})
}
})
}

@ -8,9 +8,13 @@ const testMappingStorage = require('./stateTests/mapping')
tape('solidity', function (t) {
t.test('storage decoder', function (st) {
console.log('test int storage')
testIntStorage(st, function () {
console.log('test byte storage')
testByteStorage(st, function () {
console.log('test struct storage')
testStructArrayStorage(st, function () {
console.log('test mapping storage')
testMappingStorage(st, function () {
st.end()
})

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-lib",
"version": "0.5.20",
"version": "0.5.21",
"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": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-simulator",
"version": "0.2.20",
"version": "0.2.21",
"description": "Ethereum IDE and tools for the web",
"contributors": [
{
@ -21,7 +21,7 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-lib": "^0.5.21",
"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": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -1,6 +1,6 @@
{
"name": "@remix-project/remix-solidity",
"version": "0.5.6",
"version": "0.5.7",
"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.20",
"@remix-project/remix-lib": "^0.5.21",
"async": "^2.6.2",
"eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10",
@ -57,5 +57,5 @@
},
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts",
"gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -9,7 +9,7 @@ export default (sources: Source, opts: CompilerInputOptions): string => {
settings: {
optimizer: {
enabled: opts.optimize === true || opts.optimize === 1,
runs: opts.runs || 200
runs: opts.runs > -1 ? opts.runs : 200
},
libraries: opts.libraries,
outputSelection: {

@ -1,23 +0,0 @@
module.exports = {
preset: '../../jest.config.js',
verbose: true,
silent: false, // Silent console messages, specially the 'remix-simulator' ones
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
transformIgnorePatterns: ["/node_modules/", "/dist/", "\\.pnp\\.[^\\\/]+$"],
rootDir: "./",
testTimeout: 40000,
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'],
// Coverage
collectCoverage: true,
coverageReporters: ['text', 'text-summary'],
collectCoverageFrom: [
"**/*.ts",
"!**/sol/**",
"!src/types.ts",
"!src/logger.ts"
],
coverageDirectory: '../../coverage/libs/remix-tests'
};

@ -1,12 +1,9 @@
{
"name": "@remix-project/remix-tests",
"version": "0.2.20",
"version": "0.2.21",
"description": "Tool to test Solidity smart contracts",
"main": "src/index.js",
"types": "./src/index.d.ts",
"scripts": {
"test": "node --expose-gc ./../../node_modules/.bin/jest --runInBand --logHeapUsage "
},
"contributors": [
{
"name": "Iuri Matias",
@ -20,6 +17,10 @@
"bin": {
"remix-tests": "./bin/remix-tests"
},
"scripts": {
"build": "tsc",
"test": "./../../node_modules/.bin/mocha -r tsconfig-paths/register -r ts-node/register 'tests/**/*.spec.ts'"
},
"publishConfig": {
"access": "public"
},
@ -39,10 +40,10 @@
"@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-simulator": "^0.2.20",
"@remix-project/remix-solidity": "^0.5.6",
"@remix-project/remix-url-resolver": "^0.0.41",
"@remix-project/remix-lib": "^0.5.21",
"@remix-project/remix-simulator": "^0.2.21",
"@remix-project/remix-solidity": "^0.5.7",
"@remix-project/remix-url-resolver": "^0.0.42",
"ansi-gray": "^0.1.1",
"async": "^2.6.0",
"axios": "1.1.2",
@ -80,5 +81,5 @@
"typescript": "^3.3.1"
},
"typings": "src/index.d.ts",
"gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
"gitHead": "b9f8241a57d3083c94d6499efa8e2ea2ac8ef2d1"
}

@ -1,7 +1,9 @@
import { spawnSync, execSync } from 'child_process'
import { resolve } from 'path'
import { expect } from 'chai';
describe('testRunner: remix-tests CLI', () => {
describe('testRunner: remix-tests CLI', function(){
this.timeout(120000)
// remix-tests binary, after build, is used as executable
const executablePath = resolve(__dirname + '/../../../dist/libs/remix-tests/bin/remix-tests')
@ -20,117 +22,135 @@ describe('testRunner: remix-tests CLI', () => {
}
describe('test various CLI options', () => {
test('remix-tests version', () => {
describe('test various CLI options', function() {
it('remix-tests version', () => {
const res = spawnSync(executablePath, ['-V'])
// eslint-disable-next-line @typescript-eslint/no-var-requires
expect(res.stdout.toString().trim()).toBe(require('../package.json').version)
expect(res.stdout.toString().trim()).to.equal(require('../package.json').version)
})
test('remix-tests help', () => {
it('remix-tests help', () => {
const res = spawnSync(executablePath, ['-h'])
const expectedHelp = `Usage: remix-tests [options] [command] <file_path>`
expect(res.stdout.toString().trim()).toContain(expectedHelp)
const expectedHelp = `Usage: remix-tests [options] [command]
Options:
-V, --version output the version number
-c, --compiler <string> set compiler version (e.g: 0.6.1, 0.7.1 etc)
-e, --evm <string> set EVM version (e.g: petersburg, istanbul etc)
-o, --optimize <bool> enable/disable optimization
-r, --runs <number> set runs (e.g: 150, 250 etc)
-v, --verbose <level> set verbosity level (0 to 5)
-h, --help output usage information
Commands:
version output the version number
help output usage information`
expect(res.stdout.toString().trim()).to.equal(expectedHelp)
})
test('remix-tests running a test file', () => {
it('remix-tests running a test file', function() {
const res = spawnSync(executablePath, [resolve(__dirname + '/examples_0/assert_ok_test.sol')])
//console.log(res.stdout.toString())
// match initial lines
expect(res.stdout.toString().trim()).toMatch(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).toMatch(/creation of library remix_tests.sol:Assert pending.../)
expect(res.stdout.toString().trim()).to.match(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).to.match(/creation of library remix_tests.sol:Assert pending.../)
// match test result
expect(res.stdout.toString().trim()).toMatch(/AssertOkTest/)
expect(res.stdout.toString().trim()).toMatch(/AssertOkTest okPassTest/) // check if console.log is printed
expect(res.stdout.toString().trim()).toMatch(/Ok pass test/)
expect(res.stdout.toString().trim()).toMatch(/AssertOkTest okFailTest/) // check if console.log is printed
expect(res.stdout.toString().trim()).toMatch(/Ok fail test/)
expect(res.stdout.toString().trim()).to.match(/AssertOkTest/)
expect(res.stdout.toString().trim()).to.match(/AssertOkTest okPassTest/) // check if console.log is printed
expect(res.stdout.toString().trim()).to.match(/Ok pass test/)
expect(res.stdout.toString().trim()).to.match(/AssertOkTest okFailTest/) // check if console.log is printed
expect(res.stdout.toString().trim()).to.match(/Ok fail test/)
// match fail test details
expect(res.stdout.toString().trim()).toMatch(/Expected value should be ok to: true/)
expect(res.stdout.toString().trim()).toMatch(/Received: false/)
expect(res.stdout.toString().trim()).toMatch(/Message: okFailTest fails/)
expect(res.stdout.toString().trim()).to.match(/Expected value should be ok to: true/)
expect(res.stdout.toString().trim()).to.match(/Received: false/)
expect(res.stdout.toString().trim()).to.match(/Message: okFailTest fails/)
})
test('remix-tests running a test file with custom compiler version', () => {
it('remix-tests running a test file with custom compiler version', () => {
const res = spawnSync(executablePath, ['--compiler', '0.7.4', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('Compiler version set to 0.7.4. Latest version is')).toBeTruthy()
expect(res.stdout.toString().trim().includes('Loading remote solc version v0.7.4+commit.3f05b770 ...')).toBeTruthy()
expect(res.stdout.toString().trim()).toMatch(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).toMatch(/creation of library remix_tests.sol:Assert pending.../)
expect(res.stdout.toString().trim()).to.contain('Compiler version set to 0.7.4. Latest version is')
expect(res.stdout.toString().trim()).to.contain('Loading remote solc version v0.7.4+commit.3f05b770 ...')
expect(res.stdout.toString().trim()).to.match(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).to.match(/creation of library remix_tests.sol:Assert pending.../)
// match test result
expect(res.stdout.toString().trim()).toMatch(/Ok pass test/)
expect(res.stdout.toString().trim()).toMatch(/Ok fail test/)
expect(res.stdout.toString().trim()).to.match(/Ok pass test/)
expect(res.stdout.toString().trim()).to.match(/Ok fail test/)
// match fail test details
expect(res.stdout.toString().trim()).toMatch(/Message: okFailTest fails/)
expect(res.stdout.toString().trim()).to.match(/Message: okFailTest fails/)
})
test('remix-tests running a test file with unavailable custom compiler version (should fail)', () => {
it('remix-tests running a test file with unavailable custom compiler version (should fail)', () => {
const res = spawnSync(executablePath, ['--compiler', '1.10.4', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('No compiler found in releases with version 1.10.4')).toBeTruthy()
expect(res.stdout.toString().trim()).to.contain('No compiler found in releases with version 1.10.4')
})
test('remix-tests running a test file with custom EVM', () => {
it('remix-tests running a test file with custom EVM', () => {
const res = spawnSync(executablePath, ['--evm', 'petersburg', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('EVM set to petersburg')).toBeTruthy()
expect(res.stdout.toString().trim()).toMatch(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).toMatch(/creation of library remix_tests.sol:Assert pending.../)
expect(res.stdout.toString().trim()).to.contain('EVM set to petersburg')
expect(res.stdout.toString().trim()).to.match(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).to.match(/creation of library remix_tests.sol:Assert pending.../)
// match test result
expect(res.stdout.toString().trim()).toMatch(/Ok pass test/)
expect(res.stdout.toString().trim()).toMatch(/Ok fail test/)
expect(res.stdout.toString().trim()).to.match(/Ok pass test/)
expect(res.stdout.toString().trim()).to.match(/Ok fail test/)
// match fail test details
expect(res.stdout.toString().trim()).toMatch(/Message: okFailTest fails/)
expect(res.stdout.toString().trim()).to.match(/Message: okFailTest fails/)
})
test('remix-tests running a test file by enabling optimization', () => {
it('remix-tests running a test file by enabling optimization', () => {
const res = spawnSync(executablePath, ['--optimize', 'true', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('Optimization is enabled')).toBeTruthy()
expect(res.stdout.toString().trim()).toMatch(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).toMatch(/creation of library remix_tests.sol:Assert pending.../)
expect(res.stdout.toString().trim().includes('Optimization is enabled'))
expect(res.stdout.toString().trim()).to.match(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).to.match(/creation of library remix_tests.sol:Assert pending.../)
// match test result
expect(res.stdout.toString().trim()).toMatch(/Ok pass test/)
expect(res.stdout.toString().trim()).toMatch(/Ok fail test/)
expect(res.stdout.toString().trim()).to.match(/Ok pass test/)
expect(res.stdout.toString().trim()).to.match(/Ok fail test/)
// match fail test details
expect(res.stdout.toString().trim()).toMatch(/Message: okFailTest fails/)
expect(res.stdout.toString().trim()).to.match(/Message: okFailTest fails/)
})
test('remix-tests running a test file by enabling optimization and setting runs', () => {
it('remix-tests running a test file by enabling optimization and setting runs', () => {
const res = spawnSync(executablePath, ['--optimize', 'true', '--runs', '300', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('Optimization is enabled')).toBeTruthy()
expect(res.stdout.toString().trim().includes('Runs set to 300')).toBeTruthy()
expect(res.stdout.toString().trim()).toMatch(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).toMatch(/creation of library remix_tests.sol:Assert pending.../)
expect(res.stdout.toString().trim()).to.contain('Optimization is enabled')
expect(res.stdout.toString().trim()).to.contain('Runs set to 300')
expect(res.stdout.toString().trim()).to.match(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).to.match(/creation of library remix_tests.sol:Assert pending.../)
// match test result
expect(res.stdout.toString().trim()).toMatch(/Ok pass test/)
expect(res.stdout.toString().trim()).toMatch(/Ok fail test/)
expect(res.stdout.toString().trim()).to.match(/Ok pass test/)
expect(res.stdout.toString().trim()).to.match(/Ok fail test/)
// match fail test details
expect(res.stdout.toString().trim()).toMatch(/Message: okFailTest fails/)
expect(res.stdout.toString().trim()).to.match(/Message: okFailTest fails/)
})
test('remix-tests running a test file without enabling optimization and setting runs (should fail)', () => {
it('remix-tests running a test file without enabling optimization and setting runs (should fail)', () => {
const res = spawnSync(executablePath, ['--runs', '300', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('Optimization should be enabled for runs')).toBeTruthy()
expect(res.stdout.toString().trim()).to.contain('Optimization should be enabled for runs')
})
test('remix-tests running a test file with all options', () => {
it('remix-tests running a test file with all options', () => {
const res = spawnSync(executablePath, ['--compiler', '0.7.5', '--evm', 'istanbul', '--optimize', 'true', '--runs', '250', resolve(__dirname + '/examples_0/assert_ok_test.sol')])
// match initial lines
expect(res.stdout.toString().trim().includes('Compiler version set to 0.7.5. Latest version is')).toBeTruthy()
expect(res.stdout.toString().trim().includes('Loading remote solc version v0.7.5+commit.eb77ed08 ...')).toBeTruthy()
expect(res.stdout.toString().trim().includes('EVM set to istanbul')).toBeTruthy()
expect(res.stdout.toString().trim().includes('Optimization is enabled')).toBeTruthy()
expect(res.stdout.toString().trim().includes('Runs set to 250')).toBeTruthy()
expect(res.stdout.toString().trim()).toMatch(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).toMatch(/creation of library remix_tests.sol:Assert pending.../)
expect(res.stdout.toString().trim()).to.contain('Compiler version set to 0.7.5. Latest version is')
expect(res.stdout.toString().trim()).to.contain('Loading remote solc version v0.7.5+commit.eb77ed08 ...')
expect(res.stdout.toString().trim()).to.contain('EVM set to istanbul')
expect(res.stdout.toString().trim()).to.contain('Optimization is enabled')
expect(res.stdout.toString().trim()).to.contain('Runs set to 250')
expect(res.stdout.toString().trim()).to.match(/:: Running tests using remix-tests ::/)
expect(res.stdout.toString().trim()).to.match(/creation of library remix_tests.sol:Assert pending.../)
// match test result
expect(res.stdout.toString().trim()).toMatch(/Ok pass test/)
expect(res.stdout.toString().trim()).toMatch(/Ok fail test/)
expect(res.stdout.toString().trim()).to.match(/Ok pass test/)
expect(res.stdout.toString().trim()).to.match(/Ok fail test/)
// match fail test details
expect(res.stdout.toString().trim()).toMatch(/Message: okFailTest fails/)
expect(res.stdout.toString().trim()).to.match(/Message: okFailTest fails/)
})
})
})

@ -1,11 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node", "jest"],
"types": ["node", "mocha", "chai"],
"module": "commonjs",
"esModuleInterop": true,
"allowJs": true,
"rootDir": "./",
"allowJs": true
},
"include": ["**/*.ts"]
}

@ -19,7 +19,7 @@ const RemixApp = (props: IRemixAppUi) => {
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 [locale, setLocale] = useState<{ code:string; messages:any }>({ code:'en', messages:{} });
const sidePanelRef = useRef(null)
useEffect(() => {
@ -78,7 +78,7 @@ const RemixApp = (props: IRemixAppUi) => {
}
return (
<IntlProvider locale={locale.name} messages={locale.messages}>
<IntlProvider locale={locale.code} messages={locale.messages}>
<AppProvider value={value}>
<OriginWarning></OriginWarning>
<MatomoDialog hide={!appReady}></MatomoDialog>

@ -34,10 +34,15 @@ export const RemixUiCheckbox = ({
title,
visibility,
display = 'flex',
tooltipPlacement = 'right-start'
tooltipPlacement = 'right'
}: RemixUiCheckboxProps) => {
const childJSX = (
const childJSXWithTooltip = (
<CustomTooltip
tooltipText={title}
tooltipId={`${name}Tooltip`}
placement={tooltipPlacement}
>
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<input
id={id}
@ -53,16 +58,27 @@ export const RemixUiCheckbox = ({
{label}
</label>
</div>
</CustomTooltip>
)
const childJSX = (
<div className="listenOnNetwork_2A0YE0 custom-control custom-checkbox" style={{ display: display, alignItems: 'center', visibility: visibility } as CSSProperties } onClick={onClick}>
<input
id={id}
type={inputType}
onChange={onChange}
style={{ verticalAlign: 'bottom' }}
name={name}
className="custom-control-input"
checked={checked}
/>
<label className="form-check-label custom-control-label" id={`heading${categoryId}`} style={{ paddingTop: '0.15rem' }}>
{name ? <div className="font-weight-bold">{itemName}</div> : ''}
{label}
</label>
</div>
)
return (
<CustomTooltip
tooltipText={title}
tooltipId={`${name}Tooltip`}
placement={tooltipPlacement}
>
{childJSX}
</CustomTooltip>
title ? (childJSXWithTooltip) : (childJSX)
)
}

@ -169,6 +169,7 @@ export const ButtonNavigation = ({ stepOverBack, stepIntoBack, stepIntoForward,
placement={jumpMarkupStructure[x].placement}
tooltipText={jumpMarkupStructure[x].tooltipMsg}
tooltipId={jumpMarkupStructure[x].tooltipId}
key={`${jumpMarkupStructure[x].placement}-${jumpMarkupStructure[x].tooltipMsg}-${jumpMarkupStructure[x].tagId}`}
>
{jumpMarkupStructure[x].markup}
</CustomTooltip>

@ -37,6 +37,18 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
sourceLocationStatus: ''
})
if (props.onReady) {
props.onReady({
globalContext: () => {
return {
block: state.currentBlock,
tx: state.currentTransaction,
receipt: state.currentReceipt
}
}
})
}
const panelsRef = useRef<HTMLDivElement>(null)
const debuggerTopRef = useRef(null)
@ -63,7 +75,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}, [])
debuggerModule.onDebugRequested((hash, web3?) => {
if (hash) debug(hash, web3)
if (hash) return debug(hash, web3)
})
debuggerModule.onRemoveHighlights(async () => {
@ -214,7 +226,10 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
})
}
const startDebugging = async (blockNumber, txNumber, tx, optWeb3?) => {
if (state.debugger) unLoad()
if (state.debugger) {
unLoad()
await (new Promise((resolve) => setTimeout(() => resolve({}), 1000)))
}
if (!txNumber) return
setState(prevState => {
return {
@ -284,7 +299,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
})
setTimeout(async() => {
debuggerModule.onStartDebugging()
debuggerModule.onStartDebugging(debuggerInstance)
try {
await debuggerInstance.debug(blockNumber, txNumber, tx, () => {
listenToEvents(debuggerInstance, currentReceipt)
@ -314,6 +329,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}
}, 300)
handleResize()
return debuggerInstance
}
const debug = (txHash, web3?) => {
@ -325,7 +342,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
sourceLocationStatus: ''
}
})
startDebugging(null, txHash, null, web3)
return startDebugging(null, txHash, null, web3)
}
const stepManager = {
@ -388,9 +405,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div>
<i className="fas fa-info-triangle" aria-hidden="true"></i>
<span>
<FormattedMessage id='debugger.introduction' defaultMessage='When Debugging with a transaction hash,
if the contract is verified, Remix will try to fetch the source code from Sourcify or Etherscan. Put in your Etherscan API key in the Remix settings.
For supported networks, please see' />: <a href="https://sourcify.dev" target="__blank" >https://sourcify.dev</a> & <a href="https://etherscan.io/contractsVerified" target="__blank">https://etherscan.io/contractsVerified</a>
<FormattedMessage id='debugger.introduction' />: <a href="https://sourcify.dev" target="__blank" >https://sourcify.dev</a> & <a href="https://etherscan.io/contractsVerified" target="__blank">https://etherscan.io/contractsVerified</a>
</span>
</div> }
{ state.debugging && <StepManager stepManager={ stepManager } /> }

@ -51,10 +51,15 @@ export interface IDebuggerApi {
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
showMessage (title: string, message: string): void
onStartDebugging (): void // called when debug starts
onStartDebugging (debuggerBackend: any): void // called when debug starts
onStopDebugging (): void // called when debug stops
}
type globalContextFunction = () => { block, tx, receipt }
type onReadyParams = {
globalContext: globalContextFunction
}
export interface DebuggerUIProps {
debuggerAPI: IDebuggerApi
debuggerAPI: IDebuggerApi,
onReady?: (functions: onReadyParams) => void
}

@ -9,6 +9,7 @@ export const Slider = ({ jumpTo, sliderValue, traceLength }) => {
}, [sliderValue])
const setValue = (value) => {
if (value < 0) return
if (value === slider.current.value) return
slider.current.value = value
if (onChangeId.current) {

@ -4,7 +4,7 @@ import ButtonNavigator from '../button-navigator/button-navigator' // eslint-dis
export const StepManager = ({ stepManager: { jumpTo, traceLength, stepIntoBack, stepIntoForward, stepOverBack, stepOverForward, jumpOut, jumpNextBreakpoint, jumpPreviousBreakpoint, jumpToException, registerEvent } }) => {
const [state, setState] = useState({
sliderValue: 0,
sliderValue: -1,
revertWarning: '',
stepState: '',
jumpOutDisabled: true

@ -60,7 +60,7 @@ export const TxBrowser = ({ requestDebug, updateTxNumberFlag, unloadRequested, t
disabled={!state.txNumber }
style={{ pointerEvents: 'none', color: 'white' }}
>
<span><FormattedMessage id={`debugger.${debugging ? 'stopDebugging' : 'startDebugging'}`} defaultMessage={debugging ? 'Stop debugging' : 'Start debugging'} /></span>
<span><FormattedMessage id={`debugger.${debugging ? 'stopDebugging' : 'startDebugging'}`} /></span>
</button>
</div>
)
@ -76,7 +76,7 @@ export const TxBrowser = ({ requestDebug, updateTxNumberFlag, unloadRequested, t
type='text'
onChange={({ target: { value } }) => txInputChanged(value)}
onInput={txInputOnInput}
placeholder={intl.formatMessage({id: 'debugger.placeholder', defaultMessage: 'Transaction hash, should start with 0x'})}
placeholder={intl.formatMessage({id: 'debugger.placeholder'})}
data-id='debuggerTransactionInput'
disabled={debugging}
/>
@ -84,7 +84,7 @@ export const TxBrowser = ({ requestDebug, updateTxNumberFlag, unloadRequested, t
<div className='d-flex justify-content-center w-100 btn-group py-1'>
<CustomTooltip
placement="bottom"
tooltipText={<FormattedMessage id={`debugger.${debugging ? 'stopDebugging' : 'startDebugging'}`} defaultMessage={debugging ? 'Stop debugging' : 'Start debugging'} />}
tooltipText={<FormattedMessage id={`debugger.${debugging ? 'stopDebugging' : 'startDebugging'}`} />}
tooltipId={'debuggingButtontooltip'}
tooltipClasses="text-nowrap"
>

@ -145,6 +145,46 @@ export class RemixHoverProvider implements languages.HoverProvider {
getLinks(nodeAtPosition)
getDocs(nodeAtPosition)
// getScope(nodeAtPosition)
try {
if (nodeAtPosition?.name === 'msg') {
const global = await this.props.plugin.call('debugger', 'globalContext')
if (global !== null && global[nodeAtPosition?.name]) {
contents.push({
value: `GLOBAL VARIABLE ${nodeAtPosition.name}: ${JSON.stringify(global[nodeAtPosition?.name], null, '\t')}`
})
}
}
} catch (e) {}
try {
if (nodeAtPosition?.expression?.name === 'msg' && nodeAtPosition?.memberName) {
const global = await this.props.plugin.call('debugger', 'globalContext')
if (global !== null && global[nodeAtPosition?.expression?.name][nodeAtPosition.memberName] && global[nodeAtPosition?.expression?.name][nodeAtPosition.memberName]) {
contents.push({
value: `GLOBAL VARIABLE msg.${nodeAtPosition.memberName}: ${global[nodeAtPosition?.expression?.name][nodeAtPosition.memberName]}`
})
}
}
} catch (e) {}
try {
const decodedVar = await this.props.plugin.call('debugger', 'decodeLocalVariable', nodeAtPosition.id)
if (decodedVar !== null && decodedVar.type) {
contents.push({
value: `LOCAL VARIABLE ${nodeAtPosition.name}: ${typeof(decodedVar.value) === 'string' ? decodedVar.value : JSON.stringify(decodedVar.value, null, '\t')}`
})
}
} catch (e) {}
try {
const decodedVar = await this.props.plugin.call('debugger', 'decodeStateVariable', nodeAtPosition.id)
if (decodedVar !== null && decodedVar.type) {
contents.push({
value: `STATE VARIABLE ${nodeAtPosition.name}: ${typeof(decodedVar.value) === 'string' ? decodedVar.value : JSON.stringify(decodedVar.value, null, '\t')}`
})
}
} catch (e) {}
}
setTimeout(() => {

@ -141,6 +141,7 @@ export const EditorUI = (props: EditorUIProps) => {
const editorRef = useRef(null)
const monacoRef = useRef<Monaco>(null)
const currentFileRef = useRef('')
const currentUrlRef = useRef('')
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
// const registeredDecorations = useRef({}) // registered decorations
@ -295,6 +296,8 @@ export const EditorUI = (props: EditorUIProps) => {
useEffect(() => {
if (!editorRef.current || !props.currentFile) return
currentFileRef.current = props.currentFile
props.plugin.call('fileManager', 'getUrlFromPath', currentFileRef.current).then((url) => currentUrlRef.current = url.file)
const file = editorModelsState[props.currentFile]
editorRef.current.setModel(file.model)
editorRef.current.updateOptions({ readOnly: editorModelsState[props.currentFile].readOnly })
@ -512,7 +515,7 @@ export const EditorUI = (props: EditorUIProps) => {
const model = editorRef.current.getModel()
if (model) {
setCurrentBreakpoints(prevState => {
const currentFile = currentFileRef.current
const currentFile = currentUrlRef.current
if (!prevState[currentFile]) prevState[currentFile] = {}
const decoration = Object.keys(prevState[currentFile]).filter((line) => parseInt(line) === position.lineNumber)
if (decoration.length) {
@ -671,7 +674,6 @@ export const EditorUI = (props: EditorUIProps) => {
monacoRef.current.languages.registerHoverProvider('remix-solidity', new RemixHoverProvider(props, monaco))
monacoRef.current.languages.registerCompletionItemProvider('remix-solidity', new RemixCompletionProvider(props, monaco))
loadTypes(monacoRef.current)
}
@ -683,10 +685,14 @@ export const EditorUI = (props: EditorUIProps) => {
language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'}
onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount}
options={{ glyphMargin: true, readOnly: (!editorRef.current || !props.currentFile) }}
options={{ glyphMargin: true, readOnly: ((!editorRef.current || !props.currentFile) && editorModelsState[props.currentFile]?.readOnly) }}
defaultValue={defaultEditorValue}
/>
{editorModelsState[props.currentFile]?.readOnly && <span className='pl-4 h6 mb-0 w-100 alert-info position-absolute bottom-0 end-0'>
<i className="fas fa-lock-alt p-2"></i>
The file is opened in <b>read-only</b> mode.
</span>
}
</div>
)
}

@ -4,7 +4,7 @@ import { OverlayTrigger, Tooltip } from 'react-bootstrap';
import { CustomTooltipType } from '../../types/customtooltip'
export function CustomTooltip({ children, placement, tooltipId, tooltipClasses, tooltipText, tooltipTextClasses }: CustomTooltipType) {
export function CustomTooltip({ children, placement, tooltipId, tooltipClasses, tooltipText, tooltipTextClasses, delay }: CustomTooltipType) {
return (
<Fragment>
@ -15,6 +15,7 @@ export function CustomTooltip({ children, placement, tooltipId, tooltipClasses,
{typeof tooltipText === 'string' ? (<span className={tooltipTextClasses}>{tooltipText}</span>) : (tooltipText)}
</Tooltip>
}
delay={delay}
>
{children}
</OverlayTrigger>

@ -1,5 +1,5 @@
import { Placement } from 'react-bootstrap/esm/Overlay'
import { OverlayTriggerRenderProps } from 'react-bootstrap/esm/OverlayTrigger'
import { OverlayDelay, OverlayTriggerRenderProps } from 'react-bootstrap/esm/OverlayTrigger'
export type CustomTooltipType = {
children: React.ReactElement<any, string | React.JSXElementConstructor<any>> | ((props: OverlayTriggerRenderProps) => React.ReactNode),
@ -8,4 +8,5 @@ export type CustomTooltipType = {
tooltipClasses?:string,
tooltipText: string | JSX.Element,
tooltipTextClasses?: string
delay?: OverlayDelay
}

@ -1,8 +1,10 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useRef, useContext } from 'react'
import { FormattedMessage } from 'react-intl'
import { ThemeContext, themes } from '../themeContext'
import Carousel from 'react-multi-carousel'
import 'react-multi-carousel/lib/styles.css'
const _paq = window._paq = window._paq || [] // eslint-disable-line
function HomeTabFeatured() {
@ -10,7 +12,7 @@ function HomeTabFeatured() {
return (
<div className="pt-3 pl-2" id="hTFeaturedeSection">
<label style={{ fontSize: "1.2rem" }}>Featured</label>
<label style={{ fontSize: "1.2rem" }}><FormattedMessage id='home.featured' /></label>
<div className="mb-2">
<div className="w-100 d-flex flex-column" style={{ height: "200px" }}>
<ThemeContext.Provider value={themeFilter}>
@ -35,31 +37,41 @@ function HomeTabFeatured() {
dotListClass="position-relative mt-2"
>
<div className="mx-1 px-1 d-flex">
<img src={"assets/img/bgRemi.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<img className="mt-2 pb-1" src={"/assets/img/solidity.webp"} style={{ flex: "1", height: "150px", maxWidth: "150px", paddingTop: 2, paddingBottom: 2, filter: themeFilter.filter }} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<h5>JUMP INTO WEB3</h5>
<p>The Remix Project is a rich toolset which can be used for the entire journey of contract development by users of any knowledge level, and as a learning lab for teaching and experimenting with Ethereum.</p>
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'jumpIntoWeb3'])} target="__blank" href="https://remix-project.org">More</a>
<h5><FormattedMessage id='home.solidityDevSurveyHeader' /></h5>
<p style={{ fontStyle: 'italic' }}><FormattedMessage id='home.solidityDevSurvey1' /></p>
<div style={{ fontSize: 'medium' }}>
<FormattedMessage id='home.solidityDevSurvey' />
</div>
<a className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3" target="__blank" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'soliditySurvey'])} href="https://blog.soliditylang.org/2022/12/07/solidity-developer-survey-2022-announcement/"><FormattedMessage id='home.surveyLink' /></a>
</div>
</div>
<div className="mx-1 px-1 d-flex">
<img src={"/assets/img/remixRewardUser.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 p-4" style={{ flex: "1" }}>
<h5>REMIX REWARDS</h5>
<p style={{ fontStyle: 'italic' }}>NFTs for our users!</p>
<p>
Remix Project rewards contributors, beta testers, and UX research participants with NFTs deployed on Optimism. Remix Reward holders are able to mint a second Remixer user NFT badge to give to any other user of their choice.
</p>
<a className="remixui_home_text" target="__blank" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'remixRewards'])} href="https://rewards.remix.ethereum.eth.limo">More</a>
<img src={"assets/img/bgRemi_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<h5><FormattedMessage id='home.jumpIntoWeb3' /></h5>
<div><FormattedMessage id='home.jumpIntoWeb3Text'/></div>
<a className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'jumpIntoWeb3'])} target="__blank" href="https://remix-project.org"><FormattedMessage id='home.more' /></a>
</div>
</div>
<div className="mx-1 px-1 d-flex">
<img src={"/assets/img/remixRewardBetaTester.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 p-4" style={{ flex: "1" }}>
<h5>BETA TESTING</h5>
<p style={{ fontStyle: 'italic' }}>Our community supports us.</p>
<p>You can join Beta Testing before each release of Remix IDE. Help us test now and get a handle on new features!</p>
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'betatesting'])} target="__blank" href="https://rewards.remix.ethereum.eth.limo">More</a>
<img src={"/assets/img/remixRewardUser_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<h5><FormattedMessage id='home.remixRewards' /></h5>
<p style={{ fontStyle: 'italic' }}><FormattedMessage id='home.remixRewardsText1' /></p>
<div><FormattedMessage id='home.remixRewardsText2' /></div>
<a className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3" target="__blank" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'remixRewards'])} href="https://rewards.remix.ethereum.eth.limo"><FormattedMessage id='home.more' /></a>
</div>
</div>
<div className="mx-1 px-1 d-flex">
<img src={"/assets/img/remixRewardBetaTester_small.webp"} style={{ flex: "1", height: "170px", maxWidth: "170px" }} alt="" ></img>
<div className="h6 w-50 p-4" style={{ flex: "1" }}>
<h5><FormattedMessage id='home.betaTesting' /></h5>
<p style={{ fontStyle: 'italic' }}><FormattedMessage id='home.betaTestingText1' /></p>
<div><FormattedMessage id='home.betaTestingText2' /></div>
<a className="remixui_home_text btn btn-secondary mt-2 text-decoration-none mb-3" onClick={() => _paq.push(['trackEvent', 'hometab', 'featuredSection', 'betatesting'])} target="__blank" href="https://docs.google.com/forms/d/e/1FAIpQLSd0WsJnKbeJo-BGrnf7WijxAdmE4PnC_Z4M0IApbBfHLHZdsQ/viewform"><FormattedMessage id='home.more' /></a>
</div>
</div>
</Carousel>

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useRef, useContext } from 'react'
import { FormattedMessage } from 'react-intl'
import { FormattedMessage, useIntl } from 'react-intl'
import PluginButton from './pluginButton'
import { ThemeContext } from '../themeContext'
import Carousel from 'react-multi-carousel'
@ -22,6 +22,7 @@ function HomeTabFeaturedPlugins ({plugin}: HomeTabFeaturedPluginsProps) {
const themeFilter = useContext(ThemeContext)
const carouselRef = useRef<any>({})
const carouselRefDiv = useRef(null)
const intl = useIntl()
useEffect(() => {
document.addEventListener("wheel", handleScroll)
@ -85,7 +86,7 @@ function HomeTabFeaturedPlugins ({plugin}: HomeTabFeaturedPluginsProps) {
return (
<div className="pl-2 w-100" id="hTFeaturedPlugins">
<label className="" style={{fontSize: "1.2rem"}}><FormattedMessage id='home.featuredPlugins' defaultMessage='Featured Plugins' /></label>
<label className="" style={{fontSize: "1.2rem"}}><FormattedMessage id='home.featuredPlugins' /></label>
<div ref={carouselRefDiv} className="w-100 d-flex flex-column">
<ThemeContext.Provider value={ themeFilter }>
<Carousel
@ -121,7 +122,7 @@ function HomeTabFeaturedPlugins ({plugin}: HomeTabFeaturedPluginsProps) {
imgPath="assets/img/solidityLogo.webp"
envID="solidityLogo"
envText="Solidity"
description="Compile, test and analyse smart contract."
description={intl.formatMessage({ id: 'home.solidityPluginDesc' })}
remixMaintained={true}
callback={() => startSolidity()}
/>
@ -129,28 +130,28 @@ function HomeTabFeaturedPlugins ({plugin}: HomeTabFeaturedPluginsProps) {
imgPath="assets/img/starkNetLogo.webp"
envID="starkNetLogo"
envText="StarkNet"
description="Compile and deploy contracts with Cairo, a native language for StarkNet."
description={intl.formatMessage({ id: 'home.starkNetPluginDesc' })}
l2={true}
callback={() => startStarkNet()}
/>
<PluginButton
imgPath="assets/img/solhintLogo.webp"
envID="solhintLogo" envText="Solhint linter"
description="Solhint is an open source project for linting Solidity code."
description={intl.formatMessage({ id: 'home.solhintPluginDesc' })}
callback={() => startSolhint()}
/>
<PluginButton
imgPath="assets/img/sourcifyNewLogo.webp"
envID="sourcifyLogo"
envText="Sourcify"
description="Solidity contract and metadata verification service."
description={intl.formatMessage({ id: 'home.sourcifyPluginDesc' })}
callback={() => startSourceVerify()}
/>
<PluginButton
imgPath="assets/img/unitTesting.webp"
envID="sUTLogo"
envText="Solidity unit testing"
description="Write and run unit tests for your contracts in Solidity."
description={intl.formatMessage({ id: 'home.unitTestPluginDesc' })}
callback={() => startSolidityUnitTesting()}
/>
</Carousel>

@ -154,16 +154,16 @@ function HomeTabFile ({plugin}: HomeTabFileProps) {
</ModalDialog>
<Toaster message={state.toasterMsg} />
<div className="justify-content-start mt-1 p-2 border-bottom d-flex flex-column" id="hTFileSection">
<label style={{fontSize: "1rem"}}><FormattedMessage id='home.file' defaultMessage='Files' /></label>
<button className="btn btn-primary p-2 border my-1" data-id="homeTabNewFile" style={{width: 'fit-content'}} onClick={() => createNewFile()}><FormattedMessage id='home.newFile' defaultMessage='New File' /></button>
<label className="btn p-2 border my-1" style={{width: 'fit-content'}} htmlFor="openFileInput"><FormattedMessage id='home.openFile' defaultMessage='Open File' /></label>
<label style={{fontSize: "1rem"}}><FormattedMessage id='home.files' /></label>
<button className="btn btn-primary p-2 border my-1" data-id="homeTabNewFile" style={{width: 'fit-content'}} onClick={() => createNewFile()}><FormattedMessage id='home.newFile' /></button>
<label className="btn p-2 border my-1" style={{width: 'fit-content'}} htmlFor="openFileInput"><FormattedMessage id='home.openFile' /></label>
<input title="open file" type="file" id="openFileInput" onChange={(event) => {
event.stopPropagation()
plugin.verticalIcons.select('filePanel')
uploadFile(event.target)
}} multiple />
<button className="btn p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}><FormattedMessage id='home.connectToLocalhost' defaultMessage='Connect to Localhost' /></button>
<label className="pt-2"><FormattedMessage id='home.loadFrom' defaultMessage='Load From' /></label>
<button className="btn p-2 border my-1" style={{width: 'fit-content'}} onClick={() => connectToLocalhost()}><FormattedMessage id='home.connectToLocalhost' /></button>
<label className="pt-2"><FormattedMessage id='home.loadFrom' /></label>
<div className="d-flex">
<button
className="btn p-2 border mr-2"

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useRef, useContext } from 'react'
import { useIntl, FormattedMessage } from 'react-intl'
import { ThemeContext} from '../themeContext'
import Carousel from 'react-multi-carousel'
import WorkspaceTemplate from './workspaceTemplate'
@ -19,6 +20,7 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
const themeFilter = useContext(ThemeContext)
const carouselRef = useRef<any>({})
const carouselRefDiv = useRef(null)
const intl = useIntl()
useEffect(() => {
document.addEventListener("wheel", handleScroll)
@ -37,7 +39,7 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
}
return false;
}
const handleScroll = (e) => {
if (isDescendant(carouselRefDiv.current, e.target)) {
e.stopPropagation()
@ -67,9 +69,9 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
<div className="pl-2" id="hTGetStartedSection">
<label style={{fontSize: "1.2rem"}}>
<span className="mr-2" style={{fontWeight: "bold"}}>
Get Started
<FormattedMessage id="home.getStarted" />
</span>
- Project Templates
- <FormattedMessage id="home.projectTemplates" />
</label>
<div ref={carouselRefDiv} className="w-100 d-flex flex-column">
<ThemeContext.Provider value={ themeFilter }>
@ -84,7 +86,7 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
draggable={true}
showDots={false}
responsive={
{
{
superLargeDesktop: {
breakpoint: { max: 4000, min: 3000 },
items: 5
@ -106,27 +108,27 @@ function HomeTabGetStarted ({plugin}: HomeTabGetStartedProps) {
<WorkspaceTemplate
gsID="starkNetLogo"
workspaceTitle="Blank"
description="Create an empty workspace."
description={intl.formatMessage({ id: 'home.blankTemplateDesc' })}
callback={() => createWorkspace("blank")} />
<WorkspaceTemplate
gsID="solhintLogo"
workspaceTitle="Remix Default"
description="Create a workspace with sample files."
description={intl.formatMessage({ id: 'home.remixDefaultTemplateDesc' })}
callback={() => createWorkspace("remixDefault")} />
<WorkspaceTemplate
gsID="sourcifyLogo"
workspaceTitle="OpenZeppelin ERC20"
description="Create an ERC20 token by importing OpenZeppelin library."
description={intl.formatMessage({ id: 'home.ozerc20TemplateDesc' })}
callback={() => createWorkspace("ozerc20")} />
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="OpenZeppelin ERC721"
description="Create an NFT token by importing OpenZeppelin library."
description={intl.formatMessage({ id: 'home.ozerc721TemplateDesc' })}
callback={() => createWorkspace("ozerc721")} />
<WorkspaceTemplate
gsID="sUTLogo"
workspaceTitle="0xProject ERC20"
description="Create an ERC20 token by importing 0xProject contract."
description={intl.formatMessage({ id: 'home.zeroxErc20TemplateDesc' })}
callback={() => createWorkspace("zeroxErc20")} />
</Carousel>
</ThemeContext.Provider>

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState, useContext } from 'react'
import { FormattedMessage } from 'react-intl'
import { ThemeContext } from '../themeContext'
declare global {
interface Window {
@ -40,7 +41,9 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
return (
<div className="d-flex px-2 pb-2 pt-2 d-flex flex-column" id="hTLearnSection">
<div className="d-flex justify-content-between">
<label className="py-2 align-self-center m-0" style={{fontSize: "1.2rem"}}>Learn</label>
<label className="py-2 align-self-center m-0" style={{fontSize: "1.2rem"}}>
<FormattedMessage id="home.learn" />
</label>
<button
onClick={ ()=> openLink()}
className="h-100 px-2 pt-0 btn"
@ -50,24 +53,40 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
</div>
<div className="d-flex flex-column">
<label className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Basics }})}>
<label className="card-title align-self-start m-0 float-left" style={{fontSize: "1rem"}}>Remix Basics</label>
<label className="card-title align-self-start m-0 float-left" style={{fontSize: "1rem"}}>
<FormattedMessage id="home.remixBasics" />
</label>
{(state.visibleTutorial === VisibleTutorial.Basics) && <div className="pt-2 d-flex flex-column text-left">
<span>Introduction to Remix's interface and concepts used in Ethereum, as well as the basics of Solidity.</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('basics')}>Get Started</button>
<span>
<FormattedMessage id="home.remixBasicsDesc" />
</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('basics')}>
<FormattedMessage id="home.getStarted" />
</button>
</div>}
</label>
<label className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Intermediate }})}>
<label className="card-title align-self-start m-0 float-left" style={{fontSize: "1rem"}}>Remix Intermediate</label>
<label className="card-title align-self-start m-0 float-left" style={{fontSize: "1rem"}}>
<FormattedMessage id="home.remixIntermediate" />
</label>
{(state.visibleTutorial === VisibleTutorial.Intermediate) && <div className="pt-2 d-flex flex-column text-left">
<span>Using the web3.js to interact with a contract. Using Recorder tool.</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('useofweb3js')}>Get Started</button>
<span>
<FormattedMessage id="home.remixIntermediateDesc" /></span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('useofweb3js')}>
<FormattedMessage id="home.getStarted" />
</button>
</div>}
</label>
<label className="d-flex flex-column btn border" onClick={() => setState((prevState) => {return { ...prevState, visibleTutorial: VisibleTutorial.Advanced }})}>
<label className="card-title align-self-start m-0 float-left" style={{fontSize: "1rem"}}>Remix Advanced</label>
<label className="card-title align-self-start m-0 float-left" style={{fontSize: "1rem"}}>
<FormattedMessage id="home.remixAdvanced" />
</label>
{(state.visibleTutorial === VisibleTutorial.Advanced) && <div className="pt-2 d-flex flex-column text-left">
<span>Learn the Proxy Pattern and working with Libraries in Remix. Learn to use the Debugger.</span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('deploylibraries')}>Get Started</button>
<span>
<FormattedMessage id="home.remixAdvancedDesc" /></span>
<button className="btn btn-sm btn-secondary mt-2" style={{width: 'fit-content'}} onClick={() => startLearnEthTutorial('deploylibraries')}>
<FormattedMessage id="home.getStarted" />
</button>
</div>}
</label>
</div>
@ -75,4 +94,4 @@ function HomeTabLearn ({plugin}: HomeTabLearnProps) {
)
}
export default HomeTabLearn
export default HomeTabLearn

@ -7,26 +7,26 @@ const _paq = window._paq = window._paq || [] // eslint-disable-line
function HomeTabScamAlert () {
return (
<div className="" id="hTScamAlertSection">
<label className="pl-2 text-danger" style={{fontSize: "1.2rem"}}><FormattedMessage id='home.scamAlert' defaultMessage='Scam Alerts' /></label>
<label className="pl-2 text-danger" style={{fontSize: "1.2rem"}}><FormattedMessage id='home.scamAlert' /></label>
<div className="py-2 ml-2 mb-1 align-self-end mb-2 d-flex flex-column border border-danger">
<span className="pl-4 mt-2">
<i className="pr-2 text-danger fas fa-exclamation-triangle"></i>
<b><FormattedMessage id='home.scamAlert' defaultMessage='Scam Alerts' />:</b>
<b><FormattedMessage id='home.scamAlert' />:</b>
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText' defaultMessage='The only URL Remix uses is remix.ethereum.org' />
<FormattedMessage id='home.scamAlertText' />
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText2' defaultMessage='Beware of online videos promoting "liquidity front runner bots"' />:
<FormattedMessage id='home.scamAlertText2' />:
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'scamAlert', 'learnMore'])} target="__blank" href="https://medium.com/remix-ide/remix-in-youtube-crypto-scams-71c338da32d">
<FormattedMessage id='home.learnMore' defaultMessage='Learn more' />
<FormattedMessage id='home.learnMore' />
</a>
</span>
<span className="pl-4 mt-1">
<FormattedMessage id='home.scamAlertText3' defaultMessage='Additional safety tips' />
<FormattedMessage id='home.scamAlertText3' />
: &nbsp;
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'scamAlert', 'safetyTips'])} target="__blank" href="https://remix-ide.readthedocs.io/en/latest/security.html">
<FormattedMessage id='home.here' defaultMessage='here' />
<FormattedMessage id='home.here' />
</a>
</span>
</div>

@ -3,6 +3,7 @@
import BasicLogo from 'libs/remix-ui/vertical-icons-panel/src/lib/components/BasicLogo'
import { ThemeContext } from '../themeContext'
import React, { useEffect, useState, useRef, useContext } from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper'
const _paq = window._paq = window._paq || [] // eslint-disable-line
@ -23,6 +24,7 @@ function HomeTabTitle() {
const themeFilter = useContext(ThemeContext)
const searchInputRef = useRef(null)
const remiAudioEl = useRef(null)
const intl = useIntl()
const playRemi = async () => {
remiAudioEl.current.play()
@ -57,7 +59,7 @@ function HomeTabTitle() {
<audio
id="remiAudio"
muted={false}
src="assets/audio/remiGuitar-single-power-chord-A-minor.wav"
src="assets/audio/remiGuitar-single-power-chord-A-minor.mp3"
ref={remiAudioEl}
></audio>
</div>
@ -68,7 +70,7 @@ function HomeTabTitle() {
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText="Remix Youtube Playlist"
tooltipText={<FormattedMessage id="home.remixYoutubePlaylist" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
@ -84,11 +86,11 @@ function HomeTabTitle() {
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText="Remix Twitter Profile"
tooltipText={<FormattedMessage id="home.remixTwitterProfile" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
onClick={() => {
openLink("https://twitter.com/EthereumRemix")
_paq.push(['trackEvent', 'hometab', 'socialMedia', 'twitter'])
}}
@ -100,11 +102,11 @@ function HomeTabTitle() {
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText="Remix Linkedin Profile"
tooltipText={<FormattedMessage id="home.remixLinkedinProfile" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
onClick={() => {
onClick={() => {
openLink("https://www.linkedin.com/company/ethereum-remix/")
_paq.push(['trackEvent', 'hometab', 'socialmedia', 'linkedin'])
}}
@ -116,7 +118,7 @@ function HomeTabTitle() {
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText="Remix Medium Posts"
tooltipText={<FormattedMessage id="home.remixMediumPosts" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
@ -132,7 +134,7 @@ function HomeTabTitle() {
placement={'top'}
tooltipId="overlay-tooltip"
tooltipClasses="text-nowrap"
tooltipText="Remix Gitter Channel"
tooltipText={<FormattedMessage id="home.remixGitterChannel" />}
tooltipTextClasses="border bg-light text-dark p-1 pr-3"
>
<button
@ -145,12 +147,22 @@ function HomeTabTitle() {
</CustomTooltip>
</span>
</div>
<b className="pb-1 text-dark" style={{ fontStyle: 'italic' }}>The Native IDE for Web3 Development.</b>
<b className="pb-1 text-dark" style={{ fontStyle: 'italic' }}>
<FormattedMessage id="home.nativeIDE" />
</b>
<div className="pb-1" id="hTGeneralLinks">
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'webSite'])} target="__blank" href="https://remix-project.org">Website</a>
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'documentation'])} target="__blank" href="https://remix-ide.readthedocs.io/en/latest">Documentation</a>
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'remixPlugin'])} target="__blank" href="https://remix-plugin-docs.readthedocs.io/en/latest/">Remix Plugin</a>
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'remixDesktop'])} target="__blank" href="https://github.com/ethereum/remix-desktop/releases">Remix Desktop</a>
<a className="remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'webSite'])} target="__blank" href="https://remix-project.org">
<FormattedMessage id="home.website" />
</a>
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'documentation'])} target="__blank" href="https://remix-ide.readthedocs.io/en/latest">
<FormattedMessage id="home.documentation" />
</a>
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'remixPlugin'])} target="__blank" href="https://remix-plugin-docs.readthedocs.io/en/latest/">
<FormattedMessage id="home.remixPlugin" />
</a>
<a className="pl-2 remixui_home_text" onClick={() => _paq.push(['trackEvent', 'hometab', 'header', 'remixDesktop'])} target="__blank" href="https://github.com/ethereum/remix-desktop/releases">
<FormattedMessage id="home.remixDesktop" />
</a>
</div>
<div className="d-flex pb-1 align-items-center">
<input
@ -158,7 +170,7 @@ function HomeTabTitle() {
type="text"
className="border form-control border-right-0"
id="homeTabSearchInput"
placeholder="Search Documentation"
placeholder={intl.formatMessage({ id: "home.searchDocumentation" })}
data-id="terminalInputSearch"
/>
<button

@ -105,3 +105,7 @@
top: 120px;
right: 180px;
}
.remixui_home_carouselText {
font-size: rfs-fluid-value(1rem);
}

@ -8,16 +8,18 @@ export interface RemixUiLocaleModuleProps {
}
export function RemixUiLocaleModule({ localeModule }: RemixUiLocaleModuleProps) {
const [localeName, setLocaleName] = useState('')
const [localeCode, setLocaleCode] = useState('')
useEffect(() => {
localeModule.switchLocale()
}, [localeName, localeModule])
}, [localeCode, localeModule])
return (
<div className="border-top">
<div className="border-top mb-4">
<div className="card-body pt-3 pb-2">
<h6 className="card-title"><FormattedMessage id='settings.locales' defaultMessage='Lanaguage' /></h6>
<h6 className="card-title">
<FormattedMessage id='settings.locales' />
</h6>
<div className="card-text locales-container">
{localeModule.getLocales()
? localeModule.getLocales().map((locale, idx) => (
@ -28,21 +30,21 @@ export function RemixUiLocaleModule({ localeModule }: RemixUiLocaleModuleProps)
<input
type="radio"
onChange={event => {
localeModule.switchLocale(locale.name);
setLocaleName(locale.name);
localeModule.switchLocale(locale.code);
setLocaleCode(locale.code);
}}
className="align-middle custom-control-input"
name="locale"
id={locale.name}
data-id={`settingsTabLocale${locale.name}`}
checked={localeModule.active === locale.name.toLocaleLowerCase()}
id={locale.code}
data-id={`settingsTabLocale${locale.code}`}
checked={localeModule.active === locale.code.toLocaleLowerCase()}
/>
<label
className="form-check-label custom-control-label"
data-id={`settingsTabLocaleLabel${locale.name}`}
htmlFor={locale.name}
data-id={`settingsTabLocaleLabel${locale.code}`}
htmlFor={locale.code}
>
{locale.name}
{locale.name.toLocaleUpperCase()}-{locale.localeName}
</label>
</div>
))

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

Loading…
Cancel
Save