Merge pull request #3285 from ethereum/foundry

fix remixd
pull/3301/head
bunsenstraat 2 years ago committed by GitHub
commit 25e0d6b8be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .circleci/config.yml
  2. 136
      libs/remixd/src/services/foundryClient.ts
  3. 72
      libs/remixd/src/services/hardhatClient.ts
  4. 104
      libs/remixd/src/services/truffleClient.ts

@ -316,4 +316,4 @@ workflows:
branches: branches:
only: remix_beta only: remix_beta
# VS Code Extension Version: 1.5.0 # VS Code Extension Version: 1.5.1

@ -14,13 +14,20 @@ export class FoundryClient extends PluginClient {
warnlog: boolean warnlog: boolean
buildPath: string buildPath: string
cachePath: string cachePath: string
logTimeout: NodeJS.Timeout
processingTimeout: NodeJS.Timeout
constructor (private readOnly = false) { constructor(private readOnly = false) {
super() super()
this.methods = ['compile', 'sync'] this.methods = ['compile', 'sync']
this.onActivation = () => {
console.log('Foundry plugin activated')
this.call('terminal', 'log', { type: 'log', value: 'Foundry plugin activated' })
this.startListening()
}
} }
setWebSocket (websocket: WS): void { setWebSocket(websocket: WS): void {
this.websocket = websocket this.websocket = websocket
this.websocket.addEventListener('close', () => { this.websocket.addEventListener('close', () => {
this.warnlog = false this.warnlog = false
@ -28,14 +35,37 @@ export class FoundryClient extends PluginClient {
}) })
} }
sharedFolder (currentSharedFolder: string): void { sharedFolder(currentSharedFolder: string): void {
this.currentSharedFolder = currentSharedFolder this.currentSharedFolder = currentSharedFolder
this.buildPath = utils.absolutePath('out', this.currentSharedFolder) this.buildPath = utils.absolutePath('out', this.currentSharedFolder)
this.cachePath = utils.absolutePath('cache', this.currentSharedFolder) this.cachePath = utils.absolutePath('cache', this.currentSharedFolder)
this.listenOnFoundryCompilation()
} }
compile (configPath: string) { startListening() {
if (fs.existsSync(this.buildPath) && fs.existsSync(this.cachePath)) {
this.listenOnFoundryCompilation()
} else {
this.listenOnFoundryFolder()
}
}
listenOnFoundryFolder() {
console.log('Foundry out folder doesn\'t exist... waiting for the compilation.')
try {
if(this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true })
// watch for new folders
this.watcher.on('addDir', () => {
if (fs.existsSync(this.buildPath) && fs.existsSync(this.cachePath)) {
this.listenOnFoundryCompilation()
}
})
} catch (e) {
console.log(e)
}
}
compile() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.readOnly) { if (this.readOnly) {
const errMsg = '[Foundry Compilation]: Cannot compile in read-only mode' const errMsg = '[Foundry Compilation]: Cannot compile in read-only mode'
@ -62,48 +92,70 @@ export class FoundryClient extends PluginClient {
}) })
} }
private async processArtifact () { checkPath() {
if (!fs.existsSync(this.buildPath) || !fs.existsSync(this.cachePath)) {
this.listenOnFoundryFolder()
return false
}
if (!fs.existsSync(join(this.cachePath, 'solidity-files-cache.json'))) return false
return true
}
private async processArtifact() {
if (!this.checkPath()) return
const folderFiles = await fs.readdir(this.buildPath) // "out" folder const folderFiles = await fs.readdir(this.buildPath) // "out" folder
const cache = JSON.parse(await fs.readFile(join(this.cachePath, 'solidity-files-cache.json'), { encoding: 'utf-8' })) try {
const cache = JSON.parse(await fs.readFile(join(this.cachePath, 'solidity-files-cache.json'), { encoding: 'utf-8' }))
// name of folders are file names // name of folders are file names
for (const file of folderFiles) { for (const file of folderFiles) {
const path = join(this.buildPath, file) // out/Counter.sol/ const path = join(this.buildPath, file) // out/Counter.sol/
const compilationResult = { const compilationResult = {
input: {}, input: {},
output: { output: {
contracts: {}, contracts: {},
sources: {} sources: {}
}, },
solcVersion: null, solcVersion: null,
compilationTarget: null compilationTarget: null
}
await this.readContract(path, compilationResult, cache)
this.emit('compilationFinished', compilationResult.compilationTarget, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion)
} }
await this.readContract(path, compilationResult, cache)
this.emit('compilationFinished', compilationResult.compilationTarget, { sources: compilationResult.input } , 'soljson', compilationResult.output, compilationResult.solcVersion) clearTimeout(this.logTimeout)
} this.logTimeout = setTimeout(() => {
if (!this.warnlog) { // @ts-ignore
// @ts-ignore this.call('terminal', 'log', { type: 'log', value: `receiving compilation result from Foundry` })
this.call('terminal', 'log', { type: 'log', value: 'receiving compilation result from Foundry' }) console.log('Syncing compilation result from Foundry')
this.warnlog = true }, 1000)
} catch (e) {
console.log(e)
} }
} }
listenOnFoundryCompilation () { async triggerProcessArtifact() {
try { // prevent multiple calls
clearTimeout(this.processingTimeout)
this.processingTimeout = setTimeout(async () => await this.processArtifact(), 1000)
}
listenOnFoundryCompilation() {
try {
if(this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.cachePath, { depth: 0, ignorePermissionErrors: true, ignoreInitial: true }) this.watcher = chokidar.watch(this.cachePath, { depth: 0, ignorePermissionErrors: true, ignoreInitial: true })
this.watcher.on('change', async () => await this.triggerProcessArtifact())
this.watcher.on('change', async (f: string) => this.processArtifact()) this.watcher.on('add', async () => await this.triggerProcessArtifact())
this.watcher.on('add', async (f: string) => this.processArtifact())
// process the artifact on activation // process the artifact on activation
setTimeout(() => this.processArtifact(), 1000) this.triggerProcessArtifact()
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async readContract (contractFolder, compilationResultPart, cache) { async readContract(contractFolder, compilationResultPart, cache) {
const files = await fs.readdir(contractFolder) const files = await fs.readdir(contractFolder)
for (const file of files) { for (const file of files) {
const path = join(contractFolder, file) const path = join(contractFolder, file)
const content = await fs.readFile(path, { encoding: 'utf-8' }) const content = await fs.readFile(path, { encoding: 'utf-8' })
@ -111,13 +163,13 @@ export class FoundryClient extends PluginClient {
} }
} }
async feedContractArtifactFile (path, content, compilationResultPart, cache) { async feedContractArtifactFile(path, content, compilationResultPart, cache) {
const contentJSON = JSON.parse(content) const contentJSON = JSON.parse(content)
const contractName = basename(path).replace('.json', '') const contractName = basename(path).replace('.json', '')
const currentCache = cache.files[contentJSON.ast.absolutePath] const currentCache = cache.files[contentJSON.ast.absolutePath]
if (!currentCache.artifacts[contractName]) return if (!currentCache.artifacts[contractName]) return
// extract source and version // extract source and version
const metadata = contentJSON.metadata const metadata = contentJSON.metadata
if (metadata.compiler && metadata.compiler.version) { if (metadata.compiler && metadata.compiler.version) {
@ -141,7 +193,7 @@ export class FoundryClient extends PluginClient {
console.log('\x1b[32m%s\x1b[0m', 'sources input not found, please update Foundry to the latest version.') console.log('\x1b[32m%s\x1b[0m', 'sources input not found, please update Foundry to the latest version.')
} }
compilationResultPart.compilationTarget = contentJSON.ast.absolutePath compilationResultPart.compilationTarget = contentJSON.ast.absolutePath
// extract data // extract data
if (!compilationResultPart.output['sources'][contentJSON.ast.absolutePath]) compilationResultPart.output['sources'][contentJSON.ast.absolutePath] = {} if (!compilationResultPart.output['sources'][contentJSON.ast.absolutePath]) compilationResultPart.output['sources'][contentJSON.ast.absolutePath] = {}
@ -163,10 +215,8 @@ export class FoundryClient extends PluginClient {
} }
} }
async sync () { async sync() {
console.log('syncing from Foundry') console.log('syncing Foundry with Remix...')
this.processArtifact() this.processArtifact()
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'synced with Foundry'})
} }
} }

@ -13,10 +13,17 @@ export class HardhatClient extends PluginClient {
watcher: chokidar.FSWatcher watcher: chokidar.FSWatcher
warnLog: boolean warnLog: boolean
buildPath: string buildPath: string
logTimeout: NodeJS.Timeout
processingTimeout: NodeJS.Timeout
constructor(private readOnly = false) { constructor(private readOnly = false) {
super() super()
this.methods = ['compile', 'sync'] this.methods = ['compile', 'sync']
this.onActivation = () => {
console.log('Hardhat plugin activated')
this.call('terminal', 'log', { type: 'log', value: 'Hardhat plugin activated' })
this.startListening()
}
} }
setWebSocket(websocket: WS): void { setWebSocket(websocket: WS): void {
@ -30,14 +37,15 @@ export class HardhatClient extends PluginClient {
sharedFolder(currentSharedFolder: string): void { sharedFolder(currentSharedFolder: string): void {
this.currentSharedFolder = currentSharedFolder this.currentSharedFolder = currentSharedFolder
this.buildPath = utils.absolutePath('artifacts/contracts', this.currentSharedFolder) this.buildPath = utils.absolutePath('artifacts/contracts', this.currentSharedFolder)
if(fs.existsSync(this.buildPath)) { }
startListening() {
if (fs.existsSync(this.buildPath)) {
this.listenOnHardhatCompilation() this.listenOnHardhatCompilation()
}else{ } else {
console.log('Hardhat artifacts folder doesn\'t exist... waiting for the first compilation.')
console.log('If you are using Hardhat, run `npx hardhat compile` or run the compilation with `Enable Hardhat Compilation` checked from the Remix IDE.') console.log('If you are using Hardhat, run `npx hardhat compile` or run the compilation with `Enable Hardhat Compilation` checked from the Remix IDE.')
this.listenOnHardHatFolder() this.listenOnHardHatFolder()
} }
} }
compile(configPath: string) { compile(configPath: string) {
@ -67,7 +75,16 @@ export class HardhatClient extends PluginClient {
}) })
} }
checkPath() {
if (!fs.existsSync(this.buildPath)) {
this.listenOnHardHatFolder()
return false
}
return true
}
private async processArtifact() { private async processArtifact() {
if (!this.checkPath()) return
// resolving the files // resolving the files
const folderFiles = await fs.readdir(this.buildPath) const folderFiles = await fs.readdir(this.buildPath)
const targetsSynced = [] const targetsSynced = []
@ -111,23 +128,32 @@ export class HardhatClient extends PluginClient {
} }
} }
} }
if (!this.warnLog) {
this.call('terminal', 'log', { value: 'receiving compilation result from Hardhat', type: 'log'} ) clearTimeout(this.logTimeout)
this.warnLog = true this.logTimeout = setTimeout(() => {
} this.call('terminal', 'log', { value: 'receiving compilation result from Hardhat', type: 'log' })
if (targetsSynced.length) { if (targetsSynced.length) {
console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`) console.log(`Processing artifacts for files: ${[...new Set(targetsSynced)].join(', ')}`)
this.call('terminal', 'log', { type: 'log', value: `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}` }) // @ts-ignore
} this.call('terminal', 'log', { type: 'log', value: `synced with Hardhat: ${[...new Set(targetsSynced)].join(', ')}` })
} else {
console.log('No artifacts to process')
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'No artifacts from Hardhat to process' })
}
}, 1000)
} }
listenOnHardHatFolder() { listenOnHardHatFolder() {
console.log('Hardhat artifacts folder doesn\'t exist... waiting for the compilation.')
try { try {
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true }) if(this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 2, ignorePermissionErrors: true, ignoreInitial: true })
// watch for new folders // watch for new folders
this.watcher.on('addDir', (path) => { this.watcher.on('addDir', () => {
if (path.endsWith('artifacts/contracts')) { if (fs.existsSync(this.buildPath)) {
this.buildPath = path
this.listenOnHardhatCompilation() this.listenOnHardhatCompilation()
} }
}) })
@ -136,15 +162,21 @@ export class HardhatClient extends PluginClient {
} }
} }
async triggerProcessArtifact() {
// prevent multiple calls
clearTimeout(this.processingTimeout)
this.processingTimeout = setTimeout(async () => await this.processArtifact(), 1000)
}
listenOnHardhatCompilation() { listenOnHardhatCompilation() {
try { try {
console.log('listening on Hardhat compilation...') console.log('listening on Hardhat compilation...')
if(this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.buildPath, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true }) this.watcher = chokidar.watch(this.buildPath, { depth: 1, ignorePermissionErrors: true, ignoreInitial: true })
this.watcher.on('change', async () => await this.triggerProcessArtifact())
this.watcher.on('change', () => this.processArtifact()) this.watcher.on('add', async () => await this.triggerProcessArtifact())
this.watcher.on('add', () => this.processArtifact())
// process the artifact on activation // process the artifact on activation
setTimeout(() => this.processArtifact(), 1000) this.processArtifact()
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }

@ -13,13 +13,20 @@ export class TruffleClient extends PluginClient {
watcher: chokidar.FSWatcher watcher: chokidar.FSWatcher
warnLog: boolean warnLog: boolean
buildPath: string buildPath: string
logTimeout: NodeJS.Timeout
processingTimeout: NodeJS.Timeout
constructor (private readOnly = false) { constructor(private readOnly = false) {
super() super()
this.methods = ['compile', 'sync'] this.methods = ['compile', 'sync']
this.onActivation = () => {
console.log('Truffle plugin activated')
this.call('terminal', 'log', { type: 'log', value: 'Truffle plugin activated' })
this.startListening()
}
} }
setWebSocket (websocket: WS): void { setWebSocket(websocket: WS): void {
this.websocket = websocket this.websocket = websocket
this.websocket.addEventListener('close', () => { this.websocket.addEventListener('close', () => {
this.warnLog = false this.warnLog = false
@ -27,13 +34,38 @@ export class TruffleClient extends PluginClient {
}) })
} }
sharedFolder (currentSharedFolder: string): void { sharedFolder(currentSharedFolder: string): void {
this.currentSharedFolder = currentSharedFolder this.currentSharedFolder = currentSharedFolder
this.buildPath = utils.absolutePath('build/contracts', this.currentSharedFolder) this.buildPath = utils.absolutePath('build/contracts', this.currentSharedFolder)
this.listenOnTruffleCompilation()
}
startListening() {
if (fs.existsSync(this.buildPath)) {
this.listenOnTruffleCompilation()
}
else {
this.listenOnTruffleFolder()
}
}
listenOnTruffleFolder() {
console.log('Truffle build folder doesn\'t exist... waiting for the compilation.')
try {
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.currentSharedFolder, { depth: 2, ignorePermissionErrors: true, ignoreInitial: true })
// watch for new folders
this.watcher.on('addDir', () => {
if (fs.existsSync(this.buildPath)) {
this.listenOnTruffleCompilation()
}
})
} catch (e) {
console.log(e)
}
} }
compile (configPath: string) { compile(configPath: string) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.readOnly) { if (this.readOnly) {
const errMsg = '[Truffle Compilation]: Cannot compile in read-only mode' const errMsg = '[Truffle Compilation]: Cannot compile in read-only mode'
@ -60,8 +92,19 @@ export class TruffleClient extends PluginClient {
}) })
} }
private async processArtifact () { checkPath() {
const folderFiles = await fs.readdir(this.buildPath) if (!fs.existsSync(this.buildPath)) {
this.listenOnTruffleFolder()
return false
}
return true
}
private async processArtifact() {
if (!this.checkPath()) return
const folderFiles = await fs.readdir(this.buildPath)
const filesFound = folderFiles.filter(file => file.endsWith('.json'))
// name of folders are file names // name of folders are file names
for (const file of folderFiles) { for (const file of folderFiles) {
if (file.endsWith('.json')) { if (file.endsWith('.json')) {
@ -79,27 +122,39 @@ export class TruffleClient extends PluginClient {
this.emit('compilationFinished', compilationResult.compilationTarget, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion) this.emit('compilationFinished', compilationResult.compilationTarget, { sources: compilationResult.input }, 'soljson', compilationResult.output, compilationResult.solcVersion)
} }
} }
if (!this.warnLog) { clearTimeout(this.logTimeout)
// @ts-ignore this.logTimeout = setTimeout(() => {
this.call('terminal', 'log', { type: 'log', value: 'receiving compilation result from Truffle' }) if (filesFound.length === 0) {
this.warnLog = true // @ts-ignore
} this.call('terminal', 'log', { value: 'No contract found in the Truffle build folder', type: 'log' })
} else {
// @ts-ignore
this.call('terminal', 'log', { value: 'receiving compilation result from Truffle', type: 'log' })
console.log('Syncing compilation result from Truffle')
}
}, 1000)
} }
listenOnTruffleCompilation () { async triggerProcessArtifact() {
try { // prevent multiple calls
clearTimeout(this.processingTimeout)
this.processingTimeout = setTimeout(async () => await this.processArtifact(), 1000)
}
listenOnTruffleCompilation() {
try {
if (this.watcher) this.watcher.close()
this.watcher = chokidar.watch(this.buildPath, { depth: 3, ignorePermissionErrors: true, ignoreInitial: true }) this.watcher = chokidar.watch(this.buildPath, { depth: 3, ignorePermissionErrors: true, ignoreInitial: true })
this.watcher.on('change', async () => await this.triggerProcessArtifact())
this.watcher.on('change', async (f: string) => this.processArtifact()) this.watcher.on('add', async () => await this.triggerProcessArtifact())
this.watcher.on('add', async (f: string) => this.processArtifact())
// process the artifact on activation // process the artifact on activation
setTimeout(() => this.processArtifact(), 1000) this.triggerProcessArtifact()
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
} }
async feedContractArtifactFile (path, content, compilationResultPart) { async feedContractArtifactFile(path, content, compilationResultPart) {
const contentJSON = JSON.parse(content) const contentJSON = JSON.parse(content)
const contractName = basename(path).replace('.json', '') const contractName = basename(path).replace('.json', '')
compilationResultPart.solcVersion = contentJSON.compiler.version compilationResultPart.solcVersion = contentJSON.compiler.version
@ -110,10 +165,10 @@ export class TruffleClient extends PluginClient {
// extract data // extract data
const relPath = utils.relativePath(filepath, this.currentSharedFolder) const relPath = utils.relativePath(filepath, this.currentSharedFolder)
if (!compilationResultPart.output['sources'][relPath]) compilationResultPart.output['sources'][relPath] = {} if (!compilationResultPart.output['sources'][relPath]) compilationResultPart.output['sources'][relPath] = {}
const location = contentJSON.ast.src.split(':') const location = contentJSON.ast.src.split(':')
const id = parseInt(location[location.length - 1]) const id = parseInt(location[location.length - 1])
compilationResultPart.output['sources'][relPath] = { compilationResultPart.output['sources'][relPath] = {
ast: contentJSON.ast, ast: contentJSON.ast,
id id
@ -137,10 +192,7 @@ export class TruffleClient extends PluginClient {
} }
} }
async sync () { async sync() {
console.log('syncing from Truffle')
this.processArtifact() this.processArtifact()
// @ts-ignore
this.call('terminal', 'log', { type: 'log', value: 'synced with Truffle' })
} }
} }

Loading…
Cancel
Save