From 907eed5c0658b1ea4ddc4a65924eb6fec1d4a170 Mon Sep 17 00:00:00 2001 From: mainnet-pat Date: Wed, 22 Nov 2023 13:10:07 +0000 Subject: [PATCH 001/171] Checkpoint after merge into upstream --- packages/cashc/jest.setup.js | 2 + packages/cashc/src/artifact/Artifact.ts | 14 +- packages/cashc/src/ast/Location.ts | 10 +- packages/cashc/src/compiler.ts | 5 +- .../src/generation/GenerateTargetTraversal.ts | 177 +++--- packages/cashscript/package.json | 4 +- packages/cashscript/src/Contract.ts | 6 +- packages/cashscript/src/LibauthTemplate.ts | 547 ++++++++++++++++++ .../cashscript/test/LibauthTemplate.test.ts | 54 ++ .../test/fixture/transfer_with_timeout.json | 10 +- packages/utils/src/artifact.ts | 11 + packages/utils/src/index.ts | 2 + packages/utils/src/libauth.ts | 47 ++ packages/utils/src/sourceMap.ts | 114 ++++ packages/utils/test/libauth.test.ts | 48 ++ packages/utils/test/sourceMap.test.ts | 84 +++ yarn.lock | 10 + 17 files changed, 1054 insertions(+), 91 deletions(-) create mode 100644 packages/cashscript/src/LibauthTemplate.ts create mode 100644 packages/cashscript/test/LibauthTemplate.test.ts create mode 100644 packages/utils/src/libauth.ts create mode 100644 packages/utils/src/sourceMap.ts create mode 100644 packages/utils/test/libauth.test.ts create mode 100644 packages/utils/test/sourceMap.test.ts diff --git a/packages/cashc/jest.setup.js b/packages/cashc/jest.setup.js index e69de29b..0327c5de 100644 --- a/packages/cashc/jest.setup.js +++ b/packages/cashc/jest.setup.js @@ -0,0 +1,2 @@ +import { inspect } from 'util'; +inspect.defaultOptions.depth = 10; diff --git a/packages/cashc/src/artifact/Artifact.ts b/packages/cashc/src/artifact/Artifact.ts index e830b60f..5a4c88fe 100644 --- a/packages/cashc/src/artifact/Artifact.ts +++ b/packages/cashc/src/artifact/Artifact.ts @@ -2,7 +2,13 @@ import { Artifact, Script, scriptToAsm } from '@cashscript/utils'; import { version } from '../index.js'; import { Ast } from '../ast/AST.js'; -export function generateArtifact(ast: Ast, script: Script, source: string): Artifact { +export function generateArtifact( + ast: Ast, + script: Script, + source: string, + debugScript: Script, + sourceMap: string + ): Artifact { const { contract } = ast; const constructorInputs = contract.parameters @@ -17,6 +23,7 @@ export function generateArtifact(ast: Ast, script: Script, source: string): Arti })); const bytecode = scriptToAsm(script); + const debugBytecode = scriptToAsm(debugScript); return { contractName: contract.name, @@ -24,6 +31,11 @@ export function generateArtifact(ast: Ast, script: Script, source: string): Arti abi, bytecode, source, + debug: { + bytecode: debugBytecode, + sourceMap: sourceMap, + logs: [], + }, compiler: { name: 'cashc', version, diff --git a/packages/cashc/src/ast/Location.ts b/packages/cashc/src/ast/Location.ts index 93109ea0..1ef15714 100644 --- a/packages/cashc/src/ast/Location.ts +++ b/packages/cashc/src/ast/Location.ts @@ -1,7 +1,8 @@ import type { ParserRuleContext } from 'antlr4ts/ParserRuleContext.js'; import type { Token } from 'antlr4ts'; +import { LocationI } from '@cashscript/utils'; -export class Location { +export class Location implements LocationI { constructor(public start: Point, public end: Point) {} static fromCtx(ctx: ParserRuleContext): Location | undefined { @@ -23,6 +24,13 @@ export class Location { return new Location(start, end); } + static fromObject(object: LocationI): Location { + const start = new Point(object.start.line, object.start.column); + const end = new Point(object.end.line, object.end.column); + + return new Location(start, end); + } + text(code: string): string { return code.slice(this.start.offset(code), this.end.offset(code)); } diff --git a/packages/cashc/src/compiler.ts b/packages/cashc/src/compiler.ts index e5779275..a38b5718 100644 --- a/packages/cashc/src/compiler.ts +++ b/packages/cashc/src/compiler.ts @@ -1,4 +1,4 @@ -import { Artifact, optimiseBytecode } from '@cashscript/utils'; +import { Artifact, generateSourceMap, optimiseBytecode } from '@cashscript/utils'; import { ANTLRInputStream, CommonTokenStream } from 'antlr4ts'; import fs, { PathLike } from 'fs'; import { generateArtifact } from './artifact/Artifact.js'; @@ -28,8 +28,9 @@ export function compileString(code: string): Artifact { // Bytecode optimisation const optimisedBytecode = optimiseBytecode(bytecode); + const sourceMap = generateSourceMap(traversal.locationData); - return generateArtifact(ast, optimisedBytecode, code); + return generateArtifact(ast, optimisedBytecode, code, bytecode, sourceMap); } export function compileFile(codeFile: PathLike): Artifact { diff --git a/packages/cashc/src/generation/GenerateTargetTraversal.ts b/packages/cashc/src/generation/GenerateTargetTraversal.ts index 337510e0..e7bc46b2 100644 --- a/packages/cashc/src/generation/GenerateTargetTraversal.ts +++ b/packages/cashc/src/generation/GenerateTargetTraversal.ts @@ -10,6 +10,9 @@ import { resultingType, Script, scriptToAsm, + generateSourceMap, + LocationI, + LocationData } from '@cashscript/utils'; import { ContractNode, @@ -49,19 +52,24 @@ import { compileTimeOp, compileUnaryOp, } from './utils.js'; +import { Location } from '../ast/Location.js'; -export default class GenerateTargetTraversal extends AstTraversal { +export default class GenerateTargetTraversalWithLocation extends AstTraversal { + locationData: LocationData = []; output: Script = []; stack: string[] = []; + souceMap: string; private scopeDepth = 0; private currentFunction: FunctionDefinitionNode; - private emit(op: OpOrData | OpOrData[]): void { + private emit(op: OpOrData | OpOrData[], location: LocationI, positionHint?: number): void { if (Array.isArray(op)) { - this.output.push(...op); + op.forEach(val => this.output.push(val as Op)); + op.forEach(_ => this.locationData.push([location, positionHint])); } else { this.output.push(op); + this.locationData.push([location, positionHint]); } } @@ -99,6 +107,8 @@ export default class GenerateTargetTraversal extends AstTraversal { // Minimally encode output by going Script -> ASM -> Script this.output = asmToScript(scriptToAsm(this.output)); + this.souceMap = generateSourceMap(this.locationData); + return node; } @@ -111,35 +121,35 @@ export default class GenerateTargetTraversal extends AstTraversal { node.functions = node.functions.map((f, i) => { const stackCopy = [...this.stack]; const selectorIndex = this.getStackIndex('$$'); - this.emit(encodeInt(BigInt(selectorIndex))); + this.emit(encodeInt(BigInt(selectorIndex)), f.location!); if (i === node.functions.length - 1) { - this.emit(Op.OP_ROLL); + this.emit(Op.OP_ROLL, f.location!); this.removeFromStack(selectorIndex); } else { - this.emit(Op.OP_PICK); + this.emit(Op.OP_PICK, f.location!); } // All functions are if-else statements, except the final one which is // enforced with NUMEQUALVERIFY - this.emit(encodeInt(BigInt(i))); - this.emit(Op.OP_NUMEQUAL); + this.emit(encodeInt(BigInt(i)), f.location!); + this.emit(Op.OP_NUMEQUAL, f.location!); if (i < node.functions.length - 1) { - this.emit(Op.OP_IF); + this.emit(Op.OP_IF, f.location!); } else { - this.emit(Op.OP_VERIFY); + this.emit(Op.OP_VERIFY, f.location!); } f = this.visit(f) as FunctionDefinitionNode; if (i < node.functions.length - 1) { - this.emit(Op.OP_ELSE); + this.emit(Op.OP_ELSE, f.location!, 1); } this.stack = [...stackCopy]; return f; }); for (let i = 0; i < node.functions.length - 1; i += 1) { - this.emit(Op.OP_ENDIF); + this.emit(Op.OP_ENDIF, node.functions[i+1].location!, 1); } } @@ -152,17 +162,18 @@ export default class GenerateTargetTraversal extends AstTraversal { node.parameters = this.visitList(node.parameters) as ParameterNode[]; node.body = this.visit(node.body) as BlockNode; - this.removeFinalVerify(); - this.cleanStack(); + this.removeFinalVerify(node.body); + this.cleanStack(node.body); return node; } - removeFinalVerify(): void { + removeFinalVerify(node: Node): void { // After EnsureFinalRequireTraversal, we know that the final opcodes are either // "OP_VERIFY", "OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP" or "OP_ENDIF" const finalOp = this.output.pop() as Op; + const [location] = this.locationData.pop() as [Location, number?]; // If the final op is OP_VERIFY and the stack size is less than 4 we remove it from the script // - We have the stack size check because it is more efficient to use 2DROP rather than NIP @@ -172,22 +183,22 @@ export default class GenerateTargetTraversal extends AstTraversal { // we add it back to the stack this.pushToStack('(value)'); } else { - this.emit(finalOp); + this.emit(finalOp, location!); // At this point there is no verification value left on the stack: // - scoped stack is cleared inside branch ended by OP_ENDIF // - OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP does not leave a verification value // so we add OP_1 to the script (indicating success) - this.emit(Op.OP_1); + this.emit(Op.OP_1, node.location!, 1); this.pushToStack('(value)'); } } - cleanStack(): void { + cleanStack(node: Node): void { // Keep final verification value, OP_NIP the other stack values const stackSize = this.stack.length; for (let i = 0; i < stackSize - 1; i += 1) { - this.emit(Op.OP_NIP); + this.emit(Op.OP_NIP, node.location!, 1); this.nipFromStack(); } } @@ -215,7 +226,7 @@ export default class GenerateTargetTraversal extends AstTraversal { visitAssign(node: AssignNode): Node { node.expression = this.visit(node.expression); if (this.scopeDepth > 0) { - this.emitReplace(this.getStackIndex(node.identifier.name)); + this.emitReplace(this.getStackIndex(node.identifier.name), node); this.popFromStack(); } else { this.popFromStack(); @@ -226,24 +237,25 @@ export default class GenerateTargetTraversal extends AstTraversal { // This algorithm can be optimised for hardcoded depths // See thesis for explanation - emitReplace(index: number): void { - this.emit(encodeInt(BigInt(index))); - this.emit(Op.OP_ROLL); - this.emit(Op.OP_DROP); + emitReplace(index: number, node: Node): void { + console.log("replace", index, node) + this.emit(encodeInt(BigInt(index)), node.location!); + this.emit(Op.OP_ROLL, node.location!); + this.emit(Op.OP_DROP, node.location!); for (let i = 0; i < index - 1; i += 1) { - this.emit(Op.OP_SWAP); + this.emit(Op.OP_SWAP, node.location!); if (i < index - 2) { - this.emit(Op.OP_TOALTSTACK); + this.emit(Op.OP_TOALTSTACK, node.location!); } } for (let i = 0; i < index - 2; i += 1) { - this.emit(Op.OP_FROMALTSTACK); + this.emit(Op.OP_FROMALTSTACK, node.location!); } } visitTimeOp(node: TimeOpNode): Node { node.expression = this.visit(node.expression); - this.emit(compileTimeOp(node.timeOp)); + this.emit(compileTimeOp(node.timeOp), node.location!); this.popFromStack(); return node; } @@ -251,7 +263,7 @@ export default class GenerateTargetTraversal extends AstTraversal { visitRequire(node: RequireNode): Node { node.expression = this.visit(node.expression); - this.emit(Op.OP_VERIFY); + this.emit(Op.OP_VERIFY, node.location!); this.popFromStack(); return node; } @@ -261,29 +273,30 @@ export default class GenerateTargetTraversal extends AstTraversal { this.popFromStack(); this.scopeDepth += 1; - this.emit(Op.OP_IF); + this.emit(Op.OP_IF, node.ifBlock.location!); let stackDepth = this.stack.length; node.ifBlock = this.visit(node.ifBlock); - this.removeScopedVariables(stackDepth); + this.removeScopedVariables(stackDepth, node); if (node.elseBlock) { - this.emit(Op.OP_ELSE); + this.emit(Op.OP_ELSE, node.elseBlock.location!, 1); stackDepth = this.stack.length; node.elseBlock = this.visit(node.elseBlock); - this.removeScopedVariables(stackDepth); + this.removeScopedVariables(stackDepth, node); } - this.emit(Op.OP_ENDIF); + this.emit(Op.OP_ENDIF, node.elseBlock ? node.elseBlock.location! : node.ifBlock.location!, 1); this.scopeDepth -= 1; return node; } - removeScopedVariables(depthBeforeScope: number): void { + removeScopedVariables(depthBeforeScope: number, node: Node): void { const dropCount = this.stack.length - depthBeforeScope; + console.warn(dropCount) for (let i = 0; i < dropCount; i += 1) { - this.emit(Op.OP_DROP); + this.emit(Op.OP_DROP, node.location!); this.popFromStack(); } } @@ -294,11 +307,11 @@ export default class GenerateTargetTraversal extends AstTraversal { // Special case for sized bytes cast, since it has another node to traverse if (node.size) { node.size = this.visit(node.size); - this.emit(Op.OP_NUM2BIN); + this.emit(Op.OP_NUM2BIN, node.location!); this.popFromStack(); } - this.emit(compileCast(node.expression.type as PrimitiveType, node.type)); + this.emit(compileCast(node.expression.type as PrimitiveType, node.type), node.location!); this.popFromStack(); this.pushToStack('(value)'); return node; @@ -311,7 +324,7 @@ export default class GenerateTargetTraversal extends AstTraversal { node.parameters = this.visitList(node.parameters); - this.emit(compileGlobalFunction(node.identifier.name as GlobalFunction)); + this.emit(compileGlobalFunction(node.identifier.name as GlobalFunction), node.location!); this.popFromStack(node.parameters.length); this.pushToStack('(value)'); @@ -319,10 +332,10 @@ export default class GenerateTargetTraversal extends AstTraversal { } visitMultiSig(node: FunctionCallNode): Node { - this.emit(encodeBool(false)); + this.emit(encodeBool(false), node.location!); this.pushToStack('(value)'); node.parameters = this.visitList(node.parameters); - this.emit(Op.OP_CHECKMULTISIG); + this.emit(Op.OP_CHECKMULTISIG, node.location!); const sigs = node.parameters[0] as ArrayNode; const pks = node.parameters[1] as ArrayNode; this.popFromStack(sigs.elements.length + pks.elements.length + 3); @@ -334,70 +347,70 @@ export default class GenerateTargetTraversal extends AstTraversal { visitInstantiation(node: InstantiationNode): Node { if (node.identifier.name === Class.LOCKING_BYTECODE_P2PKH) { // OP_DUP OP_HASH160 OP_PUSH<20> - this.emit(hexToBin('76a914')); + this.emit(hexToBin('76a914'), node.location!); this.pushToStack('(value)'); // this.visit(node.parameters[0]); - this.emit(Op.OP_CAT); + this.emit(Op.OP_CAT, node.location!); // OP_EQUAL OP_CHECKSIG - this.emit(hexToBin('88ac')); - this.emit(Op.OP_CAT); + this.emit(hexToBin('88ac'), node.location!); + this.emit(Op.OP_CAT, node.location!); this.popFromStack(2); } else if (node.identifier.name === Class.LOCKING_BYTECODE_P2SH20) { // OP_HASH160 OP_PUSH<20> - this.emit(hexToBin('a914')); + this.emit(hexToBin('a914'), node.location!); this.pushToStack('(value)'); //