Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/announcement.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

/* This is a contract showcasing covenants outside of regular transactional use.
* It enforces the contract to make an "announcement" on Memo.cash, and send the
Expand Down
2 changes: 1 addition & 1 deletion examples/hodl_vault.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

// This contract forces HODLing until a certain price target has been reached
// A minimum block is provided to ensure that oracle price entries from before this block are disregarded
Expand Down
2 changes: 1 addition & 1 deletion examples/mecenas.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

/* This is an unofficial CashScript port of Licho's Mecenas contract. It is
* not compatible with Licho's EC plugin, but rather meant as a demonstration
Expand Down
2 changes: 1 addition & 1 deletion examples/mecenas_locktime.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

// This is an experimental contract for a more "streaming" Mecenas experience
// Completely untested, just a concept
Expand Down
2 changes: 1 addition & 1 deletion examples/p2pkh.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

contract P2PKH(bytes20 pkh) {
// Require pk to match stored pkh and signature to match
Expand Down
6 changes: 3 additions & 3 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cashscript-examples",
"private": true,
"version": "0.12.0",
"version": "0.13.0-next.0",
"description": "Usage examples of the CashScript SDK",
"main": "p2pkh.js",
"type": "module",
Expand All @@ -13,8 +13,8 @@
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.8",
"@types/node": "^22.17.0",
"cashc": "^0.12.0",
"cashscript": "^0.12.0",
"cashc": "^0.13.0-next.0",
"cashscript": "^0.13.0-next.0",
"eslint": "^8.56.0",
"typescript": "^5.9.2"
}
Expand Down
4 changes: 2 additions & 2 deletions examples/testing-suite/artifacts/example.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
},
"compiler": {
"name": "cashc",
"version": "0.12.0"
"version": "0.13.0"
},
"updatedAt": "2025-10-02T09:56:11.510Z"
}
}
6 changes: 3 additions & 3 deletions examples/testing-suite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "testing-suite",
"version": "0.12.0",
"version": "0.13.0-next.0",
"description": "Example project to develop and test CashScript contracts",
"main": "index.js",
"type": "module",
Expand All @@ -26,8 +26,8 @@
},
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.8",
"cashc": "^0.12.0",
"cashscript": "^0.12.0",
"cashc": "^0.13.0-next.0",
"cashscript": "^0.13.0-next.0",
"url-join": "^5.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion examples/transfer_with_timeout.cash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma cashscript ^0.12.0;
pragma cashscript ^0.13.0;

contract TransferWithTimeout(
pubkey sender,
Expand Down
4 changes: 2 additions & 2 deletions packages/cashc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cashc",
"version": "0.12.0",
"version": "0.13.0-next.0",
"description": "Compile Bitcoin Cash contracts to Bitcoin Cash Script or artifacts",
"keywords": [
"bitcoin",
Expand Down Expand Up @@ -52,7 +52,7 @@
},
"dependencies": {
"@bitauth/libauth": "^3.1.0-next.8",
"@cashscript/utils": "^0.12.0",
"@cashscript/utils": "^0.13.0-next.0",
"antlr4": "^4.13.2",
"commander": "^14.0.0",
"semver": "^7.7.2"
Expand Down
13 changes: 13 additions & 0 deletions packages/cashc/src/ast/AST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ export class BranchNode extends StatementNode {
}
}

export class DoWhileNode extends StatementNode {
constructor(
public condition: ExpressionNode,
public block: BlockNode,
) {
super();
}

accept<T>(visitor: AstVisitor<T>): T {
return visitor.visitDoWhile(this);
}
}

export class BlockNode extends Node {
symbolTable?: SymbolTable;

Expand Down
15 changes: 15 additions & 0 deletions packages/cashc/src/ast/AstBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
ConsoleStatementNode,
ConsoleParameterNode,
SliceNode,
DoWhileNode,
} from './AST.js';
import { UnaryOperator, BinaryOperator, NullaryOperator } from './Operator.js';
import type {
Expand Down Expand Up @@ -68,6 +69,8 @@ import type {
StatementContext,
RequireMessageContext,
SliceContext,
DoWhileStatementContext,
LoopStatementContext,
} from '../grammar/CashScriptParser.js';
import CashScriptVisitor from '../grammar/CashScriptVisitor.js';
import { Location } from './Location.js';
Expand Down Expand Up @@ -214,6 +217,18 @@ export default class AstBuilder
return branch;
}

visitLoopStatement(ctx: LoopStatementContext): DoWhileNode {
return this.visit(ctx.doWhileStatement()) as DoWhileNode;
}

visitDoWhileStatement(ctx: DoWhileStatementContext): DoWhileNode {
const condition = this.visit(ctx.expression());
const block = this.visit(ctx.block()) as StatementNode;
const doWhile = new DoWhileNode(condition, block);
doWhile.location = Location.fromCtx(ctx);
return doWhile;
}

visitBlock(ctx: BlockContext): BlockNode {
const statements = ctx.statement_list().map((s) => this.visit(s) as StatementNode);
const block = new BlockNode(statements);
Expand Down
7 changes: 7 additions & 0 deletions packages/cashc/src/ast/AstTraversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
ConsoleStatementNode,
ConsoleParameterNode,
SliceNode,
DoWhileNode,
} from './AST.js';
import AstVisitor from './AstVisitor.js';

Expand Down Expand Up @@ -86,6 +87,12 @@ export default class AstTraversal extends AstVisitor<Node> {
return node;
}

visitDoWhile(node: DoWhileNode): Node {
node.condition = this.visit(node.condition);
node.block = this.visit(node.block) as StatementNode;
return node;
}

visitBlock(node: BlockNode): Node {
node.statements = this.visitOptionalList(node.statements) as StatementNode[];
return node;
Expand Down
2 changes: 2 additions & 0 deletions packages/cashc/src/ast/AstVisitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
NullaryOpNode,
ConsoleStatementNode,
SliceNode,
DoWhileNode,
} from './AST.js';

export default abstract class AstVisitor<T> {
Expand All @@ -39,6 +40,7 @@ export default abstract class AstVisitor<T> {
abstract visitTimeOp(node: TimeOpNode): T;
abstract visitRequire(node: RequireNode): T;
abstract visitBranch(node: BranchNode): T;
abstract visitDoWhile(node: DoWhileNode): T;
abstract visitBlock(node: BlockNode): T;
abstract visitCast(node: CastNode): T;
abstract visitFunctionCall(node: FunctionCallNode): T;
Expand Down
22 changes: 20 additions & 2 deletions packages/cashc/src/generation/GenerateTargetTraversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
ConsoleParameterNode,
ConsoleStatementNode,
SliceNode,
DoWhileNode,
} from '../ast/AST.js';
import AstTraversal from '../ast/AstTraversal.js';
import { GlobalFunction, Class } from '../ast/Globals.js';
Expand Down Expand Up @@ -197,7 +198,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal {

removeFinalVerifyFromFunction(functionBodyNode: Node): void {
// After EnsureFinalRequireTraversal, we know that the final opcodes are either
// "OP_VERIFY", "OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP" or "OP_ENDIF"
// "OP_VERIFY", "OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP", "OP_ENDIF" or "OP_UNTIL"

const finalOp = this.output.pop() as Op;
const { location, positionHint } = this.locationData.pop()!;
Expand All @@ -219,7 +220,7 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal {
this.emit(finalOp, { location, positionHint: PositionHint.END });

// At this point there is no verification value left on the stack:
// - scoped stack is cleared inside branch ended by OP_ENDIF
// - scoped stack is cleared inside block ended by OP_ENDIF or OP_UNTIL
// - OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP does not leave a verification value
// - OP_VERIFY does not leave a verification value
// so we add OP_1 to the script (indicating success)
Expand Down Expand Up @@ -381,6 +382,23 @@ export default class GenerateTargetTraversalWithLocation extends AstTraversal {
return node;
}

visitDoWhile(node: DoWhileNode): Node {
this.scopeDepth += 1;
this.emit(Op.OP_BEGIN, { location: node.location, positionHint: PositionHint.START });

const stackDepth = this.stack.length;
node.block = this.visit(node.block);
this.removeScopedVariables(stackDepth, node.block);

node.condition = this.visit(node.condition);
this.emit(Op.OP_NOT, { location: node.location, positionHint: PositionHint.END });

this.emit(Op.OP_UNTIL, { location: node.location, positionHint: PositionHint.END });
this.popFromStack();

return node;
}

removeScopedVariables(depthBeforeScope: number, node: Node): void {
const dropCount = this.stack.length - depthBeforeScope;
for (let i = 0; i < dropCount; i += 1) {
Expand Down
9 changes: 9 additions & 0 deletions packages/cashc/src/grammar/CashScript.g4
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ statement
| timeOpStatement
| requireStatement
| ifStatement
| loopStatement
| consoleStatement
;

Expand Down Expand Up @@ -79,6 +80,14 @@ ifStatement
: 'if' '(' expression ')' ifBlock=block ('else' elseBlock=block)?
;

loopStatement
: doWhileStatement
;

doWhileStatement
: 'do' block 'while' '(' expression ')' ';'
;

consoleStatement
: 'console.log' consoleParameterList ';'
;
Expand Down
Loading