diff --git a/packages/cli/src.ts/bin/ethers.ts b/packages/cli/src.ts/bin/ethers.ts index 9d375a186a..c425350aee 100644 --- a/packages/cli/src.ts/bin/ethers.ts +++ b/packages/cli/src.ts/bin/ethers.ts @@ -13,7 +13,7 @@ import { ethers } from "ethers"; import { ArgParser, CLI, dump, Help, Plugin } from "../cli"; import { getPassword, getProgressBar } from "../prompt"; -import { compile } from "../solc"; +import { compile, ContractCode, customRequire } from "../solc"; function setupContext(path: string, context: any, plugin: Plugin) { @@ -24,7 +24,8 @@ function setupContext(path: string, context: any, plugin: Plugin) { if (!context.__dirname) { context.__dirname = dirname(path); } if (!context.console) { context.console = console; } if (!context.require) { - context.require = _module.createRequireFromPath(path); + //context.require = _module.createRequireFromPath(path); + context.require = customRequire(path); } if (!context.process) { context.process = process; } @@ -234,11 +235,13 @@ class FundPlugin extends Plugin { this.throwError("Funding requires --network ropsten"); } - if (args.length !== 1) { + if (args.length === 1) { + this.toAddress = await this.getAddress(args[0], "Cannot fund ZERO address", false); + } else if (args.length === 0 && this.accounts.length === 1) { + this.toAddress = await this.accounts[0].getAddress(); + } else { this.throwUsageError("fund requires ADDRESS"); } - - this.toAddress = await this.getAddress(args[0], "Cannot fund ZERO address", false); } async run(): Promise { @@ -802,22 +805,36 @@ class CompilePlugin extends Plugin { this.throwError("compile requires exactly FILENAME"); } - this.filename = args[0]; + this.filename = resolve(args[0]); } async run(): Promise { - let source = fs.readFileSync(this.filename).toString(); - let result = compile(source, { - filename: this.filename, - optimize: (!this.noOptimize) - }); + const source = fs.readFileSync(this.filename).toString(); + + let result: Array = null; + try { + result = compile(source, { + filename: this.filename, + optimize: (!this.noOptimize) + }); + } catch (error) { + if (error.errors) { + error.errors.forEach((error: string) => { + console.log(error); + }); + } else { + throw error; + } + throw new Error("Failed to compile contract."); + } let output: any = { }; result.forEach((contract, index) => { output[contract.name] = { bytecode: contract.bytecode, runtime: contract.runtime, - interface: contract.interface.fragments.map((f) => f.format(ethers.utils.FormatTypes.full)) + interface: contract.interface.fragments.map((f) => f.format(ethers.utils.FormatTypes.full)), + compiler: contract.compiler }; }); @@ -826,7 +843,6 @@ class CompilePlugin extends Plugin { } cli.addPlugin("compile", CompilePlugin); - class DeployPlugin extends Plugin { filename: string; contractName: string; @@ -870,39 +886,56 @@ class DeployPlugin extends Plugin { this.throwError("deploy requires exactly FILENAME"); } - this.filename = args[0]; + this.filename = resolve(args[0]); } async run(): Promise { let source = fs.readFileSync(this.filename).toString(); - let result = compile(source, { - filename: this.filename, - optimize: (!this.noOptimize) - }); + let result: Array = null; + try { + result = compile(source, { + filename: this.filename, + optimize: (!this.noOptimize) + }); + } catch (error) { + if (error.errors) { + error.errors.forEach((error: string) => { + console.log(error); + }); + } else { + throw error; + } + throw new Error("Failed to compile contract."); + } - let codes = result.filter((c) => (c.bytecode !== "0x" && (this.contractName == null || this.contractName == c.name))); + const codes = result.filter((c) => (this.contractName == null || this.contractName == c.name)); if (codes.length > 1) { - this.throwError("Please specify a contract with --contract NAME"); + this.throwError("Multiple contracts found; please specify a contract with --contract NAME"); } if (codes.length === 0) { this.throwError("No contract found"); } - let factory = new ethers.ContractFactory(codes[0].interface, codes[0].bytecode, this.accounts[0]); + const factory = new ethers.ContractFactory(codes[0].interface, codes[0].bytecode, this.accounts[0]); - let contract = await factory.deploy(); + dump("Deploying:", { + Contract: codes[0].name, + Bytecode: codes[0].bytecode, + Interface: codes[0].interface.fragments.map((f) => f.format(ethers.utils.FormatTypes.full)), + Compiler: codes[0].compiler, + Optimizer: (this.noOptimize ? "No": "Yes") + }); + + const contract = await factory.deploy(); dump("Deployed:", { Contract: codes[0].name, Address: contract.address, - Bytecode: codes[0].bytecode, - Interface: codes[0].interface.fragments.map((f) => f.format(ethers.utils.FormatTypes.full)) }); } } cli.addPlugin("deploy", DeployPlugin); - cli.run(process.argv.slice(2)); diff --git a/packages/cli/src.ts/solc.ts b/packages/cli/src.ts/solc.ts index a938fda0ba..e1a373cd87 100644 --- a/packages/cli/src.ts/solc.ts +++ b/packages/cli/src.ts/solc.ts @@ -1,23 +1,17 @@ 'use strict'; import fs from "fs"; +import _module from "module"; import { dirname, resolve } from "path"; import { ethers } from "ethers"; -let _solc: any = null; -function getSolc(): any { - if (!_solc) { - _solc = require("solc"); - } - return _solc; -} - export interface ContractCode { interface: ethers.utils.Interface; name: string; - bytecode?: string; - runtime?: string + compiler: string; + bytecode: string; + runtime: string }; export type CompilerOptions = { @@ -27,7 +21,7 @@ export type CompilerOptions = { throwWarnings?: boolean; }; -export function compile(source: string, options?: CompilerOptions): Array { +function populateOptions(options?: CompilerOptions): CompilerOptions { options = ethers.utils.shallowCopy(options || { }); if (options.filename && !options.basedir) { @@ -36,10 +30,14 @@ export function compile(source: string, options?: CompilerOptions): Array { + return input; +} + +function _compile(_solc: any, source: string, options?: CompilerOptions): Array { + const compilerVersion = _solc.version(); + + const ver = compilerVersion.match(/(\d+)\.(\d+)\.(\d+)/); + if (!ver || ver[1] !== "0") { throw new Error("unknown version"); } + + const version = parseFloat(ver[2] + "." + ver[3]); + //if (version < 4.11 || version >= 7) { + if (version < 5.0 || version >= 7.0) { + throw new Error(`unsupported version: ${ ver[1] }.${ ver[2] }.${ ver[3] }`); + } + + options = populateOptions(options); + + const input = getInput(source, options); + + let findImport: any = (filename: string): { contents?: string, error?: string } => { try { return { contents: fs.readFileSync(resolve(options.basedir, filename)).toString() @@ -68,25 +85,34 @@ export function compile(source: string, options?: CompilerOptions): Array= 6) { + findImport = { import: findImport }; + } + + const outputJson = _solc.compile(JSON.stringify(input), findImport); + const output = JSON.parse(outputJson); - let errors = (output.errors || []).filter((x: any) => (x.severity === "error" || options.throwWarnings)).map((x: any) => x.formattedMessage); + const errors = (output.errors || []).filter((x: any) => (x.severity === "error" || options.throwWarnings)).map((x: any) => x.formattedMessage); if (errors.length) { - let error = new Error("compilation error"); + const error = new Error("compilation error"); (error).errors = errors; throw error; } - let result: Array = []; + const result: Array = []; for (let filename in output.contracts) { for (let name in output.contracts[filename]) { let contract = output.contracts[filename][name]; + // Skip empty contracts + if (!contract.evm.bytecode.object) { continue; } + result.push({ name: name, interface: new ethers.utils.Interface(contract.abi), bytecode: "0x" + contract.evm.bytecode.object, - runtime: "0x" + contract.evm.deployedBytecode.object + runtime: "0x" + contract.evm.deployedBytecode.object, + compiler: compilerVersion }); } } @@ -94,3 +120,33 @@ export function compile(source: string, options?: CompilerOptions): Array any { + const pathRequire = _module.createRequireFromPath(resolve(path, "./sandbox.js")); + const libRequire = _module.createRequireFromPath(resolve(__filename)); + + return function(name: string): any { + try { + return pathRequire(name); + } catch (error) { + if (name === "solc") { + try { + return libRequire(name); + } catch (error) { } + } + throw error; + } + + } +} + +export function wrapSolc(_solc: any): (source: string, options?: CompilerOptions) => Array { + return function(source: string, options?: CompilerOptions): Array { + return _compile(_solc, source, options || { }); + } +} + +export const compile = wrapSolc(customRequire(".")("solc"));