fix conflicts

pull/5370/head
filip mertens 4 years ago
commit 9038920746
  1. 3
      .circleci/config.yml
  2. 3
      apps/remix-ide-e2e/src/commands/getLastTransactionHash.ts
  3. 29
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  4. 3
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.spec.ts
  5. 4
      apps/remix-ide-e2e/src/tests/fileManager_api.spec.ts
  6. 3
      apps/remix-ide-e2e/src/tests/runAndDeploy.ts
  7. 2
      apps/remix-ide-e2e/src/tests/solidityImport.spec.ts
  8. 33
      apps/remix-ide-e2e/src/tests/transactionExecution.spec.ts
  9. 2
      apps/remix-ide/ci/makeMockCompiler.js
  10. 9
      apps/remix-ide/src/app.js
  11. 9
      apps/remix-ide/src/app/components/plugin-manager-component.js
  12. 399
      apps/remix-ide/src/app/files/dgitProvider.js
  13. 19
      apps/remix-ide/src/app/files/fileManager.js
  14. 33
      apps/remix-ide/src/app/files/remixd-handle.js
  15. 22
      apps/remix-ide/src/app/panels/file-panel.js
  16. 26
      apps/remix-ide/src/app/tabs/compileTab/compileTab.js
  17. 4
      apps/remix-ide/src/app/tabs/compileTab/compilerContainer.js
  18. 71
      apps/remix-ide/src/app/tabs/hardhat-provider.js
  19. 19
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  20. 2
      apps/remix-ide/src/app/udapp/run-tab.js
  21. 59
      apps/remix-ide/src/app/ui/confirmDialog.js
  22. 2
      apps/remix-ide/src/app/ui/universal-dapp-ui.js
  23. 57
      apps/remix-ide/src/blockchain/blockchain.js
  24. 7
      apps/remix-ide/src/blockchain/execution-context.js
  25. 14
      apps/remix-ide/src/lib/helper.js
  26. 6
      apps/remix-ide/src/remixAppManager.js
  27. 6
      libs/remix-debug/src/solidity-decoder/types/Mapping.ts
  28. 48
      libs/remix-lib/src/execution/txExecution.ts
  29. 4
      libs/remix-lib/src/execution/txFormat.ts
  30. 5
      libs/remix-lib/src/execution/txHelper.ts
  31. 6
      libs/remix-lib/src/execution/txListener.ts
  32. 4
      libs/remix-lib/src/util.ts
  33. 1
      libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts
  34. 10
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  35. 54
      libs/remix-ui/file-explorer/src/lib/reducers/fileSystem.ts
  36. 30
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  37. 8
      libs/remix-url-resolver/tests/test.ts
  38. 16
      libs/remixd/src/bin/remixd.ts
  39. 9
      libs/remixd/src/services/hardhatClient.ts
  40. 8
      libs/remixd/src/websocket.ts
  41. 794
      package-lock.json
  42. 20
      package.json

@ -239,6 +239,7 @@ jobs:
- checkout
- run: npm install
- run: npx nx build remix-ide --with-deps
- run: npm run downloadsolc_assets
- run:
name: Deploy
command: |
@ -291,6 +292,7 @@ jobs:
- checkout
- run: npm install
- run: npx nx build remix-ide --with-deps
- run: npm run downloadsolc_assets
- run:
name: Deploy
command: |
@ -319,6 +321,7 @@ jobs:
- run: npm install
- run: npm run build:libs
- run: npm run build
- run: npm run downloadsolc_assets
- run:
name: Deploy
command: |

@ -21,7 +21,8 @@ function getLastTransactionHash (browser: NightwatchBrowser, callback: (hash: st
for (let i = deployedContracts.length - 1; i >= 0; i--) {
const current = deployedContracts[i]
const attr = current.getAttribute('data-id')
if (attr && attr.replace('block_tx', '').startsWith('0x')) {
// For web3 provider, a contract call simulates a tx hash starting with 'block_txcall'
if (attr && (attr.replace('block_tx', '').startsWith('0x') || attr.replace('block_txcall', '').startsWith('0x'))) {
return attr.replace('block_tx', '')
}
}

@ -34,6 +34,19 @@ module.exports = {
})
},
'Call method from Ballot to check return value': function (browser: NightwatchBrowser) {
browser
.clickFunction('winnerName - call')
// Test in terminal
.testFunction('last',
{
to: 'Ballot.winnerName() 0x692a70D2e424a56D2C6C27aA97D1a86395877b3A',
'decoded output': { 0: 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000' }
})
// Test in Udapp UI , treeViewDiv0 shows returned value on method click
.assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000')
},
'Debug Ballot / delegate': function (browser: NightwatchBrowser) {
browser.pause(500)
.click('*[data-id="txLoggerDebugButton0x41fab8ea5b1d9fba5e0a6545ca1a2d62fff518578802c033c2b9a031a01c31b3"]')
@ -51,7 +64,8 @@ module.exports = {
browser.clickLaunchIcon('udapp')
.click('*[data-id="universalDappUiUdappClose"]')
.addFile('ballot.abi', { content: ballotABI })
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
// we are not changing the visibility for not checksumed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
.pause(500)
@ -79,6 +93,19 @@ module.exports = {
.clickFunction('delegate - transact (not payable)', { types: 'address to', values: '0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c' })
.journalLastChildIncludes('Ballot.delegate(address)')
.journalLastChildIncludes('data: 0x5c1...a733c')
},
'Call method from Ballot to check return value using external web3': function (browser: NightwatchBrowser) {
browser
.clickFunction('winnerName - call')
// Test in terminal
.journalLastChildIncludes('Ballot.winnerName()')
.testFunction('last',
{
'decoded output': { 0: 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000' }
})
// Test in Udapp UI , treeViewDiv0 shows returned value on method click
.assert.containsText('*[data-id="treeViewDiv0"]', 'bytes32: winnerName_ 0x48656c6c6f20576f726c64210000000000000000000000000000000000000000')
.end()
}
}

@ -60,7 +60,8 @@ module.exports = {
browser.clickLaunchIcon('udapp')
.click('*[data-id="universalDappUiUdappClose"]')
.addFile('ballot.abi', { content: ballotABI })
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
// we are not changing the visibility for not checksumed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
.pause(500)

@ -147,8 +147,8 @@ const executeReadFile = `
const executeCopyFile = `
const run = async () => {
await remix.call('fileManager', 'copyFile', 'contracts/3_Ballot.sol', '/', 'new_contract.sol')
const result = await remix.call('fileManager', 'readFile', 'new_contract.sol')
await remix.call('fileManager', 'copyFile', 'contracts/3_Ballot.sol', '/', 'copy_contract.sol')
const result = await remix.call('fileManager', 'readFile', 'copy_contract.sol')
console.log(result)
}

@ -69,6 +69,9 @@ module.exports = {
.testFunction('last', {
status: 'true Transaction mined and execution succeed'
})
// When this is removed and tests are running by connecting to metamask
// Consider adding tests to check return value of contract call
// See: https://github.com/ethereum/remix-project/pull/1229
.end()
},

@ -80,7 +80,7 @@ module.exports = {
'Test NPM Import (with unpkg.com)': function (browser: NightwatchBrowser) {
browser
.setSolidityCompilerVersion('soljson-v0.8.1+commit.df193b15.js')
.setSolidityCompilerVersion('soljson-v0.8.4+commit.c7e474f2.js')
.clickLaunchIcon('filePanel')
.click('li[data-id="treeViewLitreeViewItemREADME.txt"')
.addFile('Untitled9.sol', sources[8]['Untitled9.sol'])

@ -137,6 +137,21 @@ module.exports = {
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]')
.waitForElementPresent('.instance:nth-of-type(2)')
},
'Should Compile and Deploy a contract which define a custom error, the error should be logged in the terminal': function (browser: NightwatchBrowser) {
browser.testContracts('customError.sol', sources[4]['customError.sol'], ['C'])
.clickLaunchIcon('udapp')
.selectAccount('0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c') // this account will be used for this test suite
.click('#runTabView button[class^="instanceButton"]')
.waitForElementPresent('.instance:nth-of-type(3)')
.click('.instance:nth-of-type(3) > div > button')
.clickFunction('g - transact (not payable)')
.journalLastChildIncludes('Error provided by the contract:')
.journalLastChildIncludes('CustomError')
.journalLastChildIncludes('Parameters:')
.journalLastChildIncludes('2,3,error_string_2')
.journalLastChildIncludes('Debug the transaction to get more information.')
.end()
}
}
@ -218,5 +233,23 @@ contract C {
event Test(function() external);
}`
}
},
// https://github.com/ethereum/remix-project/issues/1152
{
'customError.sol': {
content: `// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
error CustomError(uint a, uint b, string c);
contract C {
function f() public pure {
revert CustomError(2, 3, "error_string");
}
function g() public {
revert CustomError(2, 3, "error_string_2");
}
}`
}
}
]

@ -3,7 +3,7 @@
var fs = require('fs')
var compiler = require('solc')
var compilerInput = require('@remix-project/remix-solidity').CompilerInput
var defaultVersion = 'soljson-v0.8.1+commit.df193b15.js'
var defaultVersion = 'soljson-v0.8.4+commit.c7e474f2.js'
const path = require('path')
compiler.loadRemoteVersion(defaultVersion, (error, solcSnapshot) => {

@ -29,11 +29,13 @@ const { OffsetToLineColumnConverter } = require('./lib/offsetToLineColumnConvert
const QueryParams = require('./lib/query-params')
const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider')
const HardhatProvider = require('./app/tabs/hardhat-provider')
const Config = require('./config')
const modalDialogCustom = require('./app/ui/modal-dialog-custom')
const modalDialog = require('./app/ui/modaldialog')
const FileManager = require('./app/files/fileManager')
const FileProvider = require('./app/files/fileProvider')
const DGitProvider = require('./app/files/dgitProvider')
const WorkspaceFileProvider = require('./app/files/workspaceFileProvider')
const toolTip = require('./app/ui/tooltip')
const CompilerMetadata = require('./app/files/compiler-metadata')
@ -256,6 +258,8 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
// ----------------- fileManager service ----------------------------
const fileManager = new FileManager(editor, appManager)
registry.put({ api: fileManager, name: 'filemanager' })
// ----------------- dGit provider ---------------------------------
const dGitProvider = new DGitProvider()
// ----------------- import content service ------------------------
const contentImport = new CompilerImport(fileManager)
@ -274,6 +278,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
const networkModule = new NetworkModule(blockchain)
// ----------------- represent the current selected web3 provider ----
const web3Provider = new Web3ProviderModule(blockchain)
const hardhatProvider = new HardhatProvider(blockchain)
// ----------------- convert offset to line/column service -----------
const offsetToLineColumnConverter = new OffsetToLineColumnConverter()
registry.put({ api: offsetToLineColumnConverter, name: 'offsettolinecolumnconverter' })
@ -309,7 +314,9 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
contextualListener,
terminal,
web3Provider,
fetchAndCompile
fetchAndCompile,
dGitProvider,
hardhatProvider
])
// LAYOUT & SYSTEM VIEWS

@ -114,6 +114,8 @@ class PluginManagerComponent extends ViewPlugin {
renderItem (profile) {
const displayName = (profile.displayName) ? profile.displayName : profile.name
const doclink = profile.documentation ? yo`<a href="${profile.documentation}" class="px-1" title="link to documentation" target="_blank"><i aria-hidden="true" class="fas fa-book"></i></a>`
: yo``
// Check version of the plugin
let versionWarning
@ -147,8 +149,11 @@ class PluginManagerComponent extends ViewPlugin {
<article id="remixPluginManagerListItem_${profile.name}" class="list-group-item py-1 mb-1 plugins-list-group-item" title="${displayName}" >
<div class="${css.row} justify-content-between align-items-center mb-2">
<h6 class="${css.displayName} plugin-name">
${displayName}
${versionWarning}
<div>
${displayName}
${doclink}
${versionWarning}
</div>
${activationButton}
</h6>
</div>

@ -0,0 +1,399 @@
'use strict'
import {
Plugin
} from '@remixproject/engine'
import git from 'isomorphic-git'
import IpfsHttpClient from 'ipfs-http-client'
import {
saveAs
} from 'file-saver'
const JSZip = require('jszip')
const path = require('path')
const FormData = require('form-data')
const axios = require('axios')
const profile = {
name: 'dGitProvider',
displayName: 'Decentralized git',
description: '',
icon: 'assets/img/fileManager.webp',
version: '0.0.1',
methods: ['init', 'status', 'log', 'commit', 'add', 'remove', 'rm', 'lsfiles', 'readblob', 'resolveref', 'branches', 'branch', 'checkout', 'currentbranch', 'push', 'pin', 'pull', 'pinList', 'unPin', 'setIpfsConfig', 'zip', 'setItem', 'getItem'],
kind: 'file-system'
}
class DGitProvider extends Plugin {
constructor () {
super(profile)
this.ipfsconfig = {
host: 'ipfs.komputing.org',
port: 443,
protocol: 'https',
ipfsurl: 'https://ipfsgw.komputing.org/ipfs/'
}
this.globalIPFSConfig = {
host: 'ipfs.io',
port: 443,
protocol: 'https',
ipfsurl: 'https://ipfs.io/ipfs/'
}
}
async getGitConfig () {
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
return {
fs: window.remixFileSystem,
dir: workspace.absolutePath
}
}
async init () {
await git.init({
...await this.getGitConfig(),
defaultBranch: 'main'
})
}
async status (cmd) {
const status = await git.statusMatrix({
...await this.getGitConfig(),
...cmd
})
return status
}
async add (cmd) {
await git.add({
...await this.getGitConfig(),
...cmd
})
this.call('fileManager', 'refresh')
}
async rm (cmd) {
await git.remove({
...await this.getGitConfig(),
...cmd
})
this.call('fileManager', 'refresh')
}
async checkout (cmd) {
await git.checkout({
...await this.getGitConfig(),
...cmd
})
this.call('fileManager', 'refresh')
}
async log (cmd) {
const status = await git.log({
...await this.getGitConfig(),
...cmd
})
return status
}
async branch (cmd) {
const status = await git.branch({
...await this.getGitConfig(),
...cmd
})
this.call('fileManager', 'refresh')
return status
}
async currentbranch () {
const name = await git.currentBranch({
...await this.getGitConfig()
})
return name
}
async branches () {
const branches = await git.listBranches({
...await this.getGitConfig()
})
return branches
}
async commit (cmd) {
await this.init()
try {
const sha = await git.commit({
...await this.getGitConfig(),
...cmd
})
return sha
} catch (e) {}
}
async lsfiles (cmd) {
const filesInStaging = await git.listFiles({
...await this.getGitConfig(),
...cmd
})
return filesInStaging
}
async resolveref (cmd) {
const oid = await git.resolveRef({
...await this.getGitConfig(),
...cmd
})
return oid
}
async readblob (cmd) {
const readBlobResult = await git.readBlob({
...await this.getGitConfig(),
...cmd
})
return readBlobResult
}
async setIpfsConfig (config) {
this.ipfsconfig = config
return new Promise((resolve, reject) => {
resolve(this.checkIpfsConfig())
})
}
async checkIpfsConfig (config) {
this.ipfs = IpfsHttpClient(config || this.ipfsconfig)
try {
await this.ipfs.config.getAll()
return true
} catch (e) {
return false
}
}
async push () {
if (!this.checkIpfsConfig()) return false
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const files = await this.getDirectory('/')
this.filesToSend = []
for (const file of files) {
const c = window.remixFileSystem.readFileSync(`${workspace.absolutePath}/${file}`)
const ob = {
path: file,
content: c
}
this.filesToSend.push(ob)
}
const addOptions = {
wrapWithDirectory: true
}
const r = await this.ipfs.add(this.filesToSend, addOptions)
return r.cid.string
}
async pin (pinataApiKey, pinataSecretApiKey) {
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const files = await this.getDirectory('/')
this.filesToSend = []
const data = new FormData()
files.forEach(async (file) => {
const c = window.remixFileSystem.readFileSync(`${workspace.absolutePath}/${file}`)
data.append('file', new Blob([c]), `base/${file}`)
})
// get last commit data
let ob
try {
const commits = await this.log({ ref: 'HEAD' })
ob = {
ref: commits[0].oid,
message: commits[0].commit.message
}
} catch (e) {
ob = {
ref: 'no commits',
message: 'no commits'
}
}
const today = new Date()
const metadata = JSON.stringify({
name: `remix - ${workspace.name} - ${today.toLocaleString()}`,
keyvalues: ob
})
const pinataOptions = JSON.stringify({
wrapWithDirectory: false
})
data.append('pinataOptions', pinataOptions)
data.append('pinataMetadata', metadata)
const url = 'https://api.pinata.cloud/pinning/pinFileToIPFS'
try {
const result = await axios
.post(url, data, {
maxBodyLength: 'Infinity',
headers: {
'Content-Type': `multipart/form-data; boundary=${data._boundary}`,
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
})
return result.data.IpfsHash
} catch (error) {
throw new Error(error)
}
}
async pinList (pinataApiKey, pinataSecretApiKey) {
const url = 'https://api.pinata.cloud/data/pinList?status=pinned'
try {
const result = await axios
.get(url, {
maxBodyLength: 'Infinity',
headers: {
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
})
return result.data
} catch (error) {
throw new Error(error)
}
}
async unPin (pinataApiKey, pinataSecretApiKey, hashToUnpin) {
const url = `https://api.pinata.cloud/pinning/unpin/${hashToUnpin}`
try {
await axios
.delete(url, {
headers: {
pinata_api_key: pinataApiKey,
pinata_secret_api_key: pinataSecretApiKey
}
})
return true
} catch (error) {
throw new Error(error)
}
};
async pull (cmd) {
const permission = await this.askUserPermission('pull', 'Import multiple files into your workspaces.')
console.log(this.ipfsconfig)
if (!permission) return false
const cid = cmd.cid
if (!cmd.local) {
this.ipfs = IpfsHttpClient(this.globalIPFSConfig)
} else {
if (!this.checkIpfsConfig()) return false
}
await this.call('filePanel', 'createWorkspace', `workspace_${Date.now()}`, false)
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
for await (const file of this.ipfs.get(cid)) {
file.path = file.path.replace(cid, '')
if (!file.content) {
continue
}
const content = []
for await (const chunk of file.content) {
content.push(chunk)
}
const dir = path.dirname(file.path)
try {
this.createDirectories(`${workspace.absolutePath}/${dir}`)
} catch (e) {}
try {
window.remixFileSystem.writeFileSync(`${workspace.absolutePath}/${file.path}`, Buffer.concat(content) || new Uint8Array())
} catch (e) {}
}
this.call('fileManager', 'refresh')
}
async getItem (name) {
if (typeof window !== 'undefined') {
return window.localStorage.getItem(name)
}
}
async setItem (name, content) {
try {
if (typeof window !== 'undefined') {
window.localStorage.setItem(name, content)
}
} catch (exception) {
return false
}
return true
}
async zip () {
const zip = new JSZip()
const workspace = await this.call('filePanel', 'getCurrentWorkspace')
const files = await this.getDirectory('/')
this.filesToSend = []
for (const file of files) {
const c = window.remixFileSystem.readFileSync(`${workspace.absolutePath}/${file}`)
zip.file(file, c)
}
await zip.generateAsync({
type: 'blob'
})
.then(function (content) {
saveAs(content, `${workspace.name}.zip`)
})
}
async createDirectories (strdirectories) {
const ignore = ['.', '/.', '']
if (ignore.indexOf(strdirectories) > -1) return false
const directories = strdirectories.split('/')
for (let i = 0; i < directories.length; i++) {
let previouspath = ''
if (i > 0) previouspath = '/' + directories.slice(0, i).join('/')
const finalPath = previouspath + '/' + directories[i]
try {
window.remixFileSystem.mkdirSync(finalPath)
} catch (e) {}
}
}
async getDirectory (dir) {
let result = []
const files = await this.call('fileManager', 'readdir', dir)
const fileArray = normalize(files)
for (const fi of fileArray) {
if (fi) {
const type = fi.data.isDirectory
if (type === true) {
result = [
...result,
...(await this.getDirectory(
`${fi.filename}`
))
]
} else {
result = [...result, fi.filename]
}
}
}
return result
}
}
const normalize = (filesList) => {
const folders = []
const files = []
Object.keys(filesList || {}).forEach(key => {
if (filesList[key].isDirectory) {
folders.push({
filename: key,
data: filesList[key]
})
} else {
files.push({
filename: key,
data: filesList[key]
})
}
})
return [...folders, ...files]
}
module.exports = DGitProvider

@ -22,7 +22,7 @@ const profile = {
icon: 'assets/img/fileManager.webp',
permission: true,
version: packageJson.version,
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile'],
methods: ['file', 'exists', 'open', 'writeFile', 'readFile', 'copyFile', 'copyDir', 'rename', 'mkdir', 'readdir', 'remove', 'getCurrentFile', 'getFile', 'getFolder', 'setFile', 'switchFile', 'refresh'],
kind: 'file-system'
}
const errorMsg = {
@ -129,6 +129,15 @@ class FileManager extends Plugin {
}
}
/*
* refresh the file explorer
*/
refresh () {
const provider = this.fileProviderOf('/')
// emit folderAdded so that File Explorer reloads the file tree
provider.event.emit('folderAdded', '/')
}
/**
* Verify if the path provided is a file
* @param {string} path path of the directory or file
@ -222,9 +231,10 @@ class FileManager extends Plugin {
await this._handleExists(dest, `Cannot paste content into ${dest}. Path does not exist.`)
await this._handleIsDir(dest, `Cannot paste content into ${dest}. Path is not directory.`)
const content = await this.readFile(src)
const copiedFileName = customName ? '/' + customName : '/' + `Copy_${helper.extractNameFromKey(src)}`
let copiedFilePath = dest + (customName ? '/' + customName : '/' + `Copy_${helper.extractNameFromKey(src)}`)
copiedFilePath = await helper.createNonClashingNameAsync(copiedFilePath, this)
await this.writeFile(dest + copiedFileName, content)
await this.writeFile(copiedFilePath, content)
} catch (e) {
throw new Error(e)
}
@ -252,7 +262,8 @@ class FileManager extends Plugin {
async inDepthCopy (src, dest, count = 0) {
const content = await this.readdir(src)
const copiedFolderPath = count === 0 ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src)
let copiedFolderPath = count === 0 ? dest + '/' + `Copy_${helper.extractNameFromKey(src)}` : dest + '/' + helper.extractNameFromKey(src)
copiedFolderPath = await helper.createNonClashingDirNameAsync(copiedFolderPath, this)
await this.mkdir(copiedFolderPath)

@ -4,6 +4,7 @@ import * as packageJson from '../../../../../package.json'
var yo = require('yo-yo')
var modalDialog = require('../ui/modaldialog')
var modalDialogCustom = require('../ui/modal-dialog-custom')
var copyToClipboard = require('../ui/copy-to-clipboard')
var csjs = require('csjs-inject')
@ -30,17 +31,17 @@ const profile = {
}
export class RemixdHandle extends WebsocketPlugin {
constructor (locahostProvider, appManager) {
constructor (localhostProvider, appManager) {
super(profile)
this.locahostProvider = locahostProvider
this.localhostProvider = localhostProvider
this.appManager = appManager
}
deactivate () {
if (super.socket) super.deactivate()
// this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
this.appManager.deactivatePlugin('hardhat')
this.locahostProvider.close((error) => {
if (this.appManager.actives.includes('hardhat')) this.appManager.deactivatePlugin('hardhat')
this.localhostProvider.close((error) => {
if (error) console.log(error)
})
}
@ -52,7 +53,6 @@ export class RemixdHandle extends WebsocketPlugin {
async canceled () {
// await this.appManager.deactivatePlugin('git') // plugin call doesn't work.. see issue https://github.com/ethereum/remix-plugin/issues/342
await this.appManager.deactivatePlugin('remixd')
await this.appManager.deactivatePlugin('hardhat')
}
/**
@ -82,11 +82,11 @@ export class RemixdHandle extends WebsocketPlugin {
this.canceled()
}
}, 3000)
this.locahostProvider.init(() => {})
this.localhostProvider.init(() => {})
this.call('manager', 'activatePlugin', 'hardhat')
}
}
if (this.locahostProvider.isConnected()) {
if (this.localhostProvider.isConnected()) {
this.deactivate()
} else if (!isElectron()) {
// warn the user only if he/she is in the browser context
@ -97,7 +97,7 @@ export class RemixdHandle extends WebsocketPlugin {
label: 'Connect',
fn: () => {
try {
this.locahostProvider.preInit()
this.localhostProvider.preInit()
super.activate()
setTimeout(() => {
if (!this.socket || (this.socket && this.socket.readyState === 3)) { // 3 means connection closed
@ -130,22 +130,27 @@ export class RemixdHandle extends WebsocketPlugin {
}
function remixdDialog () {
const commandText = 'remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance'
return yo`
<div class=${css.dialog}>
<div class=${css.dialogParagraph}>Interact with your file system from Remix. <br>See the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a> for more info.
<div class=${css.dialogParagraph}>
Access your file system from Remix IDE. Remixd the NPM module needs to be running in the background to use the Remixd plugin. For more info please check the <a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html">Remixd tutorial</a>.
</div>
<div class=${css.dialogParagraph}>If you are just looking for the remixd command here it is:
<br><br><b>remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance</b>
<span class="">${copyToClipboard(() => commandText)}</span>
</div>
<div class=${css.dialogParagraph}>If you have looked at the Remixd docs and just need remixd command, <br> here it is:
<br><b>remixd -s absolute-path-to-the-shared-folder --remix-ide your-remix-ide-URL-instance</b>
<div class=${css.dialogParagraph}>A connection will start a session between <em>${window.location.origin}</em> and your local file system <i>ws://127.0.0.1:65520</i>
<br>To see that a connection has been made, check that there is a localhost section in the Files Explorer
</div>
<div class=${css.dialogParagraph}>Connection will start a session between <em>${window.location.origin}</em> and your local file system <i>ws://127.0.0.1:65520</i>
so please make sure your system is secured enough (port 65520 neither opened nor forwarded).
<div class=${css.dialogParagraph}>Please make sure your system is secured enough (port 65520 should not be opened nor forwarded).
This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.
</div>
<div class=${css.dialogParagraph}>
<h6 class="text-danger">
Before using, make sure you have the <b>latest remixd version</b>.<br><a target="_blank" href="https://remix-ide.readthedocs.io/en/latest/remixd.html#update-to-the-latest-remixd">Read here how to update it</a>
</h6>
</div>
<div class=${css.dialogParagraph}>This feature is still in Alpha, so we recommend you to keep a copy of the shared folder.</div>
</div>
`
}

@ -35,7 +35,7 @@ const profile = {
name: 'filePanel',
displayName: 'File explorers',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'registerContextMenuItem'],
events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace'],
events: ['setWorkspace', 'renameWorkspace', 'deleteWorkspace', 'createWorkspace'],
icon: 'assets/img/fileManager.webp',
description: ' - ',
kind: 'fileexplorer',
@ -215,7 +215,7 @@ module.exports = class Filepanel extends ViewPlugin {
return browserProvider.exists(workspacePath)
}
async createWorkspace (workspaceName) {
async createWorkspace (workspaceName, setDefaults = true) {
if (!workspaceName) throw new Error('name cannot be empty')
if (checkSpecialChars(workspaceName) || checkSlash(workspaceName)) throw new Error('special characters are not allowed')
if (await this.workspaceExists(workspaceName)) throw new Error('workspace already exists')
@ -224,14 +224,16 @@ module.exports = class Filepanel extends ViewPlugin {
await this.processCreateWorkspace(workspaceName)
workspaceProvider.setWorkspace(workspaceName)
await this.request.setWorkspace(workspaceName) // tells the react component to switch to that workspace
for (const file in examples) {
setTimeout(async () => { // space creation of files to give react ui time to update.
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
}, 10)
if (setDefaults) {
for (const file in examples) {
setTimeout(async () => { // space creation of files to give react ui time to update.
try {
await workspaceProvider.set(examples[file].name, examples[file].content)
} catch (error) {
console.error(error)
}
}, 10)
}
}
}
}

@ -97,19 +97,25 @@ class CompileTab extends Plugin {
try {
if (this.fileManager.mode === 'localhost' && hhCompilation) {
const { currentVersion, optimize, runs } = this.compiler.state
const fileContent = `module.exports = {
solidity: '${currentVersion.substring(0, currentVersion.indexOf('+commit'))}',
settings: {
optimizer: {
enabled: ${optimize},
runs: ${runs}
if (currentVersion) {
const fileContent = `module.exports = {
solidity: '${currentVersion.substring(0, currentVersion.indexOf('+commit'))}',
settings: {
optimizer: {
enabled: ${optimize},
runs: ${runs}
}
}
}
`
const configFilePath = 'remix-compiler.config.js'
this.fileManager.setFileContent(configFilePath, fileContent)
this.call('hardhat', 'compile', configFilePath).then((result) => {
this.call('terminal', 'log', { type: 'info', value: result })
}).catch((error) => {
this.call('terminal', 'log', { type: 'error', value: error })
})
}
`
const configFilePath = 'remix-compiler.config.js'
this.fileManager.setFileContent(configFilePath, fileContent)
this.call('hardhat', 'compile', configFilePath)
}
this.fileManager.saveCurrentFile()
this.call('editor', 'clearAnnotations')

@ -24,7 +24,7 @@ class CompilerContainer {
timeout: 300,
allversions: null,
selectedVersion: null,
defaultVersion: 'soljson-v0.8.1+commit.df193b15.js' // this default version is defined: in makeMockCompiler (for browser test)
defaultVersion: 'soljson-v0.8.4+commit.c7e474f2.js' // this default version is defined: in makeMockCompiler (for browser test)
}
}
@ -527,7 +527,7 @@ class CompilerContainer {
// fetching both normal and wasm builds and creating a [version, baseUrl] map
async fetchAllVersion (callback) {
let selectedVersion, allVersionsWasm, isURL
let allVersions = [{ path: 'builtin', longVersion: 'Stable local version - 0.7.4' }]
let allVersions = [{ path: 'builtin', longVersion: 'Stable local version - 0.8.4' }]
// fetch normal builds
const binRes = await promisedMiniXhr(`${baseURLBin}/list.json`)
// fetch wasm builds

@ -0,0 +1,71 @@
import * as packageJson from '../../../../../package.json'
import { Plugin } from '@remixproject/engine'
import Web3 from 'web3'
const yo = require('yo-yo')
const modalDialogCustom = require('../ui/modal-dialog-custom')
const profile = {
name: 'hardhat-provider',
displayName: 'Hardhat Provider',
kind: 'provider',
description: 'Hardhat provider',
methods: ['sendAsync'],
version: packageJson.version
}
export default class HardhatProvider extends Plugin {
constructor (blockchain) {
super(profile)
this.provider = null
this.blockchain = blockchain
}
onDeactivation () {
this.provider = null
}
hardhatProviderDialogBody () {
return yo`
<div class="">
Note: To run Hardhat network node on your system, go to hardhat project folder and run command:
<div class="border p-1">npx hardhat node</div>
<br>
For more info, visit: <a href="https://hardhat.org/getting-started/#connecting-a-wallet-or-dapp-to-hardhat-network" target="_blank">Hardhat Documentation</a>
<br><br>
Hardhat JSON-RPC Endpoint
</div>
`
}
sendAsync (data) {
return new Promise((resolve, reject) => {
if (!this.provider) {
modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => {
this.provider = new Web3.providers.HttpProvider(target)
this.sendAsyncInternal(data, resolve, reject)
}, () => {
this.sendAsyncInternal(data, resolve, reject)
})
} else {
this.sendAsyncInternal(data, resolve, reject)
}
})
}
sendAsyncInternal (data, resolve, reject) {
if (this.provider) {
this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, (error, message) => {
if (error) {
this.provider = null
return reject(error)
}
resolve(message)
})
} else {
const result = data.method === 'net_listening' ? 'canceled' : []
resolve({ jsonrpc: '2.0', result: result, id: data.id })
}
}
}
module.exports = HardhatProvider

@ -9,6 +9,7 @@ const confirmDialog = require('../../ui/confirmDialog')
const modalDialog = require('../../ui/modaldialog')
const MultiParamManager = require('../../ui/multiParamManager')
const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
const _paq = window._paq = window._paq || []
class ContractDropdownUI {
@ -97,7 +98,10 @@ class ContractDropdownUI {
enableAtAddress (enable) {
if (enable) {
const address = this.atAddressButtonInput.value
if (!address || !ethJSUtil.isValidChecksumAddress(address)) return
if (!address || !ethJSUtil.isValidAddress(address)) {
this.enableAtAddress(false)
return
}
this.atAddress.removeAttribute('disabled')
this.atAddress.setAttribute('title', 'Interact with the given contract.')
} else {
@ -387,10 +391,19 @@ class ContractDropdownUI {
loadFromAddress () {
this.event.trigger('clearInstance')
var address = this.atAddressButtonInput.value
let address = this.atAddressButtonInput.value
if (!ethJSUtil.isValidChecksumAddress(address)) {
addTooltip(yo`
<span>
It seems you are not using a checksumed address.
<br>A checksummed address is an address that contains uppercase letters, as specified in <a href="https://eips.ethereum.org/EIPS/eip-55" target="_blank">EIP-55</a>.
<br>Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.
</span>`)
address = ethJSUtil.toChecksumAddress(address)
}
this.dropdownLogic.loadContractFromAddress(address,
(cb) => {
modalDialogCustom.confirm(null, 'Do you really want to interact with ' + address + ' using the current ABI definition?', cb)
modalDialogCustom.confirm('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, cb)
},
(error, loadType, abi) => {
if (error) {

@ -41,7 +41,7 @@ export class RunTab extends ViewPlugin {
this.blockchain = blockchain
this.fileManager = fileManager
this.editor = editor
this.logCallback = (msg) => { mainView.getTerminal().logHtml(msg) }
this.logCallback = (msg) => { mainView.getTerminal().logHtml(yo`<pre>${msg}</pre>`) }
this.filePanel = filePanel
this.compilersArtefacts = compilersArtefacts
this.networkModule = networkModule

@ -25,24 +25,49 @@ function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialP
}
var el = yo`
<div>
<div>You are creating a transaction on the main network. Click confirm if you are sure to continue.</div>
<div class=${css.txInfoBox}>
<div>From: ${tx.from}</div>
<div>To: ${tx.to ? tx.to : '(Contract Creation)'}</div>
<div>Amount: ${amount} Ether</div>
<div>Gas estimation: ${gasEstimation}</div>
<div>Gas limit: ${tx.gas}</div>
<div>Gas price: <input id='gasprice' oninput=${onGasPriceChange} /> Gwei <span> (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> to get more info about gas price)</span></div>
<div>Max transaction fee:<span id='txfee'></span></div>
<div>Data:</div>
<pre class=${css.wrapword}>${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
<div>
<div>You are about to create a transaction on the Main Network. Confirm the details to send the info to your provider.
<br>The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to the Main Network.</div>
<div class="mt-3 ${css.txInfoBox}">
<div>
<span class="text-dark mr-2">From:</span>
<span>${tx.from}</span>
</div>
<div>
<span class="text-dark mr-2">To:</span>
<span>${tx.to ? tx.to : '(Contract Creation)'}</span>
</div>
<div>
<span class="text-dark mr-2">Amount:</span>
<span>${amount} Ether</span>
</div>
<div>
<span class="text-dark mr-2">Gas estimation:</span>
<span>${gasEstimation}</span>
</div>
<div>
<span class="text-dark mr-2">Gas limit:</span>
<span>${tx.gas}</span>
</div>
<div>
<span class="text-dark mr-2">Max transaction fee:</span>
<span id='txfee'></span>
</div>
<div class="d-flex align-items-center my-1">
<span class="text-dark mr-2">Gas price:</span>
<input class="form-control mr-1" style='width: 40px; height: 28px;'id='gasprice' oninput=${onGasPriceChange} />
<span>Gwei (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> for current gas price info.)</span>
</div>
<div class="d-flex align-items-center">
<span class="text-dark mr-2 mb-3">Data:</span>
<pre class=${css.wrapword}>${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
</div>
</div>
<div class="d-flex py-1 align-items-center custom-control custom-checkbox ${css.checkbox}">
<input class="form-check-input custom-control-input" id='confirmsetting' type="checkbox">
<label class="m-0 form-check-label custom-control-label">Do not show this warning again.</label>
</div>
</div>
<div class=${css.checkbox}>
<input id='confirmsetting' type="checkbox">
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i> Do not ask for confirmation again. (the setting will not be persisted for the next page reload)
</div>
</div>
`
initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => {

@ -253,7 +253,7 @@ UniversalDAppUI.prototype.runTransaction = function (lookupOnly, args, valArr, i
const params = args.funABI.type !== 'fallback' ? inputsValues : ''
this.blockchain.runOrCallContractMethod(
args.contractName,
args.contractAbi,
args.contractABI,
args.funABI,
inputsValues,
args.address,

@ -1,23 +1,14 @@
const remixLib = require('@remix-project/remix-lib')
const txFormat = remixLib.execution.txFormat
const txExecution = remixLib.execution.txExecution
const typeConversion = remixLib.execution.typeConversion
const Txlistener = remixLib.execution.txListener
const TxRunner = remixLib.execution.TxRunner
const TxRunnerWeb3 = remixLib.execution.TxRunnerWeb3
const txHelper = remixLib.execution.txHelper
const EventManager = remixLib.EventManager
const { ExecutionContext } = require('./execution-context')
const Web3 = require('web3')
const async = require('async')
const { EventEmitter } = require('events')
const { resultToRemixTx } = remixLib.helpers.txResultHelper
const VMProvider = require('./providers/vm.js')
const InjectedProvider = require('./providers/injected.js')
const NodeProvider = require('./providers/node.js')
import Web3 from 'web3'
import { toBuffer, addHexPrefix } from 'ethereumjs-util'
import { waterfall } from 'async'
import { EventEmitter } from 'events'
import { ExecutionContext } from './execution-context'
import VMProvider from './providers/vm.js'
import InjectedProvider from './providers/injected.js'
import NodeProvider from './providers/node.js'
import { execution, EventManager, helpers } from '@remix-project/remix-lib'
const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const { txResultHelper: resultToRemixTx } = helpers
class Blockchain {
// NOTE: the config object will need to be refactored out in remix-lib
@ -262,6 +253,10 @@ class Blockchain {
}
if (funABI.type === 'fallback') data.dataHex = value
if (data) {
data.contractName = contractName
data.contractABI = contractAbi
}
const useCall = funABI.stateMutability === 'view' || funABI.stateMutability === 'pure'
this.runTx({ to: address, data, useCall }, confirmationCb, continueCb, promptCb, (error, txResult, _address, returnValue) => {
if (error) {
@ -392,7 +387,7 @@ class Blockchain {
runTx (args, confirmationCb, continueCb, promptCb, cb) {
const self = this
async.waterfall([
waterfall([
function getGasLimit (next) {
if (self.transactionContextAPI.getGasLimit) {
return self.transactionContextAPI.getGasLimit(next)
@ -459,16 +454,24 @@ class Blockchain {
try { error = 'error: ' + JSON.stringify(error) } catch (e) {}
}
}
next(error, result)
next(error, result, tx)
}
)
}
],
async (error, txResult) => {
async (error, txResult, tx) => {
if (error) {
return cb(error)
}
/*
value of txResult is inconsistent:
- transact to contract:
{"receipt": { ... }, "tx":{ ... }, "transactionHash":"0x7ba4c05075210fdbcf4e6660258379db5cc559e15703f9ac6f970a320c2dee09"}
- call to contract:
{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x5236a76152054a8aad0c7135bcc151f03bccb773be88fbf4823184e47fc76247"}
*/
const isVM = this.executionContext.isVM()
let execResult
let returnValue = null
@ -476,14 +479,18 @@ class Blockchain {
execResult = await this.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
if (execResult) {
// if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value.
returnValue = (execResult && isVM) ? execResult.returnValue : txResult
const vmError = txExecution.checkVMError(execResult)
returnValue = execResult ? execResult.returnValue : toBuffer(addHexPrefix(txResult.result) || '0x0000000000000000000000000000000000000000000000000000000000000000')
const vmError = txExecution.checkVMError(execResult, args.data.contractABI)
if (vmError.error) {
return cb(vmError.message)
}
}
}
if (!isVM && tx && tx.useCall) {
returnValue = toBuffer(addHexPrefix(txResult.result))
}
let address = null
if (txResult && txResult.receipt) {
address = txResult.receipt.contractAddress

@ -145,7 +145,6 @@ export class ExecutionContext {
if (context === 'web3') {
confirmCb(cb)
}
if (this.customNetWorks[context]) {
var network = this.customNetWorks[context]
this.setProviderFromEndpoint(network.provider, network.name, (error) => {
@ -189,14 +188,16 @@ export class ExecutionContext {
const oldProvider = web3.currentProvider
web3.setProvider(endpoint)
web3.eth.net.isListening((err, isConnected) => {
if (!err && isConnected) {
if (!err && isConnected === true) {
this.executionContext = context
this._updateBlockGasLimit()
this.event.trigger('contextChanged', [context])
this.event.trigger('web3EndpointChanged')
cb()
} else if (isConnected === 'canceled') {
web3.setProvider(oldProvider)
cb()
} else {
web3.setProvider(oldProvider)
cb('Not possible to connect to the Web3 provider. Make sure the provider is running, a connection is open (via IPC or RPC) or that the provider plugin is properly configured.')

@ -71,6 +71,20 @@ module.exports = {
return name + counter + prefix + '.' + ext
},
async createNonClashingDirNameAsync (name, fileManager) {
if (!name) name = 'Undefined'
let counter = ''
let exist = true
do {
const isDuplicate = await fileManager.exists(name + counter)
if (isDuplicate) counter = (counter | 0) + 1
else exist = false
} while (exist)
return name + counter
},
checkSpecialChars (name) {
return name.match(/[:*?"<>\\'|]/) != null
},

@ -111,6 +111,12 @@ export class RemixAppManager extends PluginManager {
try {
const res = await fetch(this.pluginsDirectory)
plugins = await res.json()
plugins = plugins.filter((plugin) => {
if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) {
return (plugin.targets.includes('remix'))
}
return true
})
localStorage.setItem('plugins-directory', JSON.stringify(plugins))
} catch (e) {
console.log('getting plugins list from localstorage...')

@ -1,7 +1,7 @@
'use strict'
import { RefType } from './RefType'
import { normalizeHex } from './util'
import { toBuffer, setLengthLeft, keccak, BN, bufferToHex } from 'ethereumjs-util'
import { toBuffer, setLengthLeft, keccak, BN, bufferToHex, addHexPrefix } from 'ethereumjs-util'
export class Mapping extends RefType {
keyType
@ -64,8 +64,8 @@ function getMappingLocation (key, position) {
// > the value corresponding to a mapping key k is located at keccak256(k . p) where . is concatenation.
// key should be a hex string, and position an int
const mappingK = toBuffer('0x' + key)
let mappingP = toBuffer(position)
const mappingK = toBuffer(addHexPrefix(key))
let mappingP = toBuffer(addHexPrefix(position))
mappingP = setLengthLeft(mappingP, 32)
const mappingKeyBuf = concatTypedArrays(mappingK, mappingP)
const mappingStorageLocation: Buffer = keccak(mappingKeyBuf)

@ -1,5 +1,6 @@
'use strict'
import { ethers } from 'ethers'
import { getFunctionFragment } from './txHelper'
/**
* deploy the given contract
@ -56,7 +57,7 @@ export function callFunction (from, to, data, value, gasLimit, funAbi, txRunner,
* @param {Object} execResult - execution result given by the VM
* @return {Object} - { error: true/false, message: DOMNode }
*/
export function checkVMError (execResult) {
export function checkVMError (execResult, abi) {
const errorCode = {
OUT_OF_GAS: 'out of gas',
STACK_UNDERFLOW: 'stack underflow',
@ -88,19 +89,48 @@ export function checkVMError (execResult) {
ret.error = true
} else if (exceptionError === errorCode.REVERT) {
const returnData = execResult.returnValue
// It is the hash of Error(string)
if (returnData && (returnData.slice(0, 4).toString('hex') === '08c379a0')) {
const abiCoder = new ethers.utils.AbiCoder()
const reason = abiCoder.decode(['string'], returnData.slice(4))[0]
msg = `\tThe transaction has been reverted to the initial state.\nReason provided by the contract: "${reason}".`
} else {
msg = '\tThe transaction has been reverted to the initial state.\nNote: The called function should be payable if you send value and the value you send should be less than your current balance.'
const returnDataHex = returnData.slice(0, 4).toString('hex')
let customError
if (abi) {
let decodedCustomErrorInputs
for (const item of abi) {
if (item.type === 'error') {
// ethers doesn't crash anymore if "error" type is specified, but it doesn't extract the errors. see:
// https://github.com/ethers-io/ethers.js/commit/bd05aed070ac9e1421a3e2bff2ceea150bedf9b7
// we need here to fake the type, so the "getSighash" function works properly
const fn = getFunctionFragment({ ...item, type: 'function', stateMutability: 'nonpayable' })
if (!fn) continue
const sign = fn.getSighash(item.name)
if (!sign) continue
if (returnDataHex === sign.replace('0x', '')) {
customError = item.name
decodedCustomErrorInputs = fn.decodeFunctionData(fn.getFunction(item.name), returnData)
break
}
}
}
if (decodedCustomErrorInputs) {
msg = '\tThe transaction has been reverted to the initial state.\nError provided by the contract:'
msg += `\n${customError}`
msg += '\nParameters:'
msg += `\n${decodedCustomErrorInputs}`
}
}
if (!customError) {
// It is the hash of Error(string)
if (returnData && (returnDataHex === '08c379a0')) {
const abiCoder = new ethers.utils.AbiCoder()
const reason = abiCoder.decode(['string'], returnData.slice(4))[0]
msg = `\tThe transaction has been reverted to the initial state.\nReason provided by the contract: "${reason}".`
} else {
msg = '\tThe transaction has been reverted to the initial state.\nNote: The called function should be payable if you send value and the value you send should be less than your current balance.'
}
}
ret.error = true
} else if (exceptionError === errorCode.STATIC_STATE_CHANGE) {
msg = '\tState changes is not allowed in Static Call context\n'
ret.error = true
}
ret.message = `${error}${exceptionError}${msg}\tDebug the transaction to get more information.`
ret.message = `${error}\n${exceptionError}\n${msg}\nDebug the transaction to get more information.`
return ret
}

@ -357,14 +357,12 @@ export function decodeResponse (response, fnabi) {
if (fnabi.outputs && fnabi.outputs.length > 0) {
try {
let i
const outputTypes = []
for (i = 0; i < fnabi.outputs.length; i++) {
const type = fnabi.outputs[i].type
outputTypes.push(type.indexOf('tuple') === 0 ? makeFullTypeDefinition(fnabi.outputs[i]) : type)
}
if (!response.length) response = new Uint8Array(32 * fnabi.outputs.length) // ensuring the data is at least filled by 0 cause `AbiCoder` throws if there's not engouh data
if (!response || !response.length) response = new Uint8Array(32 * fnabi.outputs.length) // ensuring the data is at least filled by 0 cause `AbiCoder` throws if there's not engouh data
// decode data
const abiCoder = new ethers.utils.AbiCoder()
const decodedObj = abiCoder.decode(outputTypes, response)

@ -38,6 +38,11 @@ export function encodeFunctionId (funABI) {
return abi.getSighash(funABI.name)
}
export function getFunctionFragment (funABI): ethers.utils.Interface {
if (funABI.type === 'fallback' || funABI.type === 'receive') return null
return new ethers.utils.Interface([funABI])
}
export function sortAbiFunction (contractabi) {
// Check if function is constant (introduced with Solidity 0.6.0)
const isConstant = ({ stateMutability }) => stateMutability === 'view' || stateMutability === 'pure'

@ -1,7 +1,7 @@
'use strict'
import { each } from 'async'
import { ethers } from 'ethers'
import { toBuffer } from 'ethereumjs-util'
import { toBuffer, addHexPrefix } from 'ethereumjs-util'
import { EventManager } from '../eventManager'
import { compareByteCode } from '../util'
import { decodeResponse } from './txFormat'
@ -68,7 +68,7 @@ export class TxListener {
execResult = await this.executionContext.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
returnValue = execResult.returnValue
} else {
returnValue = toBuffer(txResult.result)
returnValue = toBuffer(addHexPrefix(txResult.result))
}
const call = {
from: from,
@ -358,7 +358,7 @@ export class TxListener {
}
_decodeInputParams (data, abi) {
data = toBuffer('0x' + data)
data = toBuffer(addHexPrefix(data))
if (!data.length) data = new Uint8Array(32 * abi.inputs.length) // ensuring the data is at least filled by 0 cause `AbiCoder` throws if there's not engouh data
const inputTypes = []

@ -1,5 +1,5 @@
'use strict'
import { BN, bufferToHex, keccak, setLengthLeft, toBuffer } from 'ethereumjs-util'
import { BN, bufferToHex, keccak, setLengthLeft, toBuffer, addHexPrefix } from 'ethereumjs-util'
/*
contains misc util: @TODO should be splitted
@ -142,7 +142,7 @@ export function buildCallPath (index, rootCall) {
*/
// eslint-disable-next-line camelcase
export function sha3_256 (value) {
value = toBuffer(value)
value = toBuffer(addHexPrefix(value))
const retInBuffer: Buffer = keccak(setLengthLeft(value, 32))
return bufferToHex(retInBuffer)
}

@ -237,7 +237,6 @@ export const init = (provider, workspaceName: string, plugin, registry) => (disp
fetchDirectory(provider, workspaceName)(dispatch)
})
dispatch(fetchProviderSuccess(provider))
dispatch(setCurrentWorkspace(workspaceName))
} else {
dispatch(fetchProviderError('No provider available'))
}

@ -127,10 +127,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
const editRef = useRef(null)
useEffect(() => {
if (props.filesProvider) {
init(props.filesProvider, props.name, props.plugin, props.registry)(dispatch)
}
}, [props.filesProvider, props.name])
init(props.filesProvider, props.name, props.plugin, props.registry)(dispatch)
}, [])
useEffect(() => {
const provider = fileSystem.provider.provider
@ -764,10 +762,6 @@ export const FileExplorer = (props: FileExplorerProps) => {
state.copyElement.map(({ key, type }) => {
type === 'file' ? copyFile(key, dest) : copyFolder(key, dest)
})
setState(prevState => {
return { ...prevState, copyElement: [] }
})
setCanPaste(false)
}
const label = (file: File) => {

@ -9,7 +9,6 @@ export const fileSystemInitialState = {
files: {
files: [],
expandPath: [],
workspaceName: null,
blankPath: null,
isRequesting: false,
isSuccessful: false,
@ -83,7 +82,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state,
files: {
...state.files,
files: resolveDirectory(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
files: resolveDirectory(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
isRequesting: false,
isSuccessful: true,
error: null
@ -135,21 +134,12 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
}
}
}
case 'SET_CURRENT_WORKSPACE': {
return {
...state,
files: {
...state.files,
workspaceName: action.payload
}
}
}
case 'ADD_INPUT_FIELD': {
return {
...state,
files: {
...state.files,
files: addInputField(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
files: addInputField(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
blankPath: action.payload.path,
isRequesting: false,
isSuccessful: true,
@ -162,7 +152,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state,
files: {
...state.files,
files: removeInputField(state.files.workspaceName, state.files.blankPath, state.files.files),
files: removeInputField(state.provider.provider, state.files.blankPath, state.files.files),
blankPath: null,
isRequesting: false,
isSuccessful: true,
@ -175,7 +165,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state,
files: {
...state.files,
files: fileAdded(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
files: fileAdded(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
expandPath: [...new Set([...state.files.expandPath, action.payload.path])],
isRequesting: false,
isSuccessful: true,
@ -188,7 +178,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state,
files: {
...state.files,
files: folderAdded(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files),
files: folderAdded(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
expandPath: [...new Set([...state.files.expandPath, action.payload.path])],
isRequesting: false,
isSuccessful: true,
@ -201,7 +191,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state,
files: {
...state.files,
files: fileRemoved(state.files.workspaceName, action.payload.path, action.payload.removePath, state.files.files),
files: fileRemoved(state.provider.provider, action.payload.path, action.payload.removePath, state.files.files),
isRequesting: false,
isSuccessful: true,
error: null
@ -213,7 +203,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state,
files: {
...state.files,
files: fileRenamed(state.files.workspaceName, action.payload.path, action.payload.removePath, state.files.files, action.payload.files),
files: fileRenamed(state.provider.provider, action.payload.path, action.payload.removePath, state.files.files, action.payload.files),
isRequesting: false,
isSuccessful: true,
error: null
@ -244,7 +234,9 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
}
}
const resolveDirectory = (root, path: string, files, content) => {
const resolveDirectory = (provider, path: string, files, content) => {
const root = provider.workspace || provider.type
if (path === root) return { [root]: { ...content[root], ...files[root] } }
const pathArr: string[] = path.split('/').filter(value => value)
@ -287,14 +279,18 @@ const removePath = (root, path: string, pathName, files) => {
return files
}
const addInputField = (root, path: string, files, content) => {
const addInputField = (provider, path: string, files, content) => {
const root = provider.workspace || provider.type || ''
if (path === root) return { [root]: { ...content[root], ...files[root] } }
const result = resolveDirectory(root, path, files, content)
const result = resolveDirectory(provider, path, files, content)
return result
}
const removeInputField = (root, path: string, files) => {
const removeInputField = (provider, path: string, files) => {
const root = provider.workspace || provider.type || ''
if (path === root) {
delete files[root][path + '/' + 'blank']
return files
@ -302,15 +298,17 @@ const removeInputField = (root, path: string, files) => {
return removePath(root, path, path + '/' + 'blank', files)
}
const fileAdded = (root, path: string, files, content) => {
return resolveDirectory(root, path, files, content)
const fileAdded = (provider, path: string, files, content) => {
return resolveDirectory(provider, path, files, content)
}
const folderAdded = (root, path: string, files, content) => {
return resolveDirectory(root, path, files, content)
const folderAdded = (provider, path: string, files, content) => {
return resolveDirectory(provider, path, files, content)
}
const fileRemoved = (root, path: string, removedPath: string, files) => {
const fileRemoved = (provider, path: string, removedPath: string, files) => {
const root = provider.workspace || provider.type || ''
if (path === root) {
delete files[root][removedPath]
@ -319,7 +317,9 @@ const fileRemoved = (root, path: string, removedPath: string, files) => {
return removePath(root, path, extractNameFromKey(removedPath), files)
}
const fileRenamed = (root, path: string, removePath: string, files, content) => {
const fileRenamed = (provider, path: string, removePath: string, files, content) => {
const root = provider.workspace || provider.type || ''
if (path === root) {
const allFiles = { [root]: { ...content[root], ...files[root] } }

@ -67,7 +67,7 @@ export const Workspace = (props: WorkspaceProps) => {
}
props.request.getCurrentWorkspace = () => {
return state.currentWorkspace
return { name: state.currentWorkspace, isLocalhost: state.currentWorkspace === LOCALHOST, absolutePath: `${props.workspace.workspacesPath}/${state.currentWorkspace}` }
}
useEffect(() => {
@ -97,10 +97,10 @@ export const Workspace = (props: WorkspaceProps) => {
props.fileManager.setMode('browser')
}
}
props.localhost.event.off('disconnected', localhostDisconnect)
props.localhost.event.on('disconnected', localhostDisconnect)
useEffect(() => {
props.localhost.event.off('disconnected', localhostDisconnect)
props.localhost.event.on('disconnected', localhostDisconnect)
props.localhost.event.on('connected', () => {
remixdExplorer.show()
setWorkspace(LOCALHOST)
@ -253,18 +253,18 @@ export const Workspace = (props: WorkspaceProps) => {
const remixdExplorer = {
hide: async () => {
// If 'connect to localhost' is clicked from home tab, mode is not 'localhost'
if (props.fileManager.mode === 'localhost') {
await setWorkspace(NO_WORKSPACE)
props.fileManager.setMode('browser')
setState(prevState => {
return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
})
} else {
// Hide spinner in file explorer
setState(prevState => {
return { ...prevState, loadingLocalhost: false }
})
}
// if (props.fileManager.mode === 'localhost') {
await setWorkspace(NO_WORKSPACE)
props.fileManager.setMode('browser')
setState(prevState => {
return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
})
// } else {
// // Hide spinner in file explorer
// setState(prevState => {
// return { ...prevState, loadingLocalhost: false }
// })
// }
},
show: () => {
props.fileManager.setMode('localhost')

@ -59,7 +59,7 @@ describe('testRunner', () => {
// Test github import
describe('test getting github imports', () => {
const urlResolver = new RemixURLResolver()
const fileName: string = 'github.com/MathCody/solidity-examples/greeter/greeter.sol'
const fileName: string = 'github.com/ethential/solidity-examples/solidity-features-check/greeter.sol'
let results: object = {}
before(done => {
@ -78,8 +78,8 @@ describe('testRunner', () => {
})
it('should return contract content of given github path', () => {
const expt: object = {
cleanUrl: 'MathCody/solidity-examples/greeter/greeter.sol',
content: 'pragma solidity >=0.5.0 <0.6.0;\nimport \"../mortal/mortal.sol\";\n\ncontract Greeter is Mortal {\n /* Define variable greeting of the type string */\n string greeting;\n\n /* This runs when the contract is executed */\n constructor(string memory _greeting) public {\n greeting = _greeting;\n }\n\n /* Main function */\n function greet() public view returns (string memory) {\n return greeting;\n }\n}',
cleanUrl: 'ethential/solidity-examples/solidity-features-check/greeter.sol',
content: 'pragma solidity >=0.7.0;\nimport \"./mortal.sol\";\n// SPDX-License-Identifier: GPL-3.0\n\ncontract Greeter is Mortal {\n /* Define variable greeting of the type string */\n string greeting;\n\n /* This runs when the contract is executed */\n constructor(string memory _greeting) {\n greeting = _greeting;\n }\n\n /* Main function */\n function greet() public view returns (string memory) {\n return greeting;\n }\n}\n\n// 0x37aA58B2cE3Bb9576EEBCD51315070eA8806b7c4\n',
type: 'github'
}
assert.deepEqual(results, expt)
@ -268,6 +268,7 @@ describe('testRunner', () => {
})
// Test SWARM imports
/*
describe('test getting SWARM imports', () => {
const urlResolver = new RemixURLResolver()
const fileName = 'bzz-raw://a728627437140f2b0b46c1bcfb0de2126d18b40e9b61c3e31bd96abebf714619'
@ -297,6 +298,7 @@ describe('testRunner', () => {
assert.deepEqual(results, expt)
})
})
*/
})
})
})

@ -4,7 +4,7 @@ import * as semver from 'semver'
import WebSocket from '../websocket'
import * as servicesList from '../serviceList'
import * as WS from 'ws' // eslint-disable-line
import { getDomain } from '../utils'
import { getDomain, absolutePath } from '../utils'
import Axios from 'axios'
import * as fs from 'fs-extra'
import * as path from 'path'
@ -28,6 +28,7 @@ const services = {
folder: (readOnly: boolean) => new servicesList.Sharedfolder(readOnly)
}
// Similar object is also defined in websocket.ts
const ports = {
git: 65521,
hardhat: 65522,
@ -80,10 +81,15 @@ function startService<S extends 'git' | 'hardhat' | 'folder'> (service: S, callb
sharedFolderClient.setupNotifications(program.sharedFolder)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
// Run hardhat service if a hardhat project is shared as folder
const hardhatConfigFilePath = absolutePath('./', program.sharedFolder) + '/hardhat.config.js'
const isHardhatProject = fs.existsSync(hardhatConfigFilePath)
if (isHardhatProject) {
startService('hardhat', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)
sharedFolderClient.sharedFolder(program.sharedFolder)
})
}
/*
startService('git', (ws: WS, sharedFolderClient: servicesList.Sharedfolder) => {
sharedFolderClient.setWebSocket(ws)

@ -24,7 +24,6 @@ export class HardhatClient extends PluginClient {
return new Promise((resolve, reject) => {
if (this.readOnly) {
const errMsg = '[Hardhat Compilation]: Cannot compile in read-only mode'
console.log('\x1b[31m%s\x1b[0m', `${errMsg}`)
return reject(new Error(errMsg))
}
const cmd = `npx hardhat compile --config ${configPath}`
@ -33,12 +32,12 @@ export class HardhatClient extends PluginClient {
let result = ''
let error = ''
child.stdout.on('data', (data) => {
console.log('\x1b[32m%s\x1b[0m', `[Hardhat Compilation]: ${data.toString()}`)
result += data.toString()
const msg = `[Hardhat Compilation]: ${data.toString()}`
console.log('\x1b[32m%s\x1b[0m', msg)
result += msg + '\n'
})
child.stderr.on('data', (err) => {
console.log('\x1b[31m%s\x1b[0m', `[Hardhat Compilation]: ${err.toString()}`)
error += err.toString()
error += `[Hardhat Compilation]: ${err.toString()}`
})
child.on('close', () => {
if (error) reject(error)

@ -16,9 +16,13 @@ export default class WebSocket {
response.end()
})
const loopback = '127.0.0.1'
const listeners = {
65520: 'remixd',
65521: 'git',
65522: 'hardhat'
}
this.server.listen(this.port, loopback, () => {
console.log(`${new Date()} remixd is listening on ${loopback}:${this.port}`)
console.log('\x1b[32m%s\x1b[0m', `[INFO] ${new Date()} ${listeners[this.port]} is listening on ${loopback}:${this.port}`)
})
this.wsServer = new WS.Server({
server: this.server,

794
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -50,7 +50,7 @@
"bumpVersion:libs": "gulp & gulp syncLibVersions;",
"browsertest": "sleep 5 && npm run nightwatch_local",
"csslint": "csslint --ignore=order-alphabetical --errors='errors,duplicate-properties,empty-rules' --exclude-list='apps/remix-ide/src/assets/css/font-awesome.min.css' apps/remix-ide/src/assets/css/",
"downloadsolc_assets": "wget --no-check-certificate https://binaries.soliditylang.org/bin/soljson-v0.8.1+commit.df193b15.js -O ./apps/remix-ide/src/assets/js/soljson.js",
"downloadsolc_assets": "wget --no-check-certificate https://binaries.soliditylang.org/bin/soljson-v0.8.4+commit.c7e474f2.js -O ./apps/remix-ide/src/assets/js/soljson.js",
"make-mock-compiler": "node apps/remix-ide/ci/makeMockCompiler.js",
"minify": "uglifyjs --in-source-map inline --source-map-inline -c warnings=false",
"nightwatch_parallel": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js --env=chrome,firefox",
@ -134,13 +134,13 @@
"@ethereumjs/common": "^2.2.0",
"@ethereumjs/tx": "^3.1.3",
"@ethereumjs/vm": "^5.3.2",
"@remixproject/engine": "^0.3.14",
"@remixproject/engine-web": "^0.3.14",
"@remixproject/plugin": "^0.3.14",
"@remixproject/plugin-api": "^0.3.14",
"@remixproject/plugin-utils": "^0.3.14",
"@remixproject/plugin-webview": "^0.3.14",
"@remixproject/plugin-ws": "^0.3.14",
"@remixproject/engine": "^0.3.17",
"@remixproject/engine-web": "^0.3.17",
"@remixproject/plugin": "^0.3.17",
"@remixproject/plugin-api": "^0.3.17",
"@remixproject/plugin-utils": "^0.3.17",
"@remixproject/plugin-webview": "^0.3.17",
"@remixproject/plugin-ws": "^0.3.17",
"ansi-gray": "^0.1.1",
"async": "^2.6.2",
"axios": ">=0.21.1",
@ -154,12 +154,14 @@
"ethereumjs-util": "^7.0.10",
"ethers": "^5.1.4",
"express-ws": "^4.0.0",
"file-saver": "^2.0.5",
"form-data": "^4.0.0",
"fs-extra": "^3.0.1",
"http-server": "^0.11.1",
"isbinaryfile": "^3.0.2",
"isomorphic-git": "^1.8.2",
"jquery": "^3.3.1",
"jszip": "^3.6.0",
"lodash": "^4.17.21",
"latest-version": "^5.1.0",
"merge": "^1.2.0",
"npm-install-version": "^6.0.2",

Loading…
Cancel
Save