commit
70f4cb16f6
@ -1,46 +0,0 @@ |
|||||||
'use strict' |
|
||||||
var remixLib = require('@remix-project/remix-lib') |
|
||||||
var txHelper = remixLib.execution.txHelper |
|
||||||
|
|
||||||
module.exports = class CompilerAbstract { |
|
||||||
constructor (languageversion, data, source) { |
|
||||||
this.languageversion = languageversion |
|
||||||
this.data = data |
|
||||||
this.source = source // source code
|
|
||||||
} |
|
||||||
|
|
||||||
getContracts () { |
|
||||||
return this.data.contracts |
|
||||||
} |
|
||||||
|
|
||||||
getContract (name) { |
|
||||||
return txHelper.getContract(name, this.data.contracts) |
|
||||||
} |
|
||||||
|
|
||||||
visitContracts (calllback) { |
|
||||||
return txHelper.visitContracts(this.data.contracts, calllback) |
|
||||||
} |
|
||||||
|
|
||||||
getData () { |
|
||||||
return this.data |
|
||||||
} |
|
||||||
|
|
||||||
getAsts () { |
|
||||||
return this.data.sources // ast
|
|
||||||
} |
|
||||||
|
|
||||||
getSourceName (fileIndex) { |
|
||||||
if (this.data && this.data.sources) { |
|
||||||
return Object.keys(this.data.sources)[fileIndex] |
|
||||||
} else if (Object.keys(this.source.sources).length === 1) { |
|
||||||
// if we don't have ast, we return the only one filename present.
|
|
||||||
const sourcesArray = Object.keys(this.source.sources) |
|
||||||
return sourcesArray[0] |
|
||||||
} |
|
||||||
return null |
|
||||||
} |
|
||||||
|
|
||||||
getSourceCode () { |
|
||||||
return this.source |
|
||||||
} |
|
||||||
} |
|
@ -1,169 +0,0 @@ |
|||||||
'use strict' |
|
||||||
import { Plugin } from '@remixproject/engine' |
|
||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { RemixURLResolver } from '@remix-project/remix-url-resolver' |
|
||||||
const remixTests = require('@remix-project/remix-tests') |
|
||||||
const globalRegistry = require('../../global/registry') |
|
||||||
const addTooltip = require('../ui/tooltip') |
|
||||||
const async = require('async') |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'contentImport', |
|
||||||
displayName: 'content import', |
|
||||||
version: packageJson.version, |
|
||||||
methods: ['resolve', 'resolveAndSave', 'isExternalUrl'] |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = class CompilerImports extends Plugin { |
|
||||||
constructor (fileManager) { |
|
||||||
super(profile) |
|
||||||
this.fileManager = fileManager |
|
||||||
// const token = await this.call('settings', 'getGithubAccessToken')
|
|
||||||
const token = globalRegistry.get('config').api.get('settings/gist-access-token') // TODO replace with the plugin call above https://github.com/ethereum/remix-ide/issues/2288
|
|
||||||
const protocol = window.location.protocol |
|
||||||
this.urlResolver = new RemixURLResolver(token, protocol) |
|
||||||
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
|
|
||||||
} |
|
||||||
|
|
||||||
isRelativeImport (url) { |
|
||||||
return /^([^/]+)/.exec(url) |
|
||||||
} |
|
||||||
|
|
||||||
isExternalUrl (url) { |
|
||||||
const handlers = this.urlResolver.getHandlers() |
|
||||||
return handlers.some(handler => handler.match(url)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* resolve the content of @arg url. This only resolves external URLs. |
|
||||||
* |
|
||||||
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc... |
|
||||||
* @returns {Promise} - { content, cleanUrl, type, url } |
|
||||||
*/ |
|
||||||
resolve (url) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
this.import(url, null, (error, content, cleanUrl, type, url) => { |
|
||||||
if (error) return reject(error) |
|
||||||
resolve({ content, cleanUrl, type, url }) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
async import (url, force, loadingCb, cb) { |
|
||||||
if (typeof force !== 'boolean') { |
|
||||||
const temp = loadingCb |
|
||||||
loadingCb = force |
|
||||||
cb = temp |
|
||||||
force = false |
|
||||||
} |
|
||||||
if (!loadingCb) loadingCb = () => {} |
|
||||||
if (!cb) cb = () => {} |
|
||||||
|
|
||||||
var self = this |
|
||||||
if (force) delete this.previouslyHandled[url] |
|
||||||
var imported = this.previouslyHandled[url] |
|
||||||
if (imported) { |
|
||||||
return cb(null, imported.content, imported.cleanUrl, imported.type, url) |
|
||||||
} |
|
||||||
|
|
||||||
let resolved |
|
||||||
try { |
|
||||||
resolved = await this.urlResolver.resolve(url) |
|
||||||
const { content, cleanUrl, type } = resolved |
|
||||||
self.previouslyHandled[url] = { |
|
||||||
content, |
|
||||||
cleanUrl, |
|
||||||
type |
|
||||||
} |
|
||||||
cb(null, content, cleanUrl, type, url) |
|
||||||
} catch (e) { |
|
||||||
return cb(new Error('not found ' + url)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
importExternal (url, targetPath, cb) { |
|
||||||
this.import(url, |
|
||||||
// TODO: move to an event that is generated, the UI shouldn't be here
|
|
||||||
(loadingMsg) => { addTooltip(loadingMsg) }, |
|
||||||
(error, content, cleanUrl, type, url) => { |
|
||||||
if (error) return cb(error) |
|
||||||
if (this.fileManager) { |
|
||||||
const provider = this.fileManager.currentFileProvider() |
|
||||||
const path = targetPath || type + '/' + cleanUrl |
|
||||||
if (provider) provider.addExternal('.deps/' + path, content, url) |
|
||||||
} |
|
||||||
cb(null, content) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* import the content of @arg url. |
|
||||||
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*` |
|
||||||
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder |
|
||||||
* then check if the @arg url match any external url |
|
||||||
* |
|
||||||
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc... |
|
||||||
* @param {String} targetPath - (optional) internal path where the content should be saved to |
|
||||||
* @returns {Promise} - string content |
|
||||||
*/ |
|
||||||
resolveAndSave (url, targetPath) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode) |
|
||||||
if (!this.fileManager) { |
|
||||||
// fallback to just resolving the file, it won't be saved in file manager
|
|
||||||
return this.importExternal(url, targetPath, (error, content) => { |
|
||||||
if (error) return reject(error) |
|
||||||
resolve(content) |
|
||||||
}) |
|
||||||
} |
|
||||||
var provider = this.fileManager.fileProviderOf(url) |
|
||||||
if (provider) { |
|
||||||
if (provider.type === 'localhost' && !provider.isConnected()) { |
|
||||||
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`)) |
|
||||||
} |
|
||||||
provider.exists(url).then(exist => { |
|
||||||
/* |
|
||||||
if the path is absolute and the file does not exist, we can stop here |
|
||||||
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop. |
|
||||||
*/ |
|
||||||
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`)) |
|
||||||
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`)) |
|
||||||
|
|
||||||
if (exist) { |
|
||||||
return provider.get(url, (error, content) => { |
|
||||||
if (error) return reject(error) |
|
||||||
resolve(content) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
|
|
||||||
const localhostProvider = this.fileManager.getProvider('localhost') |
|
||||||
if (localhostProvider.isConnected()) { |
|
||||||
var splitted = /([^/]+)\/(.*)$/g.exec(url) |
|
||||||
return async.tryEach([ |
|
||||||
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
|
||||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2]).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }, |
|
||||||
(cb) => { this.resolveAndSave('localhost/node_modules/' + url).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
|
||||||
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2]).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }], |
|
||||||
(error, result) => { |
|
||||||
if (error) { |
|
||||||
return this.importExternal(url, targetPath, (error, content) => { |
|
||||||
if (error) return reject(error) |
|
||||||
resolve(content) |
|
||||||
}) |
|
||||||
} |
|
||||||
resolve(result) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
this.importExternal(url, targetPath, (error, content) => { |
|
||||||
if (error) return reject(error) |
|
||||||
resolve(content) |
|
||||||
}) |
|
||||||
}).catch(error => { |
|
||||||
return reject(error) |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
'use strict' |
|
||||||
|
|
||||||
module.exports = (sources, opts) => { |
|
||||||
return JSON.stringify({ |
|
||||||
language: 'Solidity', |
|
||||||
sources: sources, |
|
||||||
settings: { |
|
||||||
optimizer: { |
|
||||||
enabled: opts.optimize === true || opts.optimize === 1, |
|
||||||
runs: 200 |
|
||||||
}, |
|
||||||
libraries: opts.libraries, |
|
||||||
outputSelection: { |
|
||||||
'*': { |
|
||||||
'': ['ast'], |
|
||||||
'*': ['abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates'] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
@ -1,148 +0,0 @@ |
|||||||
'use strict' |
|
||||||
import { Plugin } from '@remixproject/engine' |
|
||||||
import * as packageJson from '../../../../../package.json' |
|
||||||
import { joinPath } from '../../lib/helper' |
|
||||||
var CompilerAbstract = require('../compiler/compiler-abstract') |
|
||||||
|
|
||||||
const profile = { |
|
||||||
name: 'compilerMetadata', |
|
||||||
methods: ['deployMetadataOf'], |
|
||||||
events: [], |
|
||||||
version: packageJson.version |
|
||||||
} |
|
||||||
|
|
||||||
class CompilerMetadata extends Plugin { |
|
||||||
constructor (blockchain, fileManager, config) { |
|
||||||
super(profile) |
|
||||||
this.blockchain = blockchain |
|
||||||
this.fileManager = fileManager |
|
||||||
this.config = config |
|
||||||
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'görli:5', 'Custom'] |
|
||||||
this.innerPath = 'artifacts' |
|
||||||
} |
|
||||||
|
|
||||||
_JSONFileName (path, contractName) { |
|
||||||
return joinPath(path, this.innerPath, contractName + '.json') |
|
||||||
} |
|
||||||
|
|
||||||
_MetadataFileName (path, contractName) { |
|
||||||
return joinPath(path, this.innerPath, contractName + '_metadata.json') |
|
||||||
} |
|
||||||
|
|
||||||
onActivation () { |
|
||||||
var self = this |
|
||||||
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => { |
|
||||||
if (!self.config.get('settings/generate-contract-metadata')) return |
|
||||||
const compiler = new CompilerAbstract(languageVersion, data, source) |
|
||||||
var provider = self.fileManager.fileProviderOf(source.target) |
|
||||||
var path = self.fileManager.extractPathOf(source.target) |
|
||||||
if (provider) { |
|
||||||
compiler.visitContracts((contract) => { |
|
||||||
if (contract.file !== source.target) return |
|
||||||
|
|
||||||
var fileName = self._JSONFileName(path, contract.name) |
|
||||||
var metadataFileName = self._MetadataFileName(path, contract.name) |
|
||||||
provider.get(fileName, (error, content) => { |
|
||||||
if (!error) { |
|
||||||
content = content || '{}' |
|
||||||
var metadata |
|
||||||
try { |
|
||||||
metadata = JSON.parse(content) |
|
||||||
} catch (e) { |
|
||||||
console.log(e) |
|
||||||
} |
|
||||||
|
|
||||||
var deploy = metadata.deploy || {} |
|
||||||
self.networks.forEach((network) => { |
|
||||||
deploy[network] = self._syncContext(contract, deploy[network] || {}) |
|
||||||
}) |
|
||||||
|
|
||||||
let parsedMetadata |
|
||||||
try { |
|
||||||
parsedMetadata = JSON.parse(contract.object.metadata) |
|
||||||
} catch (e) { |
|
||||||
console.log(e) |
|
||||||
} |
|
||||||
if (parsedMetadata) provider.set(metadataFileName, JSON.stringify(parsedMetadata, null, '\t')) |
|
||||||
|
|
||||||
var data = { |
|
||||||
deploy, |
|
||||||
data: { |
|
||||||
bytecode: contract.object.evm.bytecode, |
|
||||||
deployedBytecode: contract.object.evm.deployedBytecode, |
|
||||||
gasEstimates: contract.object.evm.gasEstimates, |
|
||||||
methodIdentifiers: contract.object.evm.methodIdentifiers |
|
||||||
}, |
|
||||||
abi: contract.object.abi |
|
||||||
} |
|
||||||
|
|
||||||
provider.set(fileName, JSON.stringify(data, null, '\t')) |
|
||||||
} |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
_syncContext (contract, metadata) { |
|
||||||
var linkReferences = metadata.linkReferences |
|
||||||
var autoDeployLib = metadata.autoDeployLib |
|
||||||
if (!linkReferences) linkReferences = {} |
|
||||||
if (autoDeployLib === undefined) autoDeployLib = true |
|
||||||
|
|
||||||
for (var libFile in contract.object.evm.bytecode.linkReferences) { |
|
||||||
if (!linkReferences[libFile]) linkReferences[libFile] = {} |
|
||||||
for (var lib in contract.object.evm.bytecode.linkReferences[libFile]) { |
|
||||||
if (!linkReferences[libFile][lib]) { |
|
||||||
linkReferences[libFile][lib] = '<address>' |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
metadata.linkReferences = linkReferences |
|
||||||
metadata.autoDeployLib = autoDeployLib |
|
||||||
return metadata |
|
||||||
} |
|
||||||
|
|
||||||
// TODO: is only called by dropdownLogic and can be moved there
|
|
||||||
deployMetadataOf (contractName, fileLocation) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
var provider |
|
||||||
let path |
|
||||||
if (fileLocation) { |
|
||||||
provider = this.fileManager.fileProviderOf(fileLocation) |
|
||||||
path = fileLocation.split('/') |
|
||||||
path.pop() |
|
||||||
path = path.join('/') |
|
||||||
} else { |
|
||||||
provider = this.fileManager.currentFileProvider() |
|
||||||
path = this.fileManager.currentPath() |
|
||||||
} |
|
||||||
|
|
||||||
if (provider) { |
|
||||||
this.blockchain.detectNetwork((err, { id, name } = {}) => { |
|
||||||
if (err) { |
|
||||||
console.log(err) |
|
||||||
reject(err) |
|
||||||
} else { |
|
||||||
var fileName = this._JSONFileName(path, contractName) |
|
||||||
provider.get(fileName, (error, content) => { |
|
||||||
if (error) return reject(error) |
|
||||||
if (!content) return resolve() |
|
||||||
try { |
|
||||||
var metadata = JSON.parse(content) |
|
||||||
metadata = metadata.deploy || {} |
|
||||||
return resolve(metadata[name + ':' + id] || metadata[name] || metadata[id] || metadata[name.toLowerCase() + ':' + id] || metadata[name.toLowerCase()]) |
|
||||||
} catch (e) { |
|
||||||
reject(e.message) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
} else { |
|
||||||
reject(new Error(`Please select the folder in the file explorer where the metadata of ${contractName} can be found`)) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
module.exports = CompilerMetadata |
|
@ -0,0 +1 @@ |
|||||||
|
{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] } |
@ -0,0 +1,3 @@ |
|||||||
|
# remix-core-plugin-core-plugin |
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev). |
@ -0,0 +1,12 @@ |
|||||||
|
{ |
||||||
|
"name": "@remix-project/core-plugin", |
||||||
|
"version": "0.0.1", |
||||||
|
"description": "This library was generated with [Nx](https://nx.dev).", |
||||||
|
"main": "index.js", |
||||||
|
"scripts": { |
||||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||||
|
}, |
||||||
|
"author": "Remix Team", |
||||||
|
"license": "ISC" |
||||||
|
} |
||||||
|
|
@ -0,0 +1,5 @@ |
|||||||
|
export { OffsetToLineColumnConverter } from './lib/offset-line-to-column-converter' |
||||||
|
export { CompilerMetadata } from './lib/compiler-metadata' |
||||||
|
export { FetchAndCompile } from './lib/compiler-fetch-and-compile' |
||||||
|
export { CompilerImports } from './lib/compiler-content-imports' |
||||||
|
export { CompilerArtefacts } from './lib/compiler-artefacts' |
@ -1,16 +1,17 @@ |
|||||||
'use strict' |
'use strict' |
||||||
import { Plugin } from '@remixproject/engine' |
import { Plugin } from '@remixproject/engine' |
||||||
import * as packageJson from '../../../../../package.json' |
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||||
import CompilerAbstract from './compiler-abstract' |
|
||||||
|
|
||||||
const profile = { |
const profile = { |
||||||
name: 'compilerArtefacts', |
name: 'compilerArtefacts', |
||||||
methods: [], |
methods: ['get', 'addResolvedContract'], |
||||||
events: [], |
events: [], |
||||||
version: packageJson.version |
version: '0.0.1' |
||||||
} |
} |
||||||
|
|
||||||
module.exports = class CompilerArtefacts extends Plugin { |
export class CompilerArtefacts extends Plugin { |
||||||
|
compilersArtefactsPerFile: any |
||||||
|
compilersArtefacts: any |
||||||
constructor () { |
constructor () { |
||||||
super(profile) |
super(profile) |
||||||
this.compilersArtefacts = {} |
this.compilersArtefacts = {} |
@ -0,0 +1,175 @@ |
|||||||
|
'use strict' |
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
import { RemixURLResolver } from '@remix-project/remix-url-resolver' |
||||||
|
const remixTests = require('@remix-project/remix-tests') |
||||||
|
const async = require('async') |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'contentImport', |
||||||
|
displayName: 'content import', |
||||||
|
version: '0.0.1', |
||||||
|
methods: ['resolve', 'resolveAndSave', 'isExternalUrl'] |
||||||
|
} |
||||||
|
|
||||||
|
export class CompilerImports extends Plugin { |
||||||
|
previouslyHandled: {} |
||||||
|
urlResolver: any |
||||||
|
constructor () { |
||||||
|
super(profile) |
||||||
|
this.urlResolver = new RemixURLResolver() |
||||||
|
this.previouslyHandled = {} // cache import so we don't make the request at each compilation.
|
||||||
|
} |
||||||
|
|
||||||
|
async setToken () { |
||||||
|
const protocol = typeof window !== 'undefined' && window.location.protocol |
||||||
|
const token = await this.call('settings', 'get', 'settings/gist-access-token') |
||||||
|
this.urlResolver.setGistToken(token, protocol) |
||||||
|
} |
||||||
|
|
||||||
|
isRelativeImport (url) { |
||||||
|
return /^([^/]+)/.exec(url) |
||||||
|
} |
||||||
|
|
||||||
|
isExternalUrl (url) { |
||||||
|
const handlers = this.urlResolver.getHandlers() |
||||||
|
return handlers.some(handler => handler.match(url)) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* resolve the content of @arg url. This only resolves external URLs. |
||||||
|
* |
||||||
|
* @param {String} url - external URL of the content. can be basically anything like raw HTTP, ipfs URL, github address etc... |
||||||
|
* @returns {Promise} - { content, cleanUrl, type, url } |
||||||
|
*/ |
||||||
|
resolve (url) { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
this.import(url, null, (error, content, cleanUrl, type, url) => { |
||||||
|
if (error) return reject(error) |
||||||
|
resolve({ content, cleanUrl, type, url }) |
||||||
|
}, null) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async import (url, force, loadingCb, cb) { |
||||||
|
if (typeof force !== 'boolean') { |
||||||
|
const temp = loadingCb |
||||||
|
loadingCb = force |
||||||
|
cb = temp |
||||||
|
force = false |
||||||
|
} |
||||||
|
if (!loadingCb) loadingCb = () => {} |
||||||
|
if (!cb) cb = () => {} |
||||||
|
|
||||||
|
var self = this |
||||||
|
if (force) delete this.previouslyHandled[url] |
||||||
|
var imported = this.previouslyHandled[url] |
||||||
|
if (imported) { |
||||||
|
return cb(null, imported.content, imported.cleanUrl, imported.type, url) |
||||||
|
} |
||||||
|
|
||||||
|
let resolved |
||||||
|
try { |
||||||
|
await this.setToken() |
||||||
|
resolved = await this.urlResolver.resolve(url) |
||||||
|
const { content, cleanUrl, type } = resolved |
||||||
|
self.previouslyHandled[url] = { |
||||||
|
content, |
||||||
|
cleanUrl, |
||||||
|
type |
||||||
|
} |
||||||
|
cb(null, content, cleanUrl, type, url) |
||||||
|
} catch (e) { |
||||||
|
return cb(new Error('not found ' + url)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
importExternal (url, targetPath, cb) { |
||||||
|
this.import(url, |
||||||
|
// TODO: handle this event
|
||||||
|
(loadingMsg) => { this.emit('message', loadingMsg) }, |
||||||
|
async (error, content, cleanUrl, type, url) => { |
||||||
|
if (error) return cb(error) |
||||||
|
try { |
||||||
|
const provider = await this.call('fileManager', 'getProviderOf', null) |
||||||
|
const path = targetPath || type + '/' + cleanUrl |
||||||
|
if (provider) provider.addExternal('.deps/' + path, content, url) |
||||||
|
} catch (err) { |
||||||
|
|
||||||
|
} |
||||||
|
cb(null, content) |
||||||
|
}, null) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* import the content of @arg url. |
||||||
|
* first look in the browser localstorage (browser explorer) or locahost explorer. if the url start with `browser/*` or `localhost/*` |
||||||
|
* then check if the @arg url is located in the localhost, in the node_modules or installed_contracts folder |
||||||
|
* then check if the @arg url match any external url |
||||||
|
* |
||||||
|
* @param {String} url - URL of the content. can be basically anything like file located in the browser explorer, in the localhost explorer, raw HTTP, github address etc... |
||||||
|
* @param {String} targetPath - (optional) internal path where the content should be saved to |
||||||
|
* @returns {Promise} - string content |
||||||
|
*/ |
||||||
|
resolveAndSave (url, targetPath) { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
if (url.indexOf('remix_tests.sol') !== -1) resolve(remixTests.assertLibCode) |
||||||
|
this.call('fileManager', 'getProviderOf', url).then((provider) => { |
||||||
|
if (provider) { |
||||||
|
if (provider.type === 'localhost' && !provider.isConnected()) { |
||||||
|
return reject(new Error(`file provider ${provider.type} not available while trying to resolve ${url}`)) |
||||||
|
} |
||||||
|
provider.exists(url).then(exist => { |
||||||
|
/* |
||||||
|
if the path is absolute and the file does not exist, we can stop here |
||||||
|
Doesn't make sense to try to resolve "localhost/node_modules/localhost/node_modules/<path>" and we'll end in an infinite loop. |
||||||
|
*/ |
||||||
|
if (!exist && url.startsWith('browser/')) return reject(new Error(`not found ${url}`)) |
||||||
|
if (!exist && url.startsWith('localhost/')) return reject(new Error(`not found ${url}`)) |
||||||
|
|
||||||
|
if (exist) { |
||||||
|
return provider.get(url, (error, content) => { |
||||||
|
if (error) return reject(error) |
||||||
|
resolve(content) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// try to resolve localhost modules (aka truffle imports) - e.g from the node_modules folder
|
||||||
|
this.call('fileManager', 'getProviderByName', 'localhost').then((localhostProvider) => { |
||||||
|
if (localhostProvider.isConnected()) { |
||||||
|
var splitted = /([^/]+)\/(.*)$/g.exec(url) |
||||||
|
return async.tryEach([ |
||||||
|
(cb) => { this.resolveAndSave('localhost/installed_contracts/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
||||||
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
|
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/installed_contracts/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }, |
||||||
|
(cb) => { this.resolveAndSave('localhost/node_modules/' + url, null).then((result) => cb(null, result)).catch((error) => cb(error.message)) }, |
||||||
|
// eslint-disable-next-line standard/no-callback-literal
|
||||||
|
(cb) => { if (!splitted) { cb('URL not parseable: ' + url) } else { this.resolveAndSave('localhost/node_modules/' + splitted[1] + '/contracts/' + splitted[2], null).then((result) => cb(null, result)).catch((error) => cb(error.message)) } }], |
||||||
|
(error, result) => { |
||||||
|
if (error) { |
||||||
|
return this.importExternal(url, targetPath, (error, content) => { |
||||||
|
if (error) return reject(error) |
||||||
|
resolve(content) |
||||||
|
}) |
||||||
|
} |
||||||
|
resolve(result) |
||||||
|
}) |
||||||
|
} |
||||||
|
this.importExternal(url, targetPath, (error, content) => { |
||||||
|
if (error) return reject(error) |
||||||
|
resolve(content) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}).catch(error => { |
||||||
|
return reject(error) |
||||||
|
}) |
||||||
|
} |
||||||
|
}).catch(() => { |
||||||
|
// fallback to just resolving the file, it won't be saved in file manager
|
||||||
|
return this.importExternal(url, targetPath, (error, content) => { |
||||||
|
if (error) return reject(error) |
||||||
|
resolve(content) |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,145 @@ |
|||||||
|
'use strict' |
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
import { CompilerAbstract } from '@remix-project/remix-solidity' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'compilerMetadata', |
||||||
|
methods: ['deployMetadataOf'], |
||||||
|
events: [], |
||||||
|
version: '0.0.1' |
||||||
|
} |
||||||
|
|
||||||
|
export class CompilerMetadata extends Plugin { |
||||||
|
networks: string[] |
||||||
|
innerPath: string |
||||||
|
constructor () { |
||||||
|
super(profile) |
||||||
|
this.networks = ['VM:-', 'main:1', 'ropsten:3', 'rinkeby:4', 'kovan:42', 'görli:5', 'Custom'] |
||||||
|
this.innerPath = 'artifacts' |
||||||
|
} |
||||||
|
|
||||||
|
_JSONFileName (path, contractName) { |
||||||
|
return this.joinPath(path, this.innerPath, contractName + '.json') |
||||||
|
} |
||||||
|
|
||||||
|
_MetadataFileName (path, contractName) { |
||||||
|
return this.joinPath(path, this.innerPath, contractName + '_metadata.json') |
||||||
|
} |
||||||
|
|
||||||
|
onActivation () { |
||||||
|
var self = this |
||||||
|
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data) => { |
||||||
|
if (!await this.call('settings', 'get', 'settings/generate-contract-metadata')) return |
||||||
|
const compiler = new CompilerAbstract(languageVersion, data, source) |
||||||
|
var path = self._extractPathOf(source.target) |
||||||
|
compiler.visitContracts((contract) => { |
||||||
|
if (contract.file !== source.target) return |
||||||
|
(async () => { |
||||||
|
const fileName = self._JSONFileName(path, contract.name) |
||||||
|
const content = await this.call('fileManager', 'exists', fileName) ? await this.call('fileManager', 'readFile', fileName) : null |
||||||
|
await this._setArtefacts(content, contract, path) |
||||||
|
})() |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
_extractPathOf (file) { |
||||||
|
var reg = /(.*)(\/).*/ |
||||||
|
var path = reg.exec(file) |
||||||
|
return path ? path[1] : '/' |
||||||
|
} |
||||||
|
|
||||||
|
async _setArtefacts (content, contract, path) { |
||||||
|
content = content || '{}' |
||||||
|
var metadata |
||||||
|
try { |
||||||
|
metadata = JSON.parse(content) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
var fileName = this._JSONFileName(path, contract.name) |
||||||
|
var metadataFileName = this._MetadataFileName(path, contract.name) |
||||||
|
|
||||||
|
var deploy = metadata.deploy || {} |
||||||
|
this.networks.forEach((network) => { |
||||||
|
deploy[network] = this._syncContext(contract, deploy[network] || {}) |
||||||
|
}) |
||||||
|
|
||||||
|
let parsedMetadata |
||||||
|
try { |
||||||
|
parsedMetadata = JSON.parse(contract.object.metadata) |
||||||
|
} catch (e) { |
||||||
|
console.log(e) |
||||||
|
} |
||||||
|
if (parsedMetadata) await this.call('fileManager', 'writeFile', metadataFileName, JSON.stringify(parsedMetadata, null, '\t')) |
||||||
|
|
||||||
|
var data = { |
||||||
|
deploy, |
||||||
|
data: { |
||||||
|
bytecode: contract.object.evm.bytecode, |
||||||
|
deployedBytecode: contract.object.evm.deployedBytecode, |
||||||
|
gasEstimates: contract.object.evm.gasEstimates, |
||||||
|
methodIdentifiers: contract.object.evm.methodIdentifiers |
||||||
|
}, |
||||||
|
abi: contract.object.abi |
||||||
|
} |
||||||
|
await this.call('fileManager', 'writeFile', fileName, JSON.stringify(data, null, '\t')) |
||||||
|
} |
||||||
|
|
||||||
|
_syncContext (contract, metadata) { |
||||||
|
var linkReferences = metadata.linkReferences |
||||||
|
var autoDeployLib = metadata.autoDeployLib |
||||||
|
if (!linkReferences) linkReferences = {} |
||||||
|
if (autoDeployLib === undefined) autoDeployLib = true |
||||||
|
|
||||||
|
for (var libFile in contract.object.evm.bytecode.linkReferences) { |
||||||
|
if (!linkReferences[libFile]) linkReferences[libFile] = {} |
||||||
|
for (var lib in contract.object.evm.bytecode.linkReferences[libFile]) { |
||||||
|
if (!linkReferences[libFile][lib]) { |
||||||
|
linkReferences[libFile][lib] = '<address>' |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
metadata.linkReferences = linkReferences |
||||||
|
metadata.autoDeployLib = autoDeployLib |
||||||
|
return metadata |
||||||
|
} |
||||||
|
|
||||||
|
async deployMetadataOf (contractName, fileLocation) { |
||||||
|
let path |
||||||
|
if (fileLocation) { |
||||||
|
path = fileLocation.split('/') |
||||||
|
path.pop() |
||||||
|
path = path.join('/') |
||||||
|
} else { |
||||||
|
try { |
||||||
|
path = this._extractPathOf(await this.call('fileManager', 'getCurrentFile')) |
||||||
|
} catch (err) { |
||||||
|
console.log(err) |
||||||
|
throw new Error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
try { |
||||||
|
const { id, name } = await this.call('network', 'detectNetwork') |
||||||
|
const fileName = this._JSONFileName(path, contractName) |
||||||
|
try { |
||||||
|
const content = await this.call('fileManager', 'readFile', fileName) |
||||||
|
if (!content) return null |
||||||
|
let metadata = JSON.parse(content) |
||||||
|
metadata = metadata.deploy || {} |
||||||
|
return metadata[name + ':' + id] || metadata[name] || metadata[id] || metadata[name.toLowerCase() + ':' + id] || metadata[name.toLowerCase()] |
||||||
|
} catch (err) { |
||||||
|
return null |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
console.log(err) |
||||||
|
throw new Error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
joinPath (...paths) { |
||||||
|
paths = paths.filter((value) => value !== '').map((path) => path.replace(/^\/|\/$/g, '')) // remove first and last slash)
|
||||||
|
if (paths.length === 1) return paths[0] |
||||||
|
return paths.join('/') |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
'use strict' |
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
|
||||||
|
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'offsetToLineColumnConverter', |
||||||
|
methods: ['offsetToLineColumn'], |
||||||
|
events: [], |
||||||
|
version: '0.0.1' |
||||||
|
} |
||||||
|
|
||||||
|
export class OffsetToLineColumnConverter extends Plugin { |
||||||
|
lineBreakPositionsByContent: {} |
||||||
|
sourceMappingDecoder: any |
||||||
|
constructor () { |
||||||
|
super(profile) |
||||||
|
this.lineBreakPositionsByContent = {} |
||||||
|
this.sourceMappingDecoder = sourceMappingDecoder |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert offset representation with line/column representation. |
||||||
|
* This is also used to resolve the content: |
||||||
|
* @arg file is the index of the file in the content sources array and content sources array does have filename as key and not index. |
||||||
|
* So we use the asts (which references both index and filename) to look up the actual content targeted by the @arg file index. |
||||||
|
* @param {{start, length}} rawLocation - offset location |
||||||
|
* @param {number} file - The index where to find the source in the sources parameters |
||||||
|
* @param {Object.<string, {content}>} sources - Map of content sources |
||||||
|
* @param {Object.<string, {ast, id}>} asts - Map of content sources |
||||||
|
*/ |
||||||
|
offsetToLineColumn (rawLocation, file, sources, asts) { |
||||||
|
if (!this.lineBreakPositionsByContent[file]) { |
||||||
|
const sourcesArray = Object.keys(sources) |
||||||
|
if (!asts || (file === 0 && sourcesArray.length === 1)) { |
||||||
|
// if we don't have ast, we process the only one available content (applicable also for compiler older than 0.4.12)
|
||||||
|
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[sourcesArray[0]].content) |
||||||
|
} else { |
||||||
|
for (var filename in asts) { |
||||||
|
const source = asts[filename] |
||||||
|
if (source.id === file) { |
||||||
|
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(sources[filename].content) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert offset representation with line/column representation. |
||||||
|
* @param {{start, length}} rawLocation - offset location |
||||||
|
* @param {number} file - The index where to find the source in the sources parameters |
||||||
|
* @param {string} content - source |
||||||
|
*/ |
||||||
|
offsetToLineColumnWithContent (rawLocation, file, content) { |
||||||
|
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(content) |
||||||
|
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Clear the cache |
||||||
|
*/ |
||||||
|
clear () { |
||||||
|
this.lineBreakPositionsByContent = {} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* called by plugin API |
||||||
|
*/ |
||||||
|
activate () { |
||||||
|
this.on('solidity', 'compilationFinished', () => { |
||||||
|
this.clear() |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
"extends": "../../tsconfig.json", |
||||||
|
"files": [], |
||||||
|
"include": [], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.lib.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
{ |
||||||
|
"extends": "./tsconfig.json", |
||||||
|
"compilerOptions": { |
||||||
|
"module": "commonjs", |
||||||
|
"outDir": "../../dist/out-tsc", |
||||||
|
"declaration": true, |
||||||
|
"rootDir": "./src", |
||||||
|
"types": ["node"] |
||||||
|
}, |
||||||
|
"exclude": ["**/*.spec.ts"], |
||||||
|
"include": ["**/*.ts"] |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
'use strict' |
||||||
|
import txHelper from './txHelper' |
||||||
|
|
||||||
|
export class CompilerAbstract { |
||||||
|
languageversion: any |
||||||
|
data: any |
||||||
|
source: any |
||||||
|
constructor (languageversion, data, source) { |
||||||
|
this.languageversion = languageversion |
||||||
|
this.data = data |
||||||
|
this.source = source // source code
|
||||||
|
} |
||||||
|
|
||||||
|
getContracts () { |
||||||
|
return this.data.contracts |
||||||
|
} |
||||||
|
|
||||||
|
getContract (name) { |
||||||
|
return txHelper.getContract(name, this.data.contracts) |
||||||
|
} |
||||||
|
|
||||||
|
visitContracts (calllback) { |
||||||
|
return txHelper.visitContracts(this.data.contracts, calllback) |
||||||
|
} |
||||||
|
|
||||||
|
getData () { |
||||||
|
return this.data |
||||||
|
} |
||||||
|
|
||||||
|
getAsts () { |
||||||
|
return this.data.sources // ast
|
||||||
|
} |
||||||
|
|
||||||
|
getSourceName (fileIndex) { |
||||||
|
if (this.data && this.data.sources) { |
||||||
|
return Object.keys(this.data.sources)[fileIndex] |
||||||
|
} else if (Object.keys(this.source.sources).length === 1) { |
||||||
|
// if we don't have ast, we return the only one filename present.
|
||||||
|
const sourcesArray = Object.keys(this.source.sources) |
||||||
|
return sourcesArray[0] |
||||||
|
} |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
getSourceCode () { |
||||||
|
return this.source |
||||||
|
} |
||||||
|
} |
@ -1,7 +1,7 @@ |
|||||||
'use strict' |
'use strict' |
||||||
import { canUseWorker, urlFromVersion } from './compiler-utils' |
import { canUseWorker, urlFromVersion } from './compiler-utils' |
||||||
import { Compiler } from '@remix-project/remix-solidity' |
import { CompilerAbstract } from './compiler-abstract' |
||||||
import CompilerAbstract from './compiler-abstract' |
import { Compiler } from './compiler' |
||||||
|
|
||||||
export const compile = async (compilationTargets, settings, contentResolverCallback) => { |
export const compile = async (compilationTargets, settings, contentResolverCallback) => { |
||||||
const res = await (() => { |
const res = await (() => { |
@ -1,3 +1,6 @@ |
|||||||
export { Compiler } from './compiler/compiler' |
export { Compiler } from './compiler/compiler' |
||||||
|
export { compile } from './compiler/compiler-helpers' |
||||||
export { default as CompilerInput } from './compiler/compiler-input' |
export { default as CompilerInput } from './compiler/compiler-input' |
||||||
|
export { CompilerAbstract } from './compiler/compiler-abstract' |
||||||
export * from './compiler/types' |
export * from './compiler/types' |
||||||
|
export * from './compiler/compiler-utils' |
||||||
|
Loading…
Reference in new issue