Skip to content

Commit

Permalink
More contained Opcode API.
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed Jan 31, 2020
1 parent 0296594 commit da8153c
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 18 deletions.
29 changes: 29 additions & 0 deletions packages/asm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
========

Expand Down
25 changes: 15 additions & 10 deletions packages/asm/src.ts/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import vm from "vm";

import { ethers } from "ethers";

import { getOpcode, Opcode } from "./opcodes";
import { Opcode } from "./opcodes";

import { parse as _parse, parser as _parser } from "./_parser";

Expand Down Expand Up @@ -61,9 +61,14 @@ class Script {
ethers: ethers,
utils: ethers.utils,

BigNumber: ethers.BigNumber,

arrayify: ethers.utils.arrayify,
hexlify: ethers.utils.hexlify,
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,
keccak256: ethers.utils.keccak256,
Expand All @@ -84,7 +89,7 @@ class Script {
formatBytes32String: ethers.utils.formatBytes32String,
parseBytes32String: ethers.utils.parseBytes32String,

getOpcode: getOpcode,
Opcode: Opcode,

sighash: function(signature: string): string {
return ethers.utils.id(ethers.utils.FunctionFragment.from(signature).format()).substring(0, 10);
Expand Down Expand Up @@ -206,7 +211,7 @@ function pushLiteral(value: ethers.utils.BytesLike | ethers.utils.Hexable | numb
const length = ethers.utils.hexDataLength(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 {
Expand Down Expand Up @@ -320,7 +325,7 @@ export class OpcodeNode extends ValueNode {

static from(options: any): OpcodeNode {
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); }

// Using the function syntax will check the operand count
Expand Down Expand Up @@ -356,7 +361,7 @@ export abstract class LabelledNode extends CodeNode {
export class LabelNode extends LabelledNode {
async assemble(assembler: Assembler, visit: AssembleVisitFunc): Promise<void> {
assembler.start(this);
visit(this, ethers.utils.hexlify(getOpcode("JUMPDEST").value));
visit(this, ethers.utils.hexlify(Opcode.from("JUMPDEST").value));
assembler.end(this);
}

Expand Down Expand Up @@ -389,7 +394,7 @@ export class DataNode extends LabelledNode {
// Replay the data as bytecode, skipping PUSH data
let i = 0;
while (i < bytecode.length) {
const opcode = getOpcode(bytecode[i++]);
const opcode = Opcode.from(bytecode[i++]);
if (opcode) {
i += opcode.isPush();
}
Expand All @@ -398,7 +403,7 @@ export class DataNode extends LabelledNode {
// The amount we overshot the data by is how much padding we need
const padding = new Uint8Array(i - bytecode.length);
// What makes more sense? INVALID or 0 (i.e. STOP)?
//padding.fill(getOpcode("INVALID").value);
//padding.fill(Opcode.from("INVALID").value);
padding.fill(0);
visit(this, ethers.utils.hexlify(padding))

Expand Down Expand Up @@ -587,7 +592,7 @@ export function disassemble(bytecode: string): Bytecode {
let i = 0;
let oob = false;
while (i < bytes.length) {
let opcode = getOpcode(bytes[i]);
let opcode = Opcode.from(bytes[i]);
if (!opcode) {
opcode = new Opcode(`unknown (${ ethers.utils.hexlify(bytes[i]) })`, bytes[i], 0, 0);
} else if (oob && opcode.mnemonic === "JUMPDEST") {
Expand Down Expand Up @@ -1017,7 +1022,7 @@ return(0, #myContract)
return (0, 0)
@checksum[
{{= (defines.checksum ? concat([ getOpcode("PUSH32"), id(_.source) ]): "0x") }}
{{= (defines.checksum ? concat([ Opcode.from("PUSH32"), id(_.source) ]): "0x") }}
]
}`;
Expand Down
3 changes: 1 addition & 2 deletions packages/asm/src.ts/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"use strict";

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";

export {
// Opcodes
getOpcode,
Opcode,

// Assembler functions
Expand Down
13 changes: 7 additions & 6 deletions packages/asm/src.ts/opcodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export class Opcode {
isStatic(): boolean {
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 = {
Expand Down Expand Up @@ -249,10 +256,4 @@ function repeat(char: string, length: number): string {
return result.substring(0, length);
}
*/
export function getOpcode(valueOrMnemonic: number | string) {
if (typeof(valueOrMnemonic) === "string") {
return OpcodeMap[valueOrMnemonic.toLowerCase()] || null;
}
return (Opcodes[valueOrMnemonic] || null);
}

0 comments on commit da8153c

Please sign in to comment.