Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: comptime expressions for message opcodes #1188

Merged
merged 3 commits into from
Dec 16, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The `SendDefaultMode` send mode constant to the standard library: PR [#1010](https://github.com/tact-lang/tact/pull/1010)
- The `replace` and `replaceGet` methods for the `Map` type: PR [#941](https://github.com/tact-lang/tact/pull/941)
- Utility for logging errors in code that was supposed to be unreachable: PR [#991](https://github.com/tact-lang/tact/pull/991)
- Ability to specify a compile-time message opcode expression: PR [#1188](https://github.com/tact-lang/tact/pull/1188)

### Changed

Expand Down
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
4 changes: 2 additions & 2 deletions src/grammar/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,8 @@ semantics.addOperation<AstNode>("astOfModuleItem", {
kind: "message_decl",
name: typeId.astOfType(),
fields: fields.astsOfList(),
opcode: unwrapOptNode(optIntMsgId, (number) =>
number.astOfExpression(),
opcode: unwrapOptNode(optIntMsgId, (msgId) =>
msgId.astOfExpression(),
),
loc: createRef(this),
});
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 {
}
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
Loading