From 910b7ab13584b29f1387814f07a73a0898afa870 Mon Sep 17 00:00:00 2001 From: yann300 Date: Sun, 13 Nov 2022 12:49:05 +0100 Subject: [PATCH] add solidity script --- apps/remix-ide/src/app.js | 7 +- .../src/app/plugins/solidity-script.tsx | 82 +++++++++++++++++++ apps/remix-ide/src/app/tabs/compile-tab.js | 7 +- apps/remix-ide/src/blockchain/blockchain.js | 2 +- apps/remix-ide/src/remixAppManager.js | 2 +- .../src/compiler/compiler-helpers.ts | 28 +++---- .../src/compiler/compiler-worker.ts | 73 +++++++++++++++++ libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 5 +- 8 files changed, 184 insertions(+), 22 deletions(-) create mode 100644 apps/remix-ide/src/app/plugins/solidity-script.tsx create mode 100644 libs/remix-solidity/src/compiler/compiler-worker.ts diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index 8155a709ab..5c8abaf428 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -16,6 +16,7 @@ import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin import { AstWalker } from '@remix-project/remix-astwalker' import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin' import { CodeParser } from './app/plugins/parser/code-parser' +import { SolidityScript } from './app/plugins/solidity-script' import { WalkthroughService } from './walkthroughService' @@ -246,7 +247,7 @@ class AppComponent { ) const codeParser = new CodeParser(new AstWalker()) - + const solidityScript = new SolidityScript() this.notification = new NotificationPlugin() @@ -298,7 +299,8 @@ class AppComponent { this.walkthroughService, search, solidityumlgen, - contractFlattener + contractFlattener, + solidityScript ]) // LAYOUT & SYSTEM VIEWS @@ -414,6 +416,7 @@ class AppComponent { await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder']) + await this.appManager.activatePlugin(['solidity-script']) this.appManager.on( 'filePanel', diff --git a/apps/remix-ide/src/app/plugins/solidity-script.tsx b/apps/remix-ide/src/app/plugins/solidity-script.tsx new file mode 100644 index 0000000000..40493a37d8 --- /dev/null +++ b/apps/remix-ide/src/app/plugins/solidity-script.tsx @@ -0,0 +1,82 @@ +import React from 'react' // eslint-disable-line +import { format } from 'util' +import { Plugin } from '@remixproject/engine' +import { compile } from '@remix-project/remix-solidity' +import { TransactionConfig } from 'web3-core' +const _paq = window._paq = window._paq || [] //eslint-disable-line + +const profile = { + name: 'solidity-script', + displayName: 'solidity-script', + description: 'solidity-script', + methods: ['execute'] +} + +export class SolidityScript extends Plugin { + constructor () { + super(profile) + } + + async execute (path: string) { + _paq.push(['trackEvent', 'SolidityScript', 'execute', 'script']) + let content = await this.call('fileManager', 'readFile', path) + const params = await this.call('solidity', 'getCompilerParameters') + content = ` + import "hardhat/console.sol"; + contract SolidityScript { + ${content} + }` + const targets = { 'script.sol': { content } } + + // compile + const compiler = await this.call('solidity', 'getCompiler') + const compilation = await compile(targets, params, async (url, cb) => { + await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message)) + }, compiler.state.worker) + + // get the vm provider + const contract = compilation.getContract('SolidityScript') + const bytecode = '0x' + contract.object.evm.bytecode.object + const web3 = await this.call('blockchain', 'web3VM') + const accounts = await this.call('blockchain', 'getAccounts') + if (!accounts || accounts.length === 0) { + throw new Error('no account available') + } + + // deploy the contract + let tx: TransactionConfig = { + from: accounts[0], + data: bytecode + } + const receipt = await web3.eth.sendTransaction(tx) + tx = { + from: accounts[0], + to: receipt.contractAddress, + data: '0x504917d1' + } + const receiptCall = await web3.eth.sendTransaction(tx) + + const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash) + + if (hhlogs && hhlogs.length) { + let finalLogs =
console.log:
+ { + hhlogs.map((log) => { + let formattedLog + // Hardhat implements the same formatting options that can be found in Node.js' console.log, + // which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args + // For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6' + // We check first arg to determine if 'util.format' is needed + if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) { + formattedLog = format(log[0], ...log.slice(1)) + } else { + formattedLog = log.join(' ') + } + return
{formattedLog}
+ })} +
+ _paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log']) + this.call('terminal', 'logHtml', finalLogs) + } + } +} diff --git a/apps/remix-ide/src/app/tabs/compile-tab.js b/apps/remix-ide/src/app/tabs/compile-tab.js index a43e71ca4b..d7fdc499e9 100644 --- a/apps/remix-ide/src/app/tabs/compile-tab.js +++ b/apps/remix-ide/src/app/tabs/compile-tab.js @@ -21,7 +21,7 @@ const profile = { documentation: 'https://remix-ide.readthedocs.io/en/latest/compile.html', version: packageJson.version, maintainedBy: 'Remix', - methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState'] + methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState', 'getCompilerParameters', 'getCompiler'] } // EditorApi: @@ -133,8 +133,13 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA } } + getCompiler () { + return this.compileTabLogic.compiler + } + getCompilerParameters () { const params = this.queryParams.get() + params.evmVersion = params.evmVersion === 'null' || params.evmVersion === 'undefined' ? null : params.evmVersion params.optimize = (params.optimize === 'false' || params.optimize === null || params.optimize === undefined) ? false : params.optimize params.optimize = params.optimize === 'true' ? true : params.optimize return params diff --git a/apps/remix-ide/src/blockchain/blockchain.js b/apps/remix-ide/src/blockchain/blockchain.js index 49367096ab..d3ee2cd90b 100644 --- a/apps/remix-ide/src/blockchain/blockchain.js +++ b/apps/remix-ide/src/blockchain/blockchain.js @@ -23,7 +23,7 @@ const profile = { name: 'blockchain', displayName: 'Blockchain', description: 'Blockchain - Logic', - methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'web3VM', 'getProvider'], + methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'getProvider'], version: packageJson.version } diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 2665ec8e3d..59a13e819b 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views 'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-trustwallet', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin', - 'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener', 'doc-gen', 'doc-viewer'] + 'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener', 'doc-gen', 'doc-viewer', 'solidity-script'] // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither'] diff --git a/libs/remix-solidity/src/compiler/compiler-helpers.ts b/libs/remix-solidity/src/compiler/compiler-helpers.ts index b7f5b7a0d4..6957cc9beb 100644 --- a/libs/remix-solidity/src/compiler/compiler-helpers.ts +++ b/libs/remix-solidity/src/compiler/compiler-helpers.ts @@ -3,21 +3,17 @@ import { canUseWorker, urlFromVersion } from './compiler-utils' import { CompilerAbstract } from './compiler-abstract' import { Compiler } from './compiler' -export const compile = async (compilationTargets, settings, contentResolverCallback) => { - const res = await (() => { - return new Promise((resolve, reject) => { - console.log('compilationTargets', compilationTargets) - const compiler = new Compiler(contentResolverCallback) - compiler.set('evmVersion', settings.evmVersion) - compiler.set('optimize', settings.optimize) - compiler.set('language', settings.language) - compiler.set('runs', settings.runs) - compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version)) - compiler.event.register('compilationFinished', (success, compilationData, source, input, version) => { - resolve(new CompilerAbstract(settings.version, compilationData, source, input)) - }) - compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, '')) +export const compile = (compilationTargets, settings, contentResolverCallback, worker?: any): Promise => { + return new Promise((resolve, reject) => { + const compiler = new Compiler(contentResolverCallback) + compiler.set('evmVersion', settings.evmVersion) + compiler.set('optimize', settings.optimize) + compiler.set('language', settings.language) + compiler.set('runs', settings.runs) + compiler.loadVersion(canUseWorker(settings.version), urlFromVersion(settings.version), worker) + compiler.event.register('compilationFinished', (success, compilationData, source, input, version) => { + resolve(new CompilerAbstract(settings.version, compilationData, source, input)) }) - })() - return res + compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, '')) + }) } diff --git a/libs/remix-solidity/src/compiler/compiler-worker.ts b/libs/remix-solidity/src/compiler/compiler-worker.ts new file mode 100644 index 0000000000..6d00d87019 --- /dev/null +++ b/libs/remix-solidity/src/compiler/compiler-worker.ts @@ -0,0 +1,73 @@ +'use strict' + +import * as solc from 'solc/wrapper' +import { CompilerInput, MessageToWorker } from './types' +let compileJSON: ((input: CompilerInput) => string) | null = (input) => { return '' } +const missingInputs: string[] = [] + +// 'DedicatedWorkerGlobalScope' object (the Worker global scope) is accessible through the self keyword +// 'dom' and 'webworker' library files can't be included together https://github.com/microsoft/TypeScript/issues/20595 +export default function (self) { // eslint-disable-line @typescript-eslint/explicit-module-boundary-types + self.addEventListener('message', (e) => { + const data: MessageToWorker = e.data + switch (data.cmd) { + case 'loadVersion': + { + delete self.Module + // NOTE: workaround some browsers? + self.Module = undefined + compileJSON = null + // importScripts() method of synchronously imports one or more scripts into the worker's scope + self.importScripts(data.data) + const compiler: solc = solc(self.Module) + compileJSON = (input) => { + try { + const missingInputsCallback = (path) => { + missingInputs.push(path) + return { error: 'Deferred import' } + } + return compiler.compile(input, { import: missingInputsCallback }) + } catch (exception) { + return JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception }) + } + } + self.postMessage({ + cmd: 'versionLoaded', + data: compiler.version(), + license: compiler.license() + }) + break + } + + case 'compile': { + missingInputs.length = 0 + if (data.input && compileJSON) { + self.postMessage({ + cmd: 'compiled', + job: data.job, + timestamp: data.timestamp, + data: compileJSON(data.input), + input: data.input, + missingInputs: missingInputs + }) + } + break + } + + case 'standalone-compile': + missingInputs.length = 0 + if (data.input && compileJSON) { + self.postMessage({ + cmd: 'standalone-compiled', + job: data.job, + timestamp: data.timestamp, + data: compileJSON(data.input), + input: data.input, + missingInputs: missingInputs + }) + } + break + } + + }, false) +} diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 1c407149b7..87481a2ae6 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -167,10 +167,13 @@ export const TabsUI = (props: TabsUIProps) => { if (tabsState.currentExt === 'js' || tabsState.currentExt === 'ts') { await props.plugin.call('scriptRunner', 'execute', content, path) _paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt]) + } else if (path.endsWith('.script.sol')) { + await props.plugin.call('solidity-script', 'execute', path) + _paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt]) } else if (tabsState.currentExt === 'sol' || tabsState.currentExt === 'yul') { await props.plugin.call('solidity', 'compile', path) _paq.push(['trackEvent', 'editor', 'clickRunFromEditor', tabsState.currentExt]) - } + } }} >