basic switch to ts

types introduced

suggested changes and worker types

more types

more types and code style update

fix to run compilation very first time after plugin activation
pull/7/head
aniket-engg 5 years ago committed by Aniket
parent 1f74b65149
commit 355a02ea3b
  1. 7
      remix-solidity/index.js
  2. 2
      remix-solidity/index.ts
  3. 797
      remix-solidity/package-lock.json
  4. 13
      remix-solidity/package.json
  5. 12
      remix-solidity/src/compiler/compiler-input.ts
  6. 36
      remix-solidity/src/compiler/compiler-worker.ts
  7. 390
      remix-solidity/src/compiler/compiler.js
  8. 411
      remix-solidity/src/compiler/compiler.ts
  9. 33
      remix-solidity/src/compiler/txHelper.js
  10. 40
      remix-solidity/src/compiler/txHelper.ts
  11. 481
      remix-solidity/src/compiler/types.ts
  12. 25
      remix-solidity/tsconfig.json

@ -1,7 +0,0 @@
const Compiler = require('./src/compiler/compiler')
const CompilerInput = require('./src/compiler/compiler-input')
module.exports = {
Compiler: Compiler,
CompilerInput: CompilerInput
}

@ -0,0 +1,2 @@
export { Compiler } from './src/compiler/compiler'
export { default as CompilerInput} from './src/compiler/compiler-input'

File diff suppressed because it is too large Load Diff

@ -2,6 +2,8 @@
"name": "remix-solidity", "name": "remix-solidity",
"version": "0.3.21", "version": "0.3.21",
"description": "Ethereum IDE and tools for the web", "description": "Ethereum IDE and tools for the web",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"contributors": [ "contributors": [
{ {
"name": "Yann Levreau", "name": "Yann Levreau",
@ -12,7 +14,6 @@
"email": "liana@ethdev.com" "email": "liana@ethdev.com"
} }
], ],
"main": "./index.js",
"dependencies": { "dependencies": {
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"remix-lib": "0.4.18", "remix-lib": "0.4.18",
@ -26,19 +27,21 @@
"@babel/preset-es2015": "latest", "@babel/preset-es2015": "latest",
"@babel/preset-es2017": "latest", "@babel/preset-es2017": "latest",
"@babel/preset-stage-0": "^7.0.0", "@babel/preset-stage-0": "^7.0.0",
"@types/node": "^13.1.1",
"babel-eslint": "^10.0.0", "babel-eslint": "^10.0.0",
"babelify": "^10.0.0", "babelify": "^10.0.0",
"standard": "^7.0.1", "standard": "^7.0.1",
"tape": "^4.6.0" "tape": "^4.6.0",
"typescript": "^3.7.4"
}, },
"scripts": { "scripts": {
"test": "standard && tape ./test/tests.js" "build": "tsc",
"test": "tsc && tape ./test/tests.js"
}, },
"standard": { "standard": {
"ignore": [ "ignore": [
"node_modules/*" "node_modules/*"
], ]
"parser": "babel-eslint"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

@ -1,7 +1,9 @@
'use strict' 'use strict'
module.exports = (sources, opts) => { import { CompilerInput, Source, CompilerInputOptions } from './types'
const o = {
export default (sources: Source, opts: CompilerInputOptions) => {
const o: CompilerInput = {
language: 'Solidity', language: 'Solidity',
sources: sources, sources: sources,
settings: { settings: {
@ -25,9 +27,9 @@ module.exports = (sources, opts) => {
o.language = opts.language o.language = opts.language
} }
if (opts.language === 'Yul' && o.settings.optimizer.enabled) { if (opts.language === 'Yul' && o.settings.optimizer.enabled) {
// details key is valid only for Yul if (!o.settings.optimizer.details)
if (!o.settings.optimizer.details) o.settings.optimizer.details = {} o.settings.optimizer.details = {}
o.settings.optimizer.details['yul'] = true o.settings.optimizer.details.yul = true
} }
return JSON.stringify(o) return JSON.stringify(o)
} }

@ -1,26 +1,23 @@
'use strict' 'use strict'
const solc = require('solc/wrapper') import solc from 'solc/wrapper'
import { CompilerInput, MessageToWorker } from './types'
let compileJSON = function () { return '' } var compileJSON: ((input: CompilerInput) => string) | null = (input) => { return '' }
const missingInputs = [] var missingInputs: string[] = []
module.exports = function (self) { export default (self) => {
self.addEventListener('message', (e) => { self.addEventListener('message', function (e) {
const data = e.data const data: MessageToWorker = e.data
switch (data.cmd) { switch (data.cmd) {
case 'loadVersion': case 'loadVersion':
delete self.Module delete self.Module
// NOTE: workaround some browsers? // NOTE: workaround some browsers?
self.Module = undefined self.Module = undefined
compileJSON = null compileJSON = null
//importScripts() method of synchronously imports one or more scripts into the worker's scope
self.importScripts(data.data) self.importScripts(data.data)
let compiler: solc = solc(self.Module)
const compiler = solc(self.Module) compileJSON = (input) => {
compileJSON = function (input) {
try { try {
let missingInputsCallback = function (path) { let missingInputsCallback = function (path) {
missingInputs.push(path) missingInputs.push(path)
@ -31,15 +28,22 @@ module.exports = function (self) {
return JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception }) return JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception })
} }
} }
self.postMessage({ self.postMessage({
cmd: 'versionLoaded', cmd: 'versionLoaded',
data: compiler.version() data: compiler.version()
}) })
break break
case 'compile': case 'compile':
missingInputs.length = 0 missingInputs.length = 0
self.postMessage({cmd: 'compiled', job: data.job, data: compileJSON(data.input), missingInputs: missingInputs}) if(data.input && compileJSON) {
self.postMessage({
cmd: 'compiled',
job: data.job,
data: compileJSON(data.input),
missingInputs: missingInputs
})
}
break break
} }
}, false) }, false)

@ -1,390 +0,0 @@
'use strict'
const solc = require('solc/wrapper')
const solcABI = require('solc/abi')
const webworkify = require('webworkify')
const compilerInput = require('./compiler-input')
const remixLib = require('remix-lib')
const EventManager = remixLib.EventManager
const txHelper = require('./txHelper')
/*
trigger compilationFinished, compilerLoaded, compilationStarted, compilationDuration
*/
function Compiler (handleImportCall) {
var self = this
this.event = new EventManager()
let compileJSON
let worker = null
let currentVersion
let optimize = false
let evmVersion = null
let language = 'Solidity'
this.setOptimize = function (_optimize) {
optimize = _optimize
}
this.setEvmVersion = function (_evmVersion) {
evmVersion = _evmVersion
}
this.setLanguage = function (_language) {
language = _language
}
let compilationStartTime = null
this.event.register('compilationFinished', (success, data, source) => {
if (success && compilationStartTime) {
this.event.trigger('compilationDuration', [(new Date().getTime()) - compilationStartTime])
}
compilationStartTime = null
})
this.event.register('compilationStarted', () => {
compilationStartTime = new Date().getTime()
})
const internalCompile = (files, target, missingInputs) => {
gatherImports(files, target, missingInputs, (error, input) => {
if (error) {
this.lastCompilationResult = null
this.event.trigger('compilationFinished', [false, {'error': { formattedMessage: error, severity: 'error' }}, files])
} else {
compileJSON(input)
}
})
}
const compile = function (files, target) {
self.event.trigger('compilationStarted', [])
internalCompile(files, target)
}
this.compile = compile
function setCompileJSON (_compileJSON) {
compileJSON = _compileJSON
}
this.setCompileJSON = setCompileJSON // this is exposed for testing
function onCompilerLoaded (version) {
currentVersion = version
self.event.trigger('compilerLoaded', [version])
}
function onInternalCompilerLoaded () {
if (worker === null) {
let compiler
if (typeof (window) === 'undefined') {
compiler = require('solc')
} else {
compiler = solc(window.Module)
}
compileJSON = function (source) {
const missingInputs = []
const missingInputsCallback = function (path) {
missingInputs.push(path)
return { error: 'Deferred import' }
}
let result
try {
const input = compilerInput(source.sources, {optimize: optimize, evmVersion: evmVersion, language: language, target: source.target})
result = compiler.compile(input, { import: missingInputsCallback })
result = JSON.parse(result)
} catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
}
compilationFinished(result, missingInputs, source)
}
onCompilerLoaded(compiler.version())
}
}
// exposed for use in node
this.onInternalCompilerLoaded = onInternalCompilerLoaded
this.lastCompilationResult = {
data: null,
source: null
}
/**
* return the contract obj of the given @arg name. Uses last compilation result.
* return null if not found
* @param {String} name - contract name
* @returns contract obj and associated file: { contract, file } or null
*/
this.getContract = (name) => {
if (this.lastCompilationResult.data && this.lastCompilationResult.data.contracts) {
return txHelper.getContract(name, this.lastCompilationResult.data.contracts)
}
return null
}
/**
* call the given @arg cb (function) for all the contracts. Uses last compilation result
* @param {Function} cb - callback
*/
this.visitContracts = (cb) => {
if (this.lastCompilationResult.data && this.lastCompilationResult.data.contracts) {
return txHelper.visitContracts(this.lastCompilationResult.data.contracts, cb)
}
return null
}
/**
* return the compiled contracts from the last compilation result
* @return {Object} - contracts
*/
this.getContracts = () => {
if (this.lastCompilationResult.data && this.lastCompilationResult.data.contracts) {
return this.lastCompilationResult.data.contracts
}
return null
}
/**
* return the sources from the last compilation result
* @param {Object} cb - map of sources
*/
this.getSources = () => {
if (this.lastCompilationResult.source) {
return this.lastCompilationResult.source.sources
}
return null
}
/**
* return the sources @arg fileName from the last compilation result
* @param {Object} cb - map of sources
*/
this.getSource = (fileName) => {
if (this.lastCompilationResult.source) {
return this.lastCompilationResult.source.sources[fileName]
}
return null
}
/**
* return the source from the last compilation result that has the given index. null if source not found
* @param {Int} index - index of the source
*/
this.getSourceName = (index) => {
if (this.lastCompilationResult.data && this.lastCompilationResult.data.sources) {
return Object.keys(this.lastCompilationResult.data.sources)[index]
}
return null
}
function compilationFinished (data, missingInputs, source) {
let noFatalErrors = true // ie warnings are ok
function isValidError (error) {
// The deferred import is not a real error
// FIXME: maybe have a better check?
if (/Deferred import/.exec(error.message)) {
return false
}
return error.severity !== 'warning'
}
if (data['error'] !== undefined) {
// Ignore warnings (and the 'Deferred import' error as those are generated by us as a workaround
if (isValidError(data['error'])) {
noFatalErrors = false
}
}
if (data['errors'] !== undefined) {
data['errors'].forEach(function (err) {
// Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround
if (isValidError(err)) {
noFatalErrors = false
}
})
}
if (!noFatalErrors) {
// There are fatal errors - abort here
self.lastCompilationResult = null
self.event.trigger('compilationFinished', [false, data, source])
} else if (missingInputs !== undefined && missingInputs.length > 0) {
// try compiling again with the new set of inputs
internalCompile(source.sources, source.target, missingInputs)
} else {
data = updateInterface(data)
self.lastCompilationResult = {
data: data,
source: source
}
self.event.trigger('compilationFinished', [true, data, source])
}
}
// TODO: needs to be changed to be more node friendly
this.loadVersion = (usingWorker, url) => {
console.log('Loading ' + url + ' ' + (usingWorker ? 'with worker' : 'without worker'))
this.event.trigger('loadingCompiler', [url, usingWorker])
if (worker !== null) {
worker.terminate()
worker = null
}
if (usingWorker) {
loadWorker(url)
} else {
loadInternal(url)
}
}
function loadInternal (url) {
delete window.Module
// NOTE: workaround some browsers?
window.Module = undefined
// Set a safe fallback until the new one is loaded
setCompileJSON(function (source) {
compilationFinished({ error: { formattedMessage: 'Compiler not yet loaded.' } })
})
const newScript = document.createElement('script')
newScript.type = 'text/javascript'
newScript.src = url
document.getElementsByTagName('head')[0].appendChild(newScript)
const check = window.setInterval(function () {
if (!window.Module) {
return
}
window.clearInterval(check)
onInternalCompilerLoaded()
}, 200)
}
function loadWorker (url) {
worker = webworkify(require('./compiler-worker.js'))
const jobs = []
worker.addEventListener('message', function (msg) {
const data = msg.data
switch (data.cmd) {
case 'versionLoaded':
onCompilerLoaded(data.data)
break
case 'compiled':
let result
try {
result = JSON.parse(data.data)
} catch (exception) {
result = { 'error': 'Invalid JSON output from the compiler: ' + exception }
}
let sources = {}
if (data.job in jobs !== undefined) {
sources = jobs[data.job].sources
delete jobs[data.job]
}
compilationFinished(result, data.missingInputs, sources)
break
}
})
worker.addEventListener('error', function (msg) {
compilationFinished({ error: 'Worker error: ' + msg.data })
})
compileJSON = function (source) {
jobs.push({sources: source})
worker.postMessage({cmd: 'compile', job: jobs.length - 1, input: compilerInput(source.sources,
{optimize: optimize, evmVersion: evmVersion, language: language, target: source.target})})
}
worker.postMessage({cmd: 'loadVersion', data: url})
}
function gatherImports (files, target, importHints, cb) {
importHints = importHints || []
// FIXME: This will only match imports if the file begins with one.
// It should tokenize by lines and check each.
// eslint-disable-next-line no-useless-escape
let importRegex = /^\s*import\s*[\'\"]([^\'\"]+)[\'\"];/g
for (var fileName in files) {
let match
while ((match = importRegex.exec(files[fileName].content))) {
let importFilePath = match[1]
if (importFilePath.startsWith('./')) {
const path = /(.*\/).*/.exec(fileName)
if (path !== null) {
importFilePath = importFilePath.replace('./', path[1])
} else {
importFilePath = importFilePath.slice(2)
}
}
// FIXME: should be using includes or sets, but there's also browser compatibility..
if (importHints.indexOf(importFilePath) === -1) {
importHints.push(importFilePath)
}
}
}
while (importHints.length > 0) {
let m = importHints.pop()
if (m in files) {
continue
}
if (handleImportCall) {
handleImportCall(m, function (err, content) {
if (err) {
cb(err)
} else {
files[m] = { content }
gatherImports(files, target, importHints, cb)
}
})
}
return
}
cb(null, { 'sources': files, 'target': target })
}
function truncateVersion (version) {
const tmp = /^(\d+.\d+.\d+)/.exec(version)
if (tmp) {
return tmp[1]
}
return version
}
function updateInterface (data) {
txHelper.visitContracts(data.contracts, (contract) => {
if (!contract.object.abi) contract.object.abi = []
if (language === 'Yul' && contract.object.abi.length === 0) {
// yul compiler does not return any abi,
// we default to accept the fallback function (which expect raw data as argument).
contract.object.abi.push({
'payable': true,
'stateMutability': 'payable',
'type': 'fallback'
})
}
data.contracts[contract.file][contract.name].abi = solcABI.update(truncateVersion(currentVersion), contract.object.abi)
})
return data
}
}
module.exports = Compiler

@ -0,0 +1,411 @@
'use strict'
import { update } from 'solc/abi'
import webworkify from 'webworkify'
import compilerInput from './compiler-input'
import { EventManager } from 'remix-lib'
import { default as txHelper } from './txHelper';
import { Source, SourceWithTarget, MessageFromWorker, CompilerState, CompilationResult,
visitContractsCallbackParam, visitContractsCallbackInterface, CompilationError,
gatherImportsCallbackInterface } from './types'
/*
trigger compilationFinished, compilerLoaded, compilationStarted, compilationDuration
*/
export class Compiler {
event: EventManager
state: CompilerState
constructor (public handleImportCall: (fileurl: string, cb: Function) => void) {
this.event = new EventManager()
this.state = {
compileJSON: null,
worker: null,
currentVersion: null,
optimize: false,
evmVersion: null,
language: 'Solidity',
compilationStartTime: null,
lastCompilationResult: {
data: null,
source: null
}
}
this.event.register('compilationFinished', (success: boolean, data: CompilationResult, source: SourceWithTarget) => {
if (success && this.state.compilationStartTime) {
this.event.trigger('compilationDuration', [(new Date().getTime()) - this.state.compilationStartTime])
}
this.state.compilationStartTime = null
})
this.event.register('compilationStarted', () => {
this.state.compilationStartTime = new Date().getTime()
})
}
/**
* @dev Setter function for CompilerState's properties (used by IDE)
* @param key key
* @param value value of key in CompilerState
*/
set <K extends keyof CompilerState>(key: K, value: CompilerState[K]) {
this.state[key] = value
}
/**
* @dev Internal function to compile the contract after gathering imports
* @param files source file
* @param missingInputs missing import file path list
*/
internalCompile (files: Source, missingInputs?: string[]): void {
this.gatherImports(files, missingInputs, (error, input) => {
if (error) {
this.state.lastCompilationResult = null
this.event.trigger('compilationFinished', [false, {'error': { formattedMessage: error, severity: 'error' }}, files])
} else if(this.state.compileJSON && input)
this.state.compileJSON(input)
})
}
/**
* @dev Compile source files (used by IDE)
* @param files source files
* @param target target file name (This is passed as it is to IDE)
*/
compile (files: Source, target: string): void {
this.state.target = target
this.event.trigger('compilationStarted', [])
this.internalCompile(files, target)
}
/**
* @dev Called when compiler is loaded, set current compiler version
* @param version compiler version
*/
onCompilerLoaded (version: string): void {
this.state.currentVersion = version
this.event.trigger('compilerLoaded', [version])
}
/**
* @dev Called when compiler is loaded internally (without worker)
*/
onInternalCompilerLoaded (): void {
if (this.state.worker === null) {
const compiler: any = typeof (window) === 'undefined' ? require('solc') : require('solc/wrapper')(window['Module'])
this.state.compileJSON = (source: SourceWithTarget) => {
let missingInputs: string[] = []
const missingInputsCallback = (path: string) => {
missingInputs.push(path)
return { error: 'Deferred import' }
}
let result: CompilationResult = {}
try {
if(source && source.sources) {
const input = compilerInput(source.sources, {optimize: this.state.optimize, evmVersion: this.state.evmVersion, language: this.state.language})
result = JSON.parse(compiler.compile(input, { import: missingInputsCallback }))
}
} catch (exception) {
result = { error: { formattedMessage: 'Uncaught JavaScript exception:\n' + exception, severity: 'error', mode: 'panic' } }
}
this.onCompilationFinished(result, missingInputs, source)
}
this.onCompilerLoaded(compiler.version())
}
}
/**
* @dev Called when compilation is finished
* @param data compilation result data
* @param missingInputs missing imports
* @param source Source
*/
onCompilationFinished (data: CompilationResult, missingInputs?: string[], source?: SourceWithTarget): void {
let noFatalErrors: boolean = true // ie warnings are ok
const checkIfFatalError = (error: CompilationError) => {
// Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround
const isValidError = (error.message && error.message === 'Deferred import') ? false : error.severity !== 'warning'
if(isValidError) noFatalErrors = false
}
if (data.error) checkIfFatalError(data.error)
if (data.errors) data.errors.forEach((err) => checkIfFatalError(err))
if (!noFatalErrors) {
// There are fatal errors, abort here
this.state.lastCompilationResult = null
this.event.trigger('compilationFinished', [false, data, source])
} else if (missingInputs !== undefined && missingInputs.length > 0) {
// try compiling again with the new set of inputs
this.internalCompile(source.sources, missingInputs)
} else {
data = this.updateInterface(data)
if(source)
{
source.target = this.state.target;
this.state.lastCompilationResult = {
data: data,
source: source
}
}
this.event.trigger('compilationFinished', [true, data, source])
}
}
/**
* @dev Load compiler using given URL (used by IDE)
* @param usingWorker if true, load compiler using worker
* @param url URL to load compiler from
*/
loadVersion (usingWorker: boolean, url: string): void {
console.log('Loading ' + url + ' ' + (usingWorker ? 'with worker' : 'without worker'))
this.event.trigger('loadingCompiler', [url, usingWorker])
if (this.state.worker) {
this.state.worker.terminate()
this.state.worker = null
}
if (usingWorker) {
this.loadWorker(url)
} else {
this.loadInternal(url)
}
}
/**
* @dev Load compiler using 'script' element (without worker)
* @param url URL to load compiler from
*/
loadInternal (url: string): void {
delete window['Module']
// NOTE: workaround some browsers?
window['Module'] = undefined
// Set a safe fallback until the new one is loaded
this.state.compileJSON = (source: SourceWithTarget) => {
this.onCompilationFinished({ error: { formattedMessage: 'Compiler not yet loaded.' } })
}
let newScript: HTMLScriptElement = document.createElement('script')
newScript.type = 'text/javascript'
newScript.src = url
document.getElementsByTagName('head')[0].appendChild(newScript)
const check: number = window.setInterval(() => {
if (!window['Module']) {
return
}
window.clearInterval(check)
this.onInternalCompilerLoaded()
}, 200)
}
/**
* @dev Load compiler using web worker
* @param url URL to load compiler from
*/
loadWorker (url: string): void {
this.state.worker = webworkify(require('./compiler-worker.js').default)
let jobs: Record<'sources', SourceWithTarget> [] = []
this.state.worker.addEventListener('message', (msg: Record <'data', MessageFromWorker>) => {
const data: MessageFromWorker = msg.data
switch (data.cmd) {
case 'versionLoaded':
if(data.data) this.onCompilerLoaded(data.data)
break
case 'compiled':
let result: CompilationResult
if(data.data && data.job !== undefined && data.job >= 0) {
try {
result = JSON.parse(data.data)
} catch (exception) {
result = { error : { formattedMessage: 'Invalid JSON output from the compiler: ' + exception }}
}
let sources: SourceWithTarget = {}
if (data.job in jobs !== undefined) {
sources = jobs[data.job].sources
delete jobs[data.job]
}
this.onCompilationFinished(result, data.missingInputs, sources)
}
break
}
})
this.state.worker.addEventListener('error', (msg: Record <'data', MessageFromWorker>) => {
this.onCompilationFinished({ error: { formattedMessage: 'Worker error: ' + msg.data }})
})
this.state.compileJSON = (source: SourceWithTarget) => {
if(source && source.sources) {
jobs.push({sources: source})
this.state.worker.postMessage({
cmd: 'compile',
job: jobs.length - 1,
input: compilerInput(source.sources, {
optimize: this.state.optimize,
evmVersion: this.state.evmVersion,
language: this.state.language
})
})
}
}
this.state.worker.postMessage({
cmd: 'loadVersion',
data: url
})
}
/**
* @dev Gather imports for compilation
* @param files file sources
* @param importHints import file list
* @param cb callback
*/
gatherImports (files: Source, importHints?: string[], cb?: gatherImportsCallbackInterface): void {
importHints = importHints || []
// FIXME: This will only match imports if the file begins with one '.'
// It should tokenize by lines and check each.
const importRegex: RegExp = /^\s*import\s*[\'\"]([^\'\"]+)[\'\"];/g
for (const fileName in files) {
let match: RegExpExecArray | null
while ((match = importRegex.exec(files[fileName].content))) {
let importFilePath = match[1]
if (importFilePath.startsWith('./')) {
const path: RegExpExecArray | null = /(.*\/).*/.exec(fileName)
importFilePath = path ? importFilePath.replace('./', path[1]) : importFilePath.slice(2)
}
if (!importHints.includes(importFilePath)) importHints.push(importFilePath)
}
}
while (importHints.length > 0) {
const m: string = importHints.pop() as string
if (m && m in files) continue
if (this.handleImportCall) {
this.handleImportCall(m, (err, content: string) => {
if (err && cb) cb(err)
else {
files[m] = { content }
this.gatherImports(files, importHints, cb)
}
})
}
return
}
if(cb)
cb(null, { 'sources': files })
}
/**
* @dev Truncate version string
* @param version version
*/
truncateVersion (version: string): string {
const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version)
return tmp ? tmp[1] : version
}
/**
* @dev Update ABI according to current compiler version
* @param data Compilation result
*/
updateInterface (data: CompilationResult) : CompilationResult {
txHelper.visitContracts(data.contracts, (contract : visitContractsCallbackParam) => {
if (!contract.object.abi) contract.object.abi = []
if (this.state.language === 'Yul' && contract.object.abi.length === 0) {
// yul compiler does not return any abi,
// we default to accept the fallback function (which expect raw data as argument).
contract.object.abi.push({
'payable': true,
'stateMutability': 'payable',
'type': 'fallback'
})
}
if(data && data.contracts && this.state.currentVersion)
data.contracts[contract.file][contract.name].abi = update(this.truncateVersion(this.state.currentVersion), contract.object.abi)
})
return data
}
/**
* @dev Get contract obj of the given contract name from last compilation result.
* @param name contract name
*/
getContract (name: string): Record<string, any> | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return txHelper.getContract(name, this.state.lastCompilationResult.data.contracts)
}
return null
}
/**
* @dev Call the given callback for all the contracts from last compilation result
* @param cb callback
*/
visitContracts (cb: visitContractsCallbackInterface) : void | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return txHelper.visitContracts(this.state.lastCompilationResult.data.contracts, cb)
}
return null
}
/**
* @dev Get the compiled contracts data from last compilation result
*/
getContracts () : CompilationResult['contracts'] | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) {
return this.state.lastCompilationResult.data.contracts
}
return null
}
/**
* @dev Get sources from last compilation result
*/
getSources () : Source | null | undefined {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.source) {
return this.state.lastCompilationResult.source.sources
}
return null
}
/**
* @dev Get sources of passed file name from last compilation result
* @param fileName file name
*/
getSource (fileName: string) : Source['filename'] | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.source && this.state.lastCompilationResult.source.sources) {
return this.state.lastCompilationResult.source.sources[fileName]
}
return null
}
/**
* @dev Get source name at passed index from last compilation result
* @param index - index of the source
*/
getSourceName (index: number): string | null {
if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.sources) {
return Object.keys(this.state.lastCompilationResult.data.sources)[index]
}
return null
}
}

@ -1,33 +0,0 @@
'use strict'
module.exports = {
/**
* return the contract obj of the given @arg name. Uses last compilation result.
* return null if not found
* @param {String} name - contract name
* @returns contract obj and associated file: { contract, file } or null
*/
getContract: (contractName, contracts) => {
for (let file in contracts) {
if (contracts[file][contractName]) {
return { object: contracts[file][contractName], file: file }
}
}
return null
},
/**
* call the given @arg cb (function) for all the contracts. Uses last compilation result
* stop visiting when cb return true
* @param {Function} cb - callback
*/
visitContracts: (contracts, cb) => {
for (let file in contracts) {
for (let name in contracts[file]) {
if (cb({ name: name, object: contracts[file][name], file: file })) return
}
}
}
}

@ -0,0 +1,40 @@
'use strict'
import { CompilationResult, visitContractsCallbackParam, visitContractsCallbackInterface } from './types'
export default {
/**
* @dev Get contract obj of given contract name from last compilation result.
* @param name contract name
* @param contracts 'contracts' object from last compilation result
*/
getContract: (contractName: string, contracts: CompilationResult["contracts"]) : Record<string, any> | null => {
for (const file in contracts) {
if (contracts[file][contractName]) {
return { object: contracts[file][contractName], file: file }
}
}
return null
},
/**
* @dev call the given callback for all contracts from last compilation result, stop visiting when cb return true
* @param contracts - 'contracts' object from last compilation result
* @param cb - callback
*/
visitContracts: (contracts: CompilationResult["contracts"], cb: visitContractsCallbackInterface) : void => {
for (const file in contracts) {
for (const name in contracts[file]) {
const param: visitContractsCallbackParam = {
name: name,
object: contracts[file][name],
file: file
}
if (cb(param)) return
}
}
}
}

@ -0,0 +1,481 @@
export interface CompilerInput {
// Required: Source code language. Currently supported are "Solidity" and "Yul".
language: Language,
// Required
sources: Source,
// Optional
settings:
{
// Optional: Sorted list of remappings
remappings?: string[],
// Optional: Optimizer settings
optimizer: {
// disabled by default
enabled: boolean,
// Optimize for how many times you intend to run the code.
// Lower values will optimize more for initial deployment cost, higher
// values will optimize more for high-frequency usage.
runs: number,
// Switch optimizer components on or off in detail.
// The "enabled" switch above provides two defaults which can be
// tweaked here. If "details" is given, "enabled" can be omitted.
details?: {
// The peephole optimizer is always on if no details are given,
// use details to switch it off.
peephole?: boolean,
// The unused jumpdest remover is always on if no details are given,
// use details to switch it off.
jumpdestRemover?: boolean,
// Sometimes re-orders literals in commutative operations.
orderLiterals?: boolean,
// Removes duplicate code blocks
deduplicate?: boolean,
// Common subexpression elimination, this is the most complicated step but
// can also provide the largest gain.
cse?: boolean,
// Optimize representation of literal numbers and strings in code.
constantOptimizer?: boolean,
// The new Yul optimizer. Mostly operates on the code of ABIEncoderV2.
// It can only be activated through the details here.
yul?: boolean,
// Tuning options for the Yul optimizer.
yulDetails?: {
// Improve allocation of stack slots for variables, can free up stack slots early.
// Activated by default if the Yul optimizer is activated.
stackAllocation: boolean
}
}
},
// Version of the EVM to compile for.
// Affects type checking and code generation.
evmVersion?: EVMVersion,
// Optional: Debugging settings
debug?: {
// How to treat revert (and require) reason strings. Settings are
// "default", "strip", "debug" and "verboseDebug".
// "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
// "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects
// "debug" injects strings for compiler-generated internal reverts (not yet implemented)
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
revertStrings: 'default' | 'strip' | 'debug' | 'verboseDebug'
}
// Metadata settings (optional)
metadata?: {
// Use only literal content and not URLs (false by default)
useLiteralContent: boolean,
// Use the given hash method for the metadata hash that is appended to the bytecode.
// The metadata hash can be removed from the bytecode via option "none".
// The other options are "ipfs" and "bzzr1".
// If the option is omitted, "ipfs" is used by default.
bytecodeHash: 'ipfs' | 'bzzr1' | 'none'
},
// Addresses of the libraries. If not all libraries are given here,
// it can result in unlinked objects whose output data is different.
libraries?: {
// The top level key is the the name of the source file where the library is used.
// If remappings are used, this source file should match the global path
// after remappings were applied.
// If this key is an empty string, that refers to a global level.
[fileName: string]: Record<string, string>
}
// The following can be used to select desired outputs based
// on file and contract names.
// If this field is omitted, then the compiler loads and does type checking,
// but will not generate any outputs apart from errors.
// The first level key is the file name and the second level key is the contract name.
// An empty contract name is used for outputs that are not tied to a contract
// but to the whole source file like the AST.
// A star as contract name refers to all contracts in the file.
// Similarly, a star as a file name matches all files.
// To select all outputs the compiler can possibly generate, use
// "outputSelection: { "*": { "*": [ "*" ], "": [ "*" ] } }"
// but note that this might slow down the compilation process needlessly.
//
// The available output types are as follows:
//
// File level (needs empty string as contract name):
// ast - AST of all source files
// legacyAST - legacy AST of all source files
//
// Contract level (needs the contract name or "*"):
// abi - ABI
// devdoc - Developer documentation (natspec)
// userdoc - User documentation (natspec)
// metadata - Metadata
// ir - Yul intermediate representation of the code before optimization
// irOptimized - Intermediate representation after optimization
// storageLayout - Slots, offsets and types of the contract's state variables.
// evm.assembly - New assembly format
// evm.legacyAssembly - Old-style assembly format in JSON
// evm.bytecode.object - Bytecode object
// evm.bytecode.opcodes - Opcodes list
// evm.bytecode.sourceMap - Source mapping (useful for debugging)
// evm.bytecode.linkReferences - Link references (if unlinked object)
// evm.deployedBytecode* - Deployed bytecode (has the same options as evm.bytecode)
// evm.methodIdentifiers - The list of function hashes
// evm.gasEstimates - Function gas estimates
// ewasm.wast - eWASM S-expressions format (not supported at the moment)
// ewasm.wasm - eWASM binary format (not supported at the moment)
//
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
// target part of that output. Additionally, `*` can be used as a wildcard to request everything.
//
outputSelection?: {
'*': {
'': [ 'legacyAST', 'ast' ],
'*': [ 'abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates' ]
}
}
}
}
export interface Source {
[fileName: string]:
{
// Optional: keccak256 hash of the source file
keccak256?: string,
// Required (unless "urls" is used): literal contents of the source file
content: string,
urls?: string[]
}
}
export interface CompilerInputOptions {
optimize: boolean | number,
libraries?: {
[fileName: string]: Record<string, string>
},
evmVersion?: EVMVersion,
language?: Language
}
export type EVMVersion = 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople' | 'petersburg' | 'istanbul' | 'berlin' | null
export type Language = 'Solidity' | 'Yul'
export interface CompilerState {
compileJSON: ((input: SourceWithTarget) => void) | null,
worker: any,
currentVersion: string| null| undefined,
optimize: boolean,
evmVersion: EVMVersion| null,
language: Language,
compilationStartTime: number| null,
target: string | null,
lastCompilationResult: {
data: CompilationResult | null,
source: SourceWithTarget | null | undefined
} | null
}
export interface SourceWithTarget {
sources?: Source,
target?: string | null | undefined
}
export interface MessageToWorker {
cmd: string,
job?: number,
input?: CompilerInput,
data?: string
}
export interface MessageFromWorker {
cmd: string,
job?: number,
missingInputs?: string[],
data?: string
}
export interface visitContractsCallbackParam {
name: string,
object: CompiledContract,
file: string
}
export interface visitContractsCallbackInterface {
(param: visitContractsCallbackParam): boolean | void
}
export interface gatherImportsCallbackInterface {
(err?: Error | null, result?: SourceWithTarget) : any
}
export interface CompilationResult {
error?: CompilationError,
/** not present if no errors/warnings were encountered */
errors?: CompilationError[]
/** This contains the file-level outputs. In can be limited/filtered by the outputSelection settings */
sources?: {
[contractName: string]: CompilationSource
}
/** This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings */
contracts?: {
/** If the language used has no contract names, this field should equal to an empty string. */
[fileName: string]: {
[contract: string]: CompiledContract
}
}
}
///////////
// ERROR //
///////////
export interface CompilationError {
/** Location within the source file */
sourceLocation?: {
file: string
start: number
end: number
}
/** Error type */
type?: CompilationErrorType
/** Component where the error originated, such as "general", "ewasm", etc. */
component?: 'general' | 'ewasm' | string
severity?: 'error' | 'warning'
message?: string
mode?: 'panic'
/** the message formatted with source location */
formattedMessage?: string
}
type CompilationErrorType =
| 'JSONError'
| 'IOError'
| 'ParserError'
| 'DocstringParsingError'
| 'SyntaxError'
| 'DeclarationError'
| 'TypeError'
| 'UnimplementedFeatureError'
| 'InternalCompilerError'
| 'Exception'
| 'CompilerError'
| 'FatalError'
| 'Warning'
////////////
// SOURCE //
////////////
export interface CompilationSource {
/** Identifier of the source (used in source maps) */
id: number
/** The AST object */
ast: AstNode
/** The legacy AST object */
legacyAST: AstNodeLegacy
}
/////////
// AST //
/////////
export interface AstNode {
absolutePath?: string
exportedSymbols?: Object
id: number
nodeType: string
nodes?: Array<AstNode>
src: string
literals?: Array<string>
file?: string
scope?: number
sourceUnit?: number
symbolAliases?: Array<string>
[x: string]: any
}
export interface AstNodeLegacy {
id: number
name: string
src: string
children?: Array<AstNodeLegacy>
attributes?: AstNodeAtt
}
export interface AstNodeAtt {
operator?: string
string?: null
type?: string
value?: string
constant?: boolean
name?: string
public?: boolean
exportedSymbols?: Object
argumentTypes?: null
absolutePath?: string
[x: string]: any
}
//////////////
// CONTRACT //
//////////////
export interface CompiledContract {
/** The Ethereum Contract ABI. If empty, it is represented as an empty array. */
abi: ABIDescription[]
// See the Metadata Output documentation (serialised JSON string)
metadata: string
/** User documentation (natural specification) */
userdoc: UserDocumentation
/** Developer documentation (natural specification) */
devdoc: DeveloperDocumentation
/** Intermediate representation (string) */
ir: string
/** EVM-related outputs */
evm: {
assembly: string
legacyAssembly: {}
/** Bytecode and related details. */
bytecode: BytecodeObject
deployedBytecode: BytecodeObject
/** The list of function hashes */
methodIdentifiers: {
[functionIdentifier: string]: string
}
// Function gas estimates
gasEstimates: {
creation: {
codeDepositCost: string
executionCost: 'infinite' | string
totalCost: 'infinite' | string
}
external: {
[functionIdentifier: string]: string
}
internal: {
[functionIdentifier: string]: 'infinite' | string
}
}
}
/** eWASM related outputs */
ewasm: {
/** S-expressions format */
wast: string
/** Binary format (hex string) */
wasm: string
}
}
/////////
// ABI //
/////////
export type ABIDescription = FunctionDescription | EventDescription
export interface FunctionDescription {
/** Type of the method. default is 'function' */
type?: 'function' | 'constructor' | 'fallback' | 'receive'
/** The name of the function. Constructor and fallback function never have name */
name?: string
/** List of parameters of the method. Fallback function doesn’t have inputs. */
inputs?: ABIParameter[]
/** List of the outputs parameters for the method, if any */
outputs?: ABIParameter[]
/** State mutability of the method */
stateMutability: 'pure' | 'view' | 'nonpayable' | 'payable'
/** true if function accepts Ether, false otherwise. Default is false */
payable?: boolean
/** true if function is either pure or view, false otherwise. Default is false */
constant?: boolean
}
export interface EventDescription {
type: 'event'
name: string
inputs: ABIParameter &
{
/** true if the field is part of the log’s topics, false if it one of the log’s data segment. */
indexed: boolean
}[]
/** true if the event was declared as anonymous. */
anonymous: boolean
}
export interface ABIParameter {
/** The name of the parameter */
name: string
/** The canonical type of the parameter */
type: ABITypeParameter
/** Used for tuple types */
components?: ABIParameter[]
}
export type ABITypeParameter =
| 'uint'
| 'uint[]' // TODO : add <M>
| 'int'
| 'int[]' // TODO : add <M>
| 'address'
| 'address[]'
| 'bool'
| 'bool[]'
| 'fixed'
| 'fixed[]' // TODO : add <M>
| 'ufixed'
| 'ufixed[]' // TODO : add <M>
| 'bytes'
| 'bytes[]' // TODO : add <M>
| 'function'
| 'function[]'
| 'tuple'
| 'tuple[]'
| string // Fallback
///////////////////////////
// NATURAL SPECIFICATION //
///////////////////////////
// Userdoc
export interface UserDocumentation {
methods: UserMethodList
notice: string
}
export type UserMethodList = {
[functionIdentifier: string]: UserMethodDoc
} & {
'constructor'?: string
}
export interface UserMethodDoc {
notice: string
}
// Devdoc
export interface DeveloperDocumentation {
author: string
title: string
details: string
methods: DevMethodList
}
export interface DevMethodList {
[functionIdentifier: string]: DevMethodDoc
}
export interface DevMethodDoc {
author: string
details: string
return: string
params: {
[param: string]: string
}
}
//////////////
// BYTECODE //
//////////////
export interface BytecodeObject {
/** The bytecode as a hex string. */
object: string
/** Opcodes list */
opcodes: string
/** The source mapping as a string. See the source mapping definition. */
sourceMap: string
/** If given, this is an unlinked object. */
linkReferences?: {
[contractName: string]: {
/** Byte offsets into the bytecode. */
[library: string]: { start: number; length: number }[]
}
}
}

@ -0,0 +1,25 @@
{
"include": ["src", "index.ts"],
"compilerOptions": {
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": ["dom", "es2018"], /* Specify library files to be included in the compilation. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
/* Module Resolution Options */
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
"paths": { "remix-solidity": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"typeRoots": [
"./@types",
"./node_modules/@types"
],
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
/* Experimental Options */
"experimentalDecorators": false, /* Enables experimental support for ES7 decorators. */
}
}
Loading…
Cancel
Save