2
0
mirror of synced 2025-02-24 03:58:06 +00:00

Better solc support in CLI; it will search the local pacakge for an existing solc version.

This commit is contained in:
Richard Moore 2020-01-29 21:36:50 -05:00
parent edb49da155
commit 7428776f75
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651
2 changed files with 133 additions and 44 deletions

View File

@ -13,7 +13,7 @@ import { ethers } from "ethers";
import { ArgParser, CLI, dump, Help, Plugin } from "../cli"; import { ArgParser, CLI, dump, Help, Plugin } from "../cli";
import { getPassword, getProgressBar } from "../prompt"; import { getPassword, getProgressBar } from "../prompt";
import { compile } from "../solc"; import { compile, ContractCode, customRequire } from "../solc";
function setupContext(path: string, context: any, plugin: Plugin) { 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.__dirname) { context.__dirname = dirname(path); }
if (!context.console) { context.console = console; } if (!context.console) { context.console = console; }
if (!context.require) { if (!context.require) {
context.require = _module.createRequireFromPath(path); //context.require = _module.createRequireFromPath(path);
context.require = customRequire(path);
} }
if (!context.process) { context.process = process; } if (!context.process) { context.process = process; }
@ -234,11 +235,13 @@ class FundPlugin extends Plugin {
this.throwError("Funding requires --network ropsten"); 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.throwUsageError("fund requires ADDRESS");
} }
this.toAddress = await this.getAddress(args[0], "Cannot fund ZERO address", false);
} }
async run(): Promise<void> { async run(): Promise<void> {
@ -802,22 +805,36 @@ class CompilePlugin extends Plugin {
this.throwError("compile requires exactly FILENAME"); this.throwError("compile requires exactly FILENAME");
} }
this.filename = args[0]; this.filename = resolve(args[0]);
} }
async run(): Promise<void> { async run(): Promise<void> {
let source = fs.readFileSync(this.filename).toString(); const source = fs.readFileSync(this.filename).toString();
let result = compile(source, {
filename: this.filename, let result: Array<ContractCode> = null;
optimize: (!this.noOptimize) 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 = { }; let output: any = { };
result.forEach((contract, index) => { result.forEach((contract, index) => {
output[contract.name] = { output[contract.name] = {
bytecode: contract.bytecode, bytecode: contract.bytecode,
runtime: contract.runtime, 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); cli.addPlugin("compile", CompilePlugin);
class DeployPlugin extends Plugin { class DeployPlugin extends Plugin {
filename: string; filename: string;
contractName: string; contractName: string;
@ -870,39 +886,56 @@ class DeployPlugin extends Plugin {
this.throwError("deploy requires exactly FILENAME"); this.throwError("deploy requires exactly FILENAME");
} }
this.filename = args[0]; this.filename = resolve(args[0]);
} }
async run(): Promise<void> { async run(): Promise<void> {
let source = fs.readFileSync(this.filename).toString(); let source = fs.readFileSync(this.filename).toString();
let result = compile(source, { let result: Array<ContractCode> = null;
filename: this.filename, try {
optimize: (!this.noOptimize) 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) { 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) { if (codes.length === 0) {
this.throwError("No contract found"); 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:", { dump("Deployed:", {
Contract: codes[0].name, Contract: codes[0].name,
Address: contract.address, 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.addPlugin("deploy", DeployPlugin);
cli.run(process.argv.slice(2)); cli.run(process.argv.slice(2));

View File

@ -1,23 +1,17 @@
'use strict'; 'use strict';
import fs from "fs"; import fs from "fs";
import _module from "module";
import { dirname, resolve } from "path"; import { dirname, resolve } from "path";
import { ethers } from "ethers"; import { ethers } from "ethers";
let _solc: any = null;
function getSolc(): any {
if (!_solc) {
_solc = require("solc");
}
return _solc;
}
export interface ContractCode { export interface ContractCode {
interface: ethers.utils.Interface; interface: ethers.utils.Interface;
name: string; name: string;
bytecode?: string; compiler: string;
runtime?: string bytecode: string;
runtime: string
}; };
export type CompilerOptions = { export type CompilerOptions = {
@ -27,7 +21,7 @@ export type CompilerOptions = {
throwWarnings?: boolean; throwWarnings?: boolean;
}; };
export function compile(source: string, options?: CompilerOptions): Array<ContractCode> { function populateOptions(options?: CompilerOptions): CompilerOptions {
options = ethers.utils.shallowCopy(options || { }); options = ethers.utils.shallowCopy(options || { });
if (options.filename && !options.basedir) { if (options.filename && !options.basedir) {
@ -36,10 +30,14 @@ export function compile(source: string, options?: CompilerOptions): Array<Contra
if (!options.filename) { options.filename = "_contract.sol"; } if (!options.filename) { options.filename = "_contract.sol"; }
if (!options.basedir) { options.basedir = "."; } if (!options.basedir) { options.basedir = "."; }
let sources: { [ filename: string]: { content: string } } = { }; return options;
}
function getInput(source: string, options: CompilerOptions): any {
const sources: { [ filename: string ]: { content: string } } = { };
sources[options.filename] = { content: source }; sources[options.filename] = { content: source };
let input: any = { const input: any = {
language: "Solidity", language: "Solidity",
sources: sources, sources: sources,
settings: { settings: {
@ -58,7 +56,26 @@ export function compile(source: string, options?: CompilerOptions): Array<Contra
}; };
} }
let findImport = (filename: string): { contents?: string, error?: string } => { return input;
}
function _compile(_solc: any, source: string, options?: CompilerOptions): Array<ContractCode> {
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 { try {
return { return {
contents: fs.readFileSync(resolve(options.basedir, filename)).toString() contents: fs.readFileSync(resolve(options.basedir, filename)).toString()
@ -68,25 +85,34 @@ export function compile(source: string, options?: CompilerOptions): Array<Contra
} }
}; };
let output = JSON.parse(getSolc().compile(JSON.stringify(input), findImport)); if (version >= 6) {
findImport = { import: findImport };
}
let errors = (output.errors || []).filter((x: any) => (x.severity === "error" || options.throwWarnings)).map((x: any) => x.formattedMessage); const outputJson = _solc.compile(JSON.stringify(input), findImport);
const output = JSON.parse(outputJson);
const errors = (output.errors || []).filter((x: any) => (x.severity === "error" || options.throwWarnings)).map((x: any) => x.formattedMessage);
if (errors.length) { if (errors.length) {
let error = new Error("compilation error"); const error = new Error("compilation error");
(<any>error).errors = errors; (<any>error).errors = errors;
throw error; throw error;
} }
let result: Array<ContractCode> = []; const result: Array<ContractCode> = [];
for (let filename in output.contracts) { for (let filename in output.contracts) {
for (let name in output.contracts[filename]) { for (let name in output.contracts[filename]) {
let contract = output.contracts[filename][name]; let contract = output.contracts[filename][name];
// Skip empty contracts
if (!contract.evm.bytecode.object) { continue; }
result.push({ result.push({
name: name, name: name,
interface: new ethers.utils.Interface(contract.abi), interface: new ethers.utils.Interface(contract.abi),
bytecode: "0x" + contract.evm.bytecode.object, 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<Contra
return result; return result;
} }
// Creates a require which will first search from the current location,
// and for solc will fallback onto the version included in @ethersproject/cli
export function customRequire(path: string): (name: string) => 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<ContractCode> {
return function(source: string, options?: CompilerOptions): Array<ContractCode> {
return _compile(_solc, source, options || { });
}
}
export const compile = wrapSolc(customRequire(".")("solc"));