Skip to content

Commit

Permalink
feat: comptime expressions for message opcodes
Browse files Browse the repository at this point in the history
  • Loading branch information
anton-trunov committed Dec 16, 2024
1 parent 903a911 commit 5e551a1
Show file tree
Hide file tree
Showing 23 changed files with 532 additions and 37 deletions.
10 changes: 10 additions & 0 deletions docs/src/content/docs/book/structs-and-messages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ message Add {
}
```

### Message opcodes

Messages are almost the same thing as [Structs](#structs) with the only difference that Messages have a 32-bit integer header in their serialization containing their unique numeric id, commonly referred to as an _opcode_ (operation code). This allows Messages to be used with [receivers](/book/receive) since the contract can tell different types of messages apart based on this id.

Tact automatically generates those unique ids (opcodes) for every received Message, but this can be manually overwritten:
Expand All @@ -114,6 +116,14 @@ message(0x7362d09c) TokenNotification {

This is useful for cases where you want to handle certain opcodes of a given smart contract, such as [Jetton standard](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md). The short-list of opcodes this contract is able to process is [given here in FunC](https://github.com/ton-blockchain/token-contract/blob/main/ft/op-codes.fc). They serve as an interface to the smart contract.

A message opcode can be any compile-time (constant) expression which evaluates to a strictly positive integer that fits into 32-bits, so the following is also a valid message declaration:

```tact
message((crc32("Tact") + 42) & 0xFFFF_FFFF) MsgWithExprOpcode {
field: Int as uint4;
}
```

:::note

For more in-depth information on this see:\
Expand Down
10 changes: 0 additions & 10 deletions src/grammar/__snapshots__/grammar.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -734,16 +734,6 @@ Line 2, col 13:
"
`;
exports[`grammar should fail message-negative-opcode 1`] = `
"<unknown>:1:9: Parse error: expected "0", "1".."9", "0O", "0o", "0B", "0b", "0X", or "0x"
Line 1, col 9:
> 1 | message(-1) Foo { }
^
2 |
"
`;
exports[`grammar should fail struct-double-semicolon 1`] = `
"<unknown>:2:19: Parse error: expected "}"
Expand Down
2 changes: 1 addition & 1 deletion src/grammar/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export type AstStructDecl = {
export type AstMessageDecl = {
kind: "message_decl";
name: AstId;
opcode: AstNumber | null;
opcode: AstExpression | null;
fields: AstFieldDecl[];
id: number;
loc: SrcInfo;
Expand Down
2 changes: 1 addition & 1 deletion src/grammar/grammar.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Tact {
ConstantDeclaration = ConstantAttribute* const id ":" Type (";" | &"}")

StructDecl = "struct" typeId "{" StructFields "}" --regular
| "message" ("(" integerLiteral ")")? typeId "{" StructFields "}" --message
| "message" ("(" Expression ")")? typeId "{" StructFields "}" --message

StructField = FieldDecl
StructFields = ListOf<StructField, ";"> ";"?
Expand Down
2 changes: 1 addition & 1 deletion src/grammar/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export class AstHasher {

private hashMessageDecl(node: AstMessageDecl): string {
const fieldsHash = this.hashFields(node.fields);
return `message|${fieldsHash}|${node.opcode?.value}`;
return `message|${fieldsHash}|${node.opcode ? this.hash(node.opcode) : "null"}`;
}

private hashFunctionDef(node: AstFunctionDef): string {
Expand Down
3 changes: 3 additions & 0 deletions src/grammar/iterators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export function traverse(node: AstNode, callback: (node: AstNode) => void) {
case "struct_decl":
case "message_decl":
traverse(node.name, callback);
if (node.kind === "message_decl" && node.opcode !== null) {
traverse(node.opcode, callback);
}
node.fields.forEach((e) => {
traverse(e, callback);
});
Expand Down
7 changes: 7 additions & 0 deletions src/grammar/rename.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,14 @@ export class AstRenamer {
private renameModuleItemContents(item: AstModuleItem): AstModuleItem {
switch (item.kind) {
case "struct_decl":
return item;
case "message_decl":
if (item.opcode !== null) {
return {
...item,
opcode: this.renameExpression(item.opcode),
};
}
return item;
case "function_def":
return this.renameFunctionContents(item as AstFunctionDef);
Expand Down
2 changes: 1 addition & 1 deletion src/prettyPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ export const ppAstMessage: Printer<A.AstMessageDecl> =
({ name, opcode, fields }) =>
(c) => {
const prefixCode =
opcode !== null ? `(${A.astNumToString(opcode)})` : "";
opcode !== null ? `(${ppAstExpression(opcode)})` : "";

return c.concat([
c.row(`message${prefixCode} ${ppAstId(name)} `),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
message(1) Msg1 {}
message(1) Msg2 {}
message(1 + 0) Msg2 {}

contract Test {
receive(msg: Msg1) { }
Expand Down
6 changes: 6 additions & 0 deletions src/test/contracts/case-message-opcode.tact
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,11 @@ message MyMessageAuto {
value: Int;
}

const DEADBEEF: Int = 0xdeadbeef;

message(DEADBEEF + 1) MyMessageWithExprOpcode {
a: Int;
}

contract TestContract {
}
8 changes: 7 additions & 1 deletion src/test/contracts/renamer-expected/case-message-opcode.tact
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,11 @@ message message_decl_4 {
value: Int;
}

contract contract_5 {
message(constant_def_5 + 1) message_decl_6 {
a: Int;
}

const constant_def_5: Int = 0xdeadbeef;

contract contract_7 {
}
2 changes: 1 addition & 1 deletion src/test/e2e-emulated/contracts/structs.tact
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct IntFields {
i257: Int as int257;
}

message(0xea01f46a) UintFields {
message(0xea01f469 + 1) UintFields {
u1: Int as uint1;
u2: Int as uint2;
u3: Int as uint3;
Expand Down
Loading

0 comments on commit 5e551a1

Please sign in to comment.