diff --git a/packages/asm/src.ts/assembler.ts b/packages/asm/src.ts/assembler.ts index 8096ae86bc..bbbab7089b 100644 --- a/packages/asm/src.ts/assembler.ts +++ b/packages/asm/src.ts/assembler.ts @@ -655,9 +655,9 @@ export function formatBytecode(bytecode: Array): string { const push = opcode.isPush(); if (push) { if (op.pushValue) { - operation = op.pushValue + `${ repeat(" ", 65 - op.pushValue.length) }; ${ operation } `; + operation = op.pushValue + `${ repeat(" ", 67 - op.pushValue.length) }; #${ push } `; } else { - operation += `${ repeat(" ", 65 - operation.length) }; OOB!! `; + operation += `${ repeat(" ", 67 - operation.length) }; OOB!! `; } } diff --git a/packages/cli/package.json b/packages/cli/package.json index f4928372eb..0d5f35275b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -2,10 +2,12 @@ "author": "Richard Moore ", "bin": { "ethers": "./lib/bin/ethers.js", + "ethers-asm": "./lib/bin/ethers-asm.js", "ethers-ens": "./lib/bin/ethers-ens.js", "ethers-ts": "./lib/bin/ethers-ts.js" }, "dependencies": { + "@ethersproject/asm": ">=5.0.0-beta.148", "@ethersproject/basex": ">=5.0.0-beta.127", "ethers": ">=5.0.0-beta.156", "mime-types": "2.1.11", diff --git a/packages/cli/src.ts/bin/ethers-asm.ts b/packages/cli/src.ts/bin/ethers-asm.ts new file mode 100644 index 0000000000..1a1b9fcf85 --- /dev/null +++ b/packages/cli/src.ts/bin/ethers-asm.ts @@ -0,0 +1,150 @@ +#!/usr/bin/env node + +"use strict"; + +import fs from "fs"; +import _module from "module"; +import { resolve } from "path"; + +import { assemble, disassemble, formatBytecode, parse } from "@ethersproject/asm"; + +import { ArgParser, CLI, Help, Plugin } from "../cli"; + +function repeat(text: string, length: number): string { + if (text.length === 0) { throw new Error("boo"); } + let result = text; + while (result.length < length) { + result += result; + } + return result.substring(0, length); +} + +let cli = new CLI(null, { + account: false, + provider: false, + transaction: false +}); + +class AssemblePlugin extends Plugin { + + filename: string; + content: string; + disassemble: boolean; + defines: { [ key: string ]: string | boolean } + + static getHelp(): Help { + return { + name: "[ FILENAME ]", + help: "Process the file (or stdin)" + }; + } + static getOptionHelp(): Array { + return [ + { + name: "--disassemble", + help: "Disassemble input bytecode" + }, + { + name: "--define KEY=VALUE", + help: "provide assembler defines" + } + ]; + } + + async prepareOptions(argParser: ArgParser): Promise { + await super.prepareOptions(argParser); + + // Get all the --define key=value pairs + this.defines = { }; + argParser.consumeOptions("define").forEach((pair) => { + const match = pair.match(/([a-z][a-z0-9_]+)(=.*)?/i); + if (!match) { + this.throwError(`invalid define: ${ pair }`); + } + this.defines[match[1]] = (match[2] ? match[2].substring(1): true); + }); + + // We are disassembling... + this.disassemble = argParser.consumeFlag("disassemble"); + } + + async prepareArgs(args: Array): Promise { + await super.prepareArgs(args); + + if (args.length > 1) { + this.throwError("assembler requires at most one FILENAME"); + + } else if (args.length === 1) { + this.filename = resolve(args[0]); + this.content = fs.readFileSync(this.filename).toString(); + + } else { + this.content = await (new Promise((resolve, reject) => { + let data = ""; + + process.stdin.setEncoding('utf8'); + + process.stdin.on("data", (chunk) => { + data += chunk; + }); + + process.stdin.on("end", () => { + resolve(data); + }); + + process.stdin.on("error", (error) => { + reject(error); + }); + })); + } + + if (this.disassemble) { + const bytecodes: Array = [ ]; + const leftovers = this.content.replace(/(?:(?:0x)?((:?[0-9a-f][0-9a-f])*))/gi, (all, bytecode) => { + bytecodes.push(bytecode); + return repeat(" ", all.length); + }); + + if (leftovers.trim()) { + + // Process the leftovers + const chunks = leftovers.split(/(\s+)/); + const offset = (chunks[0] ? 0: chunks[1].length); + const column = (chunks[0] ? 0: chunks[1].split(/[\r\n]/).pop().length); + + + // Count the lines in the prefix + const prefix = this.content.substring(0, offset); + const lineNo = prefix.length - prefix.replace(/\r|\n|\r\n/g, '').length; + + // Get teh actual error line + const line = this.content.substring(offset - column).split(/\n/)[0]; + + // Formatted output line + let output = `Invalid Bytecode Character found in line ${ lineNo + 1 }, column ${ column + 1 }\n`; + output += line + "\n"; + output += repeat("-", column) + "^"; + + this.throwError(output); + } + + this.content = "0x" + bytecodes.join(""); + } + } + + + async run(): Promise { + if (this.disassemble) { + console.log(formatBytecode(disassemble(this.content))); + } else { + console.log(await assemble(parse(this.content), { + defines: this.defines, + filename: this.filename, + })); + } + } +} +cli.setPlugin(AssemblePlugin); + +cli.run(process.argv.slice(2)) + diff --git a/packages/cli/src.ts/cli.ts b/packages/cli/src.ts/cli.ts index 2baf24677c..605a89a832 100644 --- a/packages/cli/src.ts/cli.ts +++ b/packages/cli/src.ts/cli.ts @@ -883,15 +883,20 @@ export class CLI { } console.log("OTHER OPTIONS"); - console.log(" --wait Wait until transactions are mined"); + if (this.options.transaction) { + console.log(" --wait Wait until transactions are mined"); + } console.log(" --debug Show stack traces for errors"); console.log(" --help Show this usage and exit"); console.log(" --version Show this version and exit"); console.log(""); - console.log("(*) By including mnemonics or private keys on the command line they are"); - console.log(" possibly readable by other users on your system and may get stored in"); - console.log(" your bash history file. This is NOT recommended."); - console.log(""); + + if (this.options.account) { + console.log("(*) By including mnemonics or private keys on the command line they are"); + console.log(" possibly readable by other users on your system and may get stored in"); + console.log(" your bash history file. This is NOT recommended."); + console.log(""); + } if (message) { console.log(message);