More contained Opcode API.

This commit is contained in:
Richard Moore 2020-01-30 21:41:03 -05:00
parent 0296594aba
commit da8153c877
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651
4 changed files with 52 additions and 18 deletions

@ -170,6 +170,35 @@ impossible to synthesize recursive hardware.
} }
``` ```
Or code that tries to include its own hash internally:
```
@myContract {
; NOT OK! hash(hash(hash( .... (data))) will never resolve stable bytecode
@checksumBad[
{{= keccak256(myContract) }}
]
; The hash excluding of bytecode excluding the checksum works.
@checksumOk[
{{= keccak256(myContract.slice(0, checksumOk.offset)) }}
{{= zeroPad("0x", 32) }}
{{= keccak256(myContract.slice(checksumOk.offset + 32)) }}
]
; But this is fine... The source code of a file does not change
@checksumGood[
{{= id(myContract.source) }
]
; Even better; this will make disassembled code look nicer...
@checksumBest[
{{= concat([ Opcode.from("PUSH32"), id(myContract.source) ]) }
]
}
```
Building Building
======== ========

@ -14,7 +14,7 @@ import vm from "vm";
import { ethers } from "ethers"; import { ethers } from "ethers";
import { getOpcode, Opcode } from "./opcodes"; import { Opcode } from "./opcodes";
import { parse as _parse, parser as _parser } from "./_parser"; import { parse as _parse, parser as _parser } from "./_parser";
@ -61,9 +61,14 @@ class Script {
ethers: ethers, ethers: ethers,
utils: ethers.utils, utils: ethers.utils,
BigNumber: ethers.BigNumber,
arrayify: ethers.utils.arrayify, arrayify: ethers.utils.arrayify,
hexlify: ethers.utils.hexlify,
concat: hexConcat, concat: hexConcat,
hexlify: ethers.utils.hexlify,
zeroPad: function(value: ethers.utils.BytesLike, length: number) {
return ethers.utils.hexlify(ethers.utils.zeroPad(value, length));
},
id: ethers.utils.id, id: ethers.utils.id,
keccak256: ethers.utils.keccak256, keccak256: ethers.utils.keccak256,
@ -84,7 +89,7 @@ class Script {
formatBytes32String: ethers.utils.formatBytes32String, formatBytes32String: ethers.utils.formatBytes32String,
parseBytes32String: ethers.utils.parseBytes32String, parseBytes32String: ethers.utils.parseBytes32String,
getOpcode: getOpcode, Opcode: Opcode,
sighash: function(signature: string): string { sighash: function(signature: string): string {
return ethers.utils.id(ethers.utils.FunctionFragment.from(signature).format()).substring(0, 10); return ethers.utils.id(ethers.utils.FunctionFragment.from(signature).format()).substring(0, 10);
@ -206,7 +211,7 @@ function pushLiteral(value: ethers.utils.BytesLike | ethers.utils.Hexable | numb
const length = ethers.utils.hexDataLength(hex); const length = ethers.utils.hexDataLength(hex);
if (length === 0 || length > 32) { throw new Error(`literal out of range: ${ hex }`); } if (length === 0 || length > 32) { throw new Error(`literal out of range: ${ hex }`); }
return hexConcat([ getOpcode("PUSH" + String(length)), hex ]); return hexConcat([ Opcode.from("PUSH" + String(length)), hex ]);
} }
export class LiteralNode extends ValueNode { export class LiteralNode extends ValueNode {
@ -320,7 +325,7 @@ export class OpcodeNode extends ValueNode {
static from(options: any): OpcodeNode { static from(options: any): OpcodeNode {
if (options.type !== "opcode") { throw new Error("expected opcode type"); } if (options.type !== "opcode") { throw new Error("expected opcode type"); }
const opcode = getOpcode(options.mnemonic); const opcode = Opcode.from(options.mnemonic);
if (!opcode) { throw new Error("unknown opcode: " + options.mnemonic); } if (!opcode) { throw new Error("unknown opcode: " + options.mnemonic); }
// Using the function syntax will check the operand count // Using the function syntax will check the operand count
@ -356,7 +361,7 @@ export abstract class LabelledNode extends CodeNode {
export class LabelNode extends LabelledNode { export class LabelNode extends LabelledNode {
async assemble(assembler: Assembler, visit: AssembleVisitFunc): Promise<void> { async assemble(assembler: Assembler, visit: AssembleVisitFunc): Promise<void> {
assembler.start(this); assembler.start(this);
visit(this, ethers.utils.hexlify(getOpcode("JUMPDEST").value)); visit(this, ethers.utils.hexlify(Opcode.from("JUMPDEST").value));
assembler.end(this); assembler.end(this);
} }
@ -389,7 +394,7 @@ export class DataNode extends LabelledNode {
// Replay the data as bytecode, skipping PUSH data // Replay the data as bytecode, skipping PUSH data
let i = 0; let i = 0;
while (i < bytecode.length) { while (i < bytecode.length) {
const opcode = getOpcode(bytecode[i++]); const opcode = Opcode.from(bytecode[i++]);
if (opcode) { if (opcode) {
i += opcode.isPush(); i += opcode.isPush();
} }
@ -398,7 +403,7 @@ export class DataNode extends LabelledNode {
// The amount we overshot the data by is how much padding we need // The amount we overshot the data by is how much padding we need
const padding = new Uint8Array(i - bytecode.length); const padding = new Uint8Array(i - bytecode.length);
// What makes more sense? INVALID or 0 (i.e. STOP)? // What makes more sense? INVALID or 0 (i.e. STOP)?
//padding.fill(getOpcode("INVALID").value); //padding.fill(Opcode.from("INVALID").value);
padding.fill(0); padding.fill(0);
visit(this, ethers.utils.hexlify(padding)) visit(this, ethers.utils.hexlify(padding))
@ -587,7 +592,7 @@ export function disassemble(bytecode: string): Bytecode {
let i = 0; let i = 0;
let oob = false; let oob = false;
while (i < bytes.length) { while (i < bytes.length) {
let opcode = getOpcode(bytes[i]); let opcode = Opcode.from(bytes[i]);
if (!opcode) { if (!opcode) {
opcode = new Opcode(`unknown (${ ethers.utils.hexlify(bytes[i]) })`, bytes[i], 0, 0); opcode = new Opcode(`unknown (${ ethers.utils.hexlify(bytes[i]) })`, bytes[i], 0, 0);
} else if (oob && opcode.mnemonic === "JUMPDEST") { } else if (oob && opcode.mnemonic === "JUMPDEST") {
@ -1017,7 +1022,7 @@ return(0, #myContract)
return (0, 0) return (0, 0)
@checksum[ @checksum[
{{= (defines.checksum ? concat([ getOpcode("PUSH32"), id(_.source) ]): "0x") }} {{= (defines.checksum ? concat([ Opcode.from("PUSH32"), id(_.source) ]): "0x") }}
] ]
}`; }`;

@ -1,13 +1,12 @@
"use strict"; "use strict";
import { assemble, CodeNode, DataNode, disassemble, EvaluationNode, ExecutionNode, formatBytecode, LabelNode, LabelledNode, LinkNode, LiteralNode, Node, OpcodeNode, parse, ScopeNode, ValueNode } from "./assembler"; import { assemble, CodeNode, DataNode, disassemble, EvaluationNode, ExecutionNode, formatBytecode, LabelNode, LabelledNode, LinkNode, LiteralNode, Node, OpcodeNode, parse, ScopeNode, ValueNode } from "./assembler";
import { getOpcode, Opcode } from "./opcodes"; import { Opcode } from "./opcodes";
import { AssemblerOptions, AssembleVisitFunc, Bytecode, Location, Operation, VisitFunc } from "./assembler"; import { AssemblerOptions, AssembleVisitFunc, Bytecode, Location, Operation, VisitFunc } from "./assembler";
export { export {
// Opcodes // Opcodes
getOpcode,
Opcode, Opcode,
// Assembler functions // Assembler functions

@ -55,6 +55,13 @@ export class Opcode {
isStatic(): boolean { isStatic(): boolean {
throw new Error("@TODO: return true if certain non-state-changing"); throw new Error("@TODO: return true if certain non-state-changing");
} }
static from(valueOrMnemonic: number | string) {
if (typeof(valueOrMnemonic) === "string") {
return OpcodeMap[valueOrMnemonic.toLowerCase()] || null;
}
return (Opcodes[valueOrMnemonic] || null);
}
} }
type _Opcode = { type _Opcode = {
@ -249,10 +256,4 @@ function repeat(char: string, length: number): string {
return result.substring(0, length); return result.substring(0, length);
} }
*/ */
export function getOpcode(valueOrMnemonic: number | string) {
if (typeof(valueOrMnemonic) === "string") {
return OpcodeMap[valueOrMnemonic.toLowerCase()] || null;
}
return (Opcodes[valueOrMnemonic] || null);
}