diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d984b5f..3ab56004a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New CSpell dictionaries: TVM instructions and adjusted list of Fift words: PR [#881](https://github.com/tact-lang/tact/pull/881) - Docs: the `description` property to the frontmatter of the each page for better SEO: PR [#916](https://github.com/tact-lang/tact/pull/916) - Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921) +- Ability to specify a compile-time method ID expression for getters: PR [#922](https://github.com/tact-lang/tact/pull/922) ### Changed diff --git a/docs/src/content/docs/book/functions.mdx b/docs/src/content/docs/book/functions.mdx index ab96a9a98..1a29b2be9 100644 --- a/docs/src/content/docs/book/functions.mdx +++ b/docs/src/content/docs/book/functions.mdx @@ -3,6 +3,8 @@ title: Functions description: "Global, asm, native functions, as well as receivers, getters and storage functions, plus the many attributes that allow for great flexibility and expressivity of Tact language" --- +import { Badge } from '@astrojs/starlight/components'; + Functions in Tact could be defined in different ways: * Global static function @@ -128,6 +130,7 @@ contract Treasure { ## Getter Functions Getter functions define getters on smart contracts and can be defined only within a contract or trait. +Getter functions cannot be used to read some other contract's state: if you need to obtain some data you need to do that by sending a message with a request and define a receiver which would process the request answer. ```tact contract Treasure { @@ -136,3 +139,37 @@ contract Treasure { } } ``` + +### Explicit resolution of method ID collisions + +

+ +As other functions in TVM contracts, getters have their *unique* associated function selectors which are some integers ids (called *method IDs*). +Some of those integers are reserved for internal purposes, e.g. -4, -3, -2, -1, 0 are reserved IDs and +regular functions (internal to a contract and not callable from outside) are usually numbered by subsequent (small) integers starting from 1. +By default, getters have associated method IDs that are derived from their names using the [CRC16](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) algorithm as follows: +`crc16() & 0xffff) | 0x10000`. +Sometimes this can get you the same method ID for getters with different names. +If this happens, you can either rename some of the contract's getters or +specify the getter's method ID manually as a compile-time expression like so: + +```tact +contract ManualMethodId { + const methodId: Int = 16384 + 42; + + get(self.methodId) fun methodId1(): Int { + return self.methodId; + } + + get(crc32("crc32") + 42 & 0x3ffff | 0x4000) + fun methodId2(): Int { + return 0; + } +} +``` + +Note that you *cannot* use method IDs that are reserved by TVM and you cannot use some initial positive integers because those will be used as function selectors by the compiler. + +User-specified method IDs are 19-bit signed integers, so you can use integers from $-2^{18}$ to $-5$ and from $2^{14}$ to $2^{18} - 1$. + +Also, a few method IDs are reserved for the usage by the getters the Tact compiler can insert during compilation, those are 113617, 115390, 121275. diff --git a/knip.json b/knip.json index d268afa29..b6828108d 100644 --- a/knip.json +++ b/knip.json @@ -7,9 +7,5 @@ "src/prettyPrinter.ts", ".github/workflows/tact*.yml" ], - "ignoreDependencies": [ - "@tact-lang/ton-abi", - "@tact-lang/ton-jest", - "@types/jest" - ] + "ignoreDependencies": ["@tact-lang/ton-abi"] } diff --git a/src/bindings/writeTypescript.ts b/src/bindings/writeTypescript.ts index e2b5c6f7c..4f06b19f6 100644 --- a/src/bindings/writeTypescript.ts +++ b/src/bindings/writeTypescript.ts @@ -600,9 +600,19 @@ export function writeTypescript( writeArgumentToStack(a.name, a.type, w); } } - w.append( - `let source = (await provider.get('${g.name}', builder.build())).stack;`, - ); + if (g.methodId) { + // 'as any' is used because Sandbox contracts's getters can be called + // using the function name or the method id number + // but the ContractProvider's interface get methods can only + // take strings (function names) + w.append( + `let source = (await provider.get(${g.methodId} as any, builder.build())).stack;`, + ); + } else { + w.append( + `let source = (await provider.get('${g.name}', builder.build())).stack;`, + ); + } if (g.returnType) { writeGetParser("result", g.returnType, w); w.append(`return result;`); diff --git a/src/generator/createABI.ts b/src/generator/createABI.ts index 45f6ae6e1..503edfa1c 100644 --- a/src/generator/createABI.ts +++ b/src/generator/createABI.ts @@ -123,6 +123,7 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI { if (f.isGetter) { getters.push({ name: f.name, + methodId: f.methodId, arguments: f.params.map((v) => ({ name: idText(v.name), type: createABITypeRefFromTypeRef(ctx, v.type, v.loc), diff --git a/src/generator/writers/writeFunction.ts b/src/generator/writers/writeFunction.ts index 79cccaa43..980a57f9c 100644 --- a/src/generator/writers/writeFunction.ts +++ b/src/generator/writers/writeFunction.ts @@ -12,7 +12,6 @@ import { import { getType, resolveTypeRef } from "../../types/resolveDescriptors"; import { getExpType } from "../../types/resolveExpression"; import { FunctionDescription, TypeRef } from "../../types/types"; -import { getMethodId } from "../../utils/utils"; import { WriterContext } from "../Writer"; import { resolveFuncPrimitive } from "./resolveFuncPrimitive"; import { resolveFuncType } from "./resolveFuncType"; @@ -681,45 +680,45 @@ function writeNonMutatingFunction( }); } -export function writeGetter(f: FunctionDescription, ctx: WriterContext) { +export function writeGetter(f: FunctionDescription, wCtx: WriterContext) { // Render tensors - const self = f.self?.kind === "ref" ? getType(ctx.ctx, f.self.name) : null; + const self = f.self?.kind === "ref" ? getType(wCtx.ctx, f.self.name) : null; if (!self) { throw new Error(`No self type for getter ${idTextErr(f.name)}`); // Impossible } - ctx.append( - `_ %${f.name}(${f.params.map((v) => resolveFuncTupleType(v.type, ctx) + " " + funcIdOf(v.name)).join(", ")}) method_id(${getMethodId(f.name)}) {`, + wCtx.append( + `_ %${f.name}(${f.params.map((v) => resolveFuncTupleType(v.type, wCtx) + " " + funcIdOf(v.name)).join(", ")}) method_id(${f.methodId!}) {`, ); - ctx.inIndent(() => { + wCtx.inIndent(() => { // Unpack parameters for (const param of f.params) { unwrapExternal( funcIdOf(param.name), funcIdOf(param.name), param.type, - ctx, + wCtx, ); } // Load contract state - ctx.append(`var self = ${ops.contractLoad(self.name, ctx)}();`); + wCtx.append(`var self = ${ops.contractLoad(self.name, wCtx)}();`); // Execute get method - ctx.append( - `var res = self~${ctx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`, + wCtx.append( + `var res = self~${wCtx.used(ops.extension(self.name, f.name))}(${f.params.map((v) => funcIdOf(v.name)).join(", ")});`, ); // Pack if needed if (f.returns.kind === "ref") { - const t = getType(ctx.ctx, f.returns.name); + const t = getType(wCtx.ctx, f.returns.name); if (t.kind === "struct" || t.kind === "contract") { if (f.returns.optional) { - ctx.append( - `return ${ops.typeToOptExternal(t.name, ctx)}(res);`, + wCtx.append( + `return ${ops.typeToOptExternal(t.name, wCtx)}(res);`, ); } else { - ctx.append( - `return ${ops.typeToExternal(t.name, ctx)}(res);`, + wCtx.append( + `return ${ops.typeToExternal(t.name, wCtx)}(res);`, ); } return; @@ -727,8 +726,8 @@ export function writeGetter(f: FunctionDescription, ctx: WriterContext) { } // Return result - ctx.append(`return res;`); + wCtx.append(`return res;`); }); - ctx.append(`}`); - ctx.append(); + wCtx.append(`}`); + wCtx.append(); } diff --git a/src/grammar/__snapshots__/grammar.spec.ts.snap b/src/grammar/__snapshots__/grammar.spec.ts.snap index f3d715854..5bfd1e508 100644 --- a/src/grammar/__snapshots__/grammar.spec.ts.snap +++ b/src/grammar/__snapshots__/grammar.spec.ts.snap @@ -141,6 +141,17 @@ Line 1, col 20: " `; +exports[`grammar should fail contract-getter-parens-no-method-id 1`] = ` +":2:9: Parse error: expected "\\"", "initOf", "null", "_", "A".."Z", "a".."z", "false", "true", "0", "1".."9", "0O", "0o", "0B", "0b", "0X", "0x", "(", "~", "!", "+", or "-" + +Line 2, col 9: + 1 | contract Test { +> 2 | get() fun test(): Int { + ^ + 3 | return 0 +" +`; + exports[`grammar should fail contract-init-trailing-comma-empty-params 1`] = ` "Syntax error: :2:10: Empty parameter list should not have a dangling comma. Line 2, col 10: @@ -934,6 +945,133 @@ exports[`grammar should parse case-35 1`] = ` } `; +exports[`grammar should parse contract-getter-with-method-id 1`] = ` +{ + "id": 17, + "imports": [], + "items": [ + { + "attributes": [], + "declarations": [ + { + "attributes": [ + { + "loc": get(crc32("crc32") + 42 & 0x3ffff | 0x4000), + "methodId": { + "id": 10, + "kind": "op_binary", + "left": { + "id": 8, + "kind": "op_binary", + "left": { + "id": 6, + "kind": "op_binary", + "left": { + "args": [ + { + "id": 3, + "kind": "string", + "loc": "crc32", + "value": "crc32", + }, + ], + "function": { + "id": 2, + "kind": "id", + "loc": crc32, + "text": "crc32", + }, + "id": 4, + "kind": "static_call", + "loc": crc32("crc32"), + }, + "loc": crc32("crc32") + 42, + "op": "+", + "right": { + "base": 10, + "id": 5, + "kind": "number", + "loc": 42, + "value": 42n, + }, + }, + "loc": crc32("crc32") + 42 & 0x3ffff, + "op": "&", + "right": { + "base": 16, + "id": 7, + "kind": "number", + "loc": 0x3ffff, + "value": 262143n, + }, + }, + "loc": crc32("crc32") + 42 & 0x3ffff | 0x4000, + "op": "|", + "right": { + "base": 16, + "id": 9, + "kind": "number", + "loc": 0x4000, + "value": 16384n, + }, + }, + "type": "get", + }, + ], + "id": 15, + "kind": "function_def", + "loc": get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int { + return 0 + }, + "name": { + "id": 11, + "kind": "id", + "loc": test, + "text": "test", + }, + "params": [], + "return": { + "id": 12, + "kind": "type_id", + "loc": Int, + "text": "Int", + }, + "statements": [ + { + "expression": { + "base": 10, + "id": 13, + "kind": "number", + "loc": 0, + "value": 0n, + }, + "id": 14, + "kind": "statement_return", + "loc": return 0, + }, + ], + }, + ], + "id": 16, + "kind": "contract", + "loc": contract Test { + get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int { + return 0 + } +}, + "name": { + "id": 1, + "kind": "id", + "loc": Test, + "text": "Test", + }, + "traits": [], + }, + ], + "kind": "module", +} +`; + exports[`grammar should parse contract-optional-semicolon-for-last-const-def 1`] = ` { "id": 7, diff --git a/src/grammar/ast.ts b/src/grammar/ast.ts index c8cdfa6df..b9267b6d1 100644 --- a/src/grammar/ast.ts +++ b/src/grammar/ast.ts @@ -633,7 +633,7 @@ export type AstContractAttribute = { }; export type AstFunctionAttribute = - | { type: "get"; loc: SrcInfo } + | { type: "get"; methodId: AstExpression | null; loc: SrcInfo } | { type: "mutates"; loc: SrcInfo } | { type: "extends"; loc: SrcInfo } | { type: "virtual"; loc: SrcInfo } diff --git a/src/grammar/grammar.ohm b/src/grammar/grammar.ohm index 3b8dec037..d4f38897e 100644 --- a/src/grammar/grammar.ohm +++ b/src/grammar/grammar.ohm @@ -74,7 +74,8 @@ Tact { ContractAttribute = "@interface" "(" stringLiteral ")" --interface - FunctionAttribute = "get" --getter // 'get' cannot be a reserved word because there is the map '.get' method + // 'get' cannot be a reserved word because there is the map '.get' method + FunctionAttribute = "get" ("(" Expression ")")? --getter | mutates --mutates | extends --extends | virtual --virtual diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts index ef702b3c3..760d58b13 100644 --- a/src/grammar/grammar.ts +++ b/src/grammar/grammar.ts @@ -583,8 +583,12 @@ semantics.addOperation("astOfAsmInstruction", { }); semantics.addOperation("astOfFunctionAttributes", { - FunctionAttribute_getter(_) { - return { type: "get", loc: createRef(this) }; + FunctionAttribute_getter(_getKwd, _optLparen, optMethodId, _optRparen) { + return { + type: "get", + methodId: unwrapOptNode(optMethodId, (e) => e.astOfExpression()), + loc: createRef(this), + }; }, FunctionAttribute_extends(_) { return { type: "extends", loc: createRef(this) }; diff --git a/src/grammar/rename.ts b/src/grammar/rename.ts index 6d46105b5..7ec7523d0 100644 --- a/src/grammar/rename.ts +++ b/src/grammar/rename.ts @@ -15,6 +15,7 @@ import { AstFunctionDecl, AstConstantDecl, AstNode, + AstFunctionAttribute, } from "./ast"; import { dummySrcInfo } from "./grammar"; import { AstSorter } from "./sort"; @@ -266,8 +267,29 @@ export class AstRenamer { private renameFunctionContents( functionDef: AstFunctionDef, ): AstFunctionDef { + const attributes = this.renameFunctionAttributes( + functionDef.attributes, + ); const statements = this.renameStatements(functionDef.statements); - return { ...functionDef, statements }; + return { ...functionDef, attributes, statements }; + } + + /** + * Renames getter's methodId expression. + */ + private renameFunctionAttributes( + functionAttrs: AstFunctionAttribute[], + ): AstFunctionAttribute[] { + return functionAttrs.map((attr) => { + if (attr.type === "get" && attr.methodId !== null) { + return { + ...attr, + methodId: this.renameExpression(attr.methodId), + }; + } else { + return attr; + } + }); } /** @@ -403,7 +425,7 @@ export class AstRenamer { case "id": return { ...expr, - text: this.renamed.get(expr.text) ?? expr.text, + text: this.givenNames.get(expr.text) ?? expr.text, }; case "op_binary": return { diff --git a/src/grammar/test-failed/contract-getter-parens-no-method-id.tact b/src/grammar/test-failed/contract-getter-parens-no-method-id.tact new file mode 100644 index 000000000..f3de3915f --- /dev/null +++ b/src/grammar/test-failed/contract-getter-parens-no-method-id.tact @@ -0,0 +1,5 @@ +contract Test { + get() fun test(): Int { + return 0 + } +} diff --git a/src/grammar/test/contract-getter-with-method-id.tact b/src/grammar/test/contract-getter-with-method-id.tact new file mode 100644 index 000000000..2026cdaf4 --- /dev/null +++ b/src/grammar/test/contract-getter-with-method-id.tact @@ -0,0 +1,5 @@ +contract Test { + get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int { + return 0 + } +} diff --git a/src/prettyPrinter.ts b/src/prettyPrinter.ts index b70e69e01..0ac67d4d9 100644 --- a/src/prettyPrinter.ts +++ b/src/prettyPrinter.ts @@ -464,12 +464,22 @@ export class PrettyPrinter { `${this.ppAstId(arg.name)}: ${this.ppAstType(arg.type)}`, ) .join(", "); - const attrsRaw = attributes.map((attr) => attr.type).join(" "); + const attrsRaw = attributes + .map((attr) => this.ppAstFunctionAttribute(attr)) + .join(" "); const attrsFormatted = attrsRaw ? `${attrsRaw} ` : ""; const returnType = retTy ? `: ${this.ppAstType(retTy)}` : ""; return `${attrsFormatted}fun ${this.ppAstId(name)}(${argsFormatted})${returnType}`; } + ppAstFunctionAttribute(attr: AstFunctionAttribute): string { + if (attr.type === "get" && attr.methodId !== null) { + return `get(${this.ppAstExpression(attr.methodId)})`; + } else { + return attr.type; + } + } + ppAstReceiver(receive: AstReceiver): string { const header = this.ppAstReceiverHeader(receive); const stmtsFormatted = this.ppStatementBlock(receive.statements); diff --git a/src/storage/__snapshots__/resolveAllocation.spec.ts.snap b/src/storage/__snapshots__/resolveAllocation.spec.ts.snap index 274c24bff..79708c492 100644 --- a/src/storage/__snapshots__/resolveAllocation.spec.ts.snap +++ b/src/storage/__snapshots__/resolveAllocation.spec.ts.snap @@ -2258,6 +2258,7 @@ exports[`resolveAllocation should write program 1`] = ` "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "main", "origin": "user", "params": [ diff --git a/src/test/contracts/getter-with-method-id-const.tact b/src/test/contracts/getter-with-method-id-const.tact new file mode 100644 index 000000000..6780dc499 --- /dev/null +++ b/src/test/contracts/getter-with-method-id-const.tact @@ -0,0 +1,7 @@ +const FOO: Int = crc32("crc32") + 42; + +contract Test { + get(FOO) fun test(): Int { + return FOO; + } +} diff --git a/src/test/contracts/getter-with-method-id.tact b/src/test/contracts/getter-with-method-id.tact new file mode 100644 index 000000000..9857e558e --- /dev/null +++ b/src/test/contracts/getter-with-method-id.tact @@ -0,0 +1,6 @@ +contract Test { + get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun test(): Int { + return 0; + } +} + diff --git a/src/test/contracts/renamer-expected/case-4.tact b/src/test/contracts/renamer-expected/case-4.tact index a6f007b01..28dbc9e1e 100644 --- a/src/test/contracts/renamer-expected/case-4.tact +++ b/src/test/contracts/renamer-expected/case-4.tact @@ -37,10 +37,10 @@ contract contract_6 { fun function_def_8() { let d: Int? = null; self.a = 10; - d = a > 0 ? self.a : 0; + d = constant_def_2 > 0 ? self.a : 0; let res: Bool = isZero(1, 2, false, self.c); let e = 42; - self.b = a; + self.b = constant_def_2; self.c = Source{a: 10, b: 20}; } diff --git a/src/test/contracts/renamer-expected/getter-with-method-id-const.tact b/src/test/contracts/renamer-expected/getter-with-method-id-const.tact new file mode 100644 index 000000000..e842470d9 --- /dev/null +++ b/src/test/contracts/renamer-expected/getter-with-method-id-const.tact @@ -0,0 +1,7 @@ +const constant_def_0: Int = crc32("crc32") + 42; + +contract contract_1 { + get(constant_def_0) fun function_def_2(): Int { + return constant_def_0; + } +} diff --git a/src/test/contracts/renamer-expected/getter-with-method-id.tact b/src/test/contracts/renamer-expected/getter-with-method-id.tact new file mode 100644 index 000000000..2d8e63ff3 --- /dev/null +++ b/src/test/contracts/renamer-expected/getter-with-method-id.tact @@ -0,0 +1,5 @@ +contract contract_0 { + get(crc32("crc32") + 42 & 0x3ffff | 0x4000) fun function_def_1(): Int { + return 0; + } +} diff --git a/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap b/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap index 9d3c1e7d9..33a4b617f 100644 --- a/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap +++ b/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap @@ -115,6 +115,7 @@ exports[`local-type-inference should automatically set types for let statements "getters": [ { "arguments": [], + "methodId": 70304, "name": "test1", "returnType": { "format": 257, @@ -125,6 +126,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 74435, "name": "test2", "returnType": { "format": 257, @@ -135,6 +137,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 78562, "name": "test3", "returnType": { "kind": "simple", @@ -144,6 +147,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 82437, "name": "test4", "returnType": { "kind": "simple", @@ -153,6 +157,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 86564, "name": "test5", "returnType": { "kind": "simple", @@ -162,6 +167,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 90695, "name": "test6", "returnType": { "kind": "simple", @@ -171,6 +177,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 94822, "name": "test7", "returnType": { "kind": "simple", @@ -180,6 +187,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 99209, "name": "test8", "returnType": { "kind": "simple", @@ -189,6 +197,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 103336, "name": "test9", "returnType": { "kind": "simple", @@ -198,6 +207,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 107552, "name": "test10", "returnType": { "kind": "simple", @@ -207,6 +217,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 111617, "name": "test11", "returnType": { "kind": "simple", @@ -216,6 +227,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 99426, "name": "test12", "returnType": { "key": "int", @@ -225,6 +237,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 103491, "name": "test13", "returnType": { "key": "int", @@ -235,6 +248,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 124068, "name": "test14", "returnType": { "kind": "simple", @@ -244,6 +258,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 128133, "name": "test15", "returnType": { "kind": "simple", @@ -253,6 +268,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 115942, "name": "test16", "returnType": { "format": 257, @@ -263,6 +279,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 120007, "name": "test17", "returnType": { "format": 257, @@ -273,6 +290,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 75048, "name": "test18", "returnType": { "format": 257, @@ -283,6 +301,7 @@ exports[`local-type-inference should automatically set types for let statements }, { "arguments": [], + "methodId": 79113, "name": "test19", "returnType": { "format": 257, diff --git a/src/test/e2e-emulated/contracts/getters.tact b/src/test/e2e-emulated/contracts/getters.tact index bbc89b550..18e5d5026 100644 --- a/src/test/e2e-emulated/contracts/getters.tact +++ b/src/test/e2e-emulated/contracts/getters.tact @@ -10,6 +10,8 @@ message SetIdAndData { data: Cell; } +const METHOD_ID: Int = 16384; + contract Test with Deployable { id: Int as uint32 = 0; anotherData: Cell; @@ -54,4 +56,19 @@ contract Test with Deployable { get fun contractAsInput(test: Test): Test { return test; } + + // method ids are 19 bit signed integers + // so we truncate the result with a (18-bit) bit mask + get((crc32("methodId") + 42) & 0b111111111111111111) fun methodIdExpr(): Bool { + return true; + } + get(METHOD_ID) fun methodIdConst(): Int { + return METHOD_ID; + } + get(-pow2(18)) fun methodIdMin(): Bool { + return true; + } + get(pow2(18) - 1) fun methodIdMax(): Bool { + return true; + } } \ No newline at end of file diff --git a/src/test/e2e-emulated/getters.spec.ts b/src/test/e2e-emulated/getters.spec.ts index 8719f19cf..341307ed4 100644 --- a/src/test/e2e-emulated/getters.spec.ts +++ b/src/test/e2e-emulated/getters.spec.ts @@ -78,5 +78,10 @@ describe("getters", () => { anotherData: beginCell().storeUint(123, 64).endCell(), }), ).toMatchSnapshot(); + + expect(await contract.getMethodIdExpr()).toBe(true); + expect(await contract.getMethodIdConst()).toBe(2n ** 14n); + expect(await contract.getMethodIdMin()).toBe(true); + expect(await contract.getMethodIdMax()).toBe(true); }); }); diff --git a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap index dd0d9ca5d..8437d6927 100644 --- a/src/types/__snapshots__/resolveDescriptors.spec.ts.snap +++ b/src/types/__snapshots__/resolveDescriptors.spec.ts.snap @@ -328,39 +328,6 @@ Line 4, col 17: " `; -exports[`resolveDescriptors should fail descriptors for getter-collision 1`] = ` -":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1' -Pick a different getter name to avoid collisions -Line 8, col 13: - 7 | -> 8 | get fun pko() { - ^~~ - 9 | return; -" -`; - -exports[`resolveDescriptors should fail descriptors for getter-collision-trait 1`] = ` -":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1' -Pick a different getter name to avoid collisions -Line 8, col 13: - 7 | -> 8 | get fun pko() { - ^~~ - 9 | return; -" -`; - -exports[`resolveDescriptors should fail descriptors for getter-collision-with-trait 1`] = ` -":4:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1' -Pick a different getter name to avoid collisions -Line 4, col 13: - 3 | trait T { -> 4 | get fun pko() { - ^~~ - 5 | return; -" -`; - exports[`resolveDescriptors should fail descriptors for getter-outside-contract 1`] = ` ":8:1: Getters must be defined within a contract Line 8, col 1: @@ -761,6 +728,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-extends-fun 1`] = "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "loadRef", "origin": "user", "params": [], @@ -920,6 +888,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-no-arg-shuffl "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "foo", "origin": "user", "params": [ @@ -1064,6 +1033,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-no-ret-shuffl "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "foo", "origin": "user", "params": [ @@ -1188,6 +1158,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-no-shuffle 2` "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "foo", "origin": "user", "params": [ @@ -1485,6 +1456,7 @@ exports[`resolveDescriptors should resolve descriptors for asm-fun-shuffle-pair "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "foo", "origin": "user", "params": [ @@ -4428,6 +4400,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -4475,6 +4448,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -4501,6 +4475,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "getter", "origin": "user", "params": [], @@ -4574,6 +4549,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -4642,6 +4618,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -4682,6 +4659,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "isMutating": true, "isOverride": true, "isVirtual": false, + "methodId": null, "name": "getter", "origin": "user", "params": [], @@ -4763,6 +4741,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -4810,6 +4789,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -4836,6 +4816,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "getter", "origin": "user", "params": [], @@ -4980,6 +4961,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -5041,6 +5023,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -5081,6 +5064,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "isMutating": true, "isOverride": false, "isVirtual": true, + "methodId": null, "name": "getter", "origin": "user", "params": [], @@ -5154,6 +5138,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -5222,6 +5207,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -5262,6 +5248,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "isMutating": true, "isOverride": true, "isVirtual": false, + "methodId": null, "name": "getter", "origin": "user", "params": [], @@ -5343,6 +5330,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -5404,6 +5392,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr }, { "loc": get, + "methodId": null, "type": "get", }, ], @@ -5444,6 +5433,7 @@ exports[`resolveDescriptors should resolve descriptors for contract-getter-overr "isMutating": true, "isOverride": false, "isVirtual": true, + "methodId": null, "name": "getter", "origin": "user", "params": [], @@ -5618,6 +5608,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test_extends", "origin": "user", "params": [ @@ -5734,6 +5725,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test_extends_self", "origin": "user", "params": [ @@ -5854,6 +5846,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test_mutates", "origin": "user", "params": [ @@ -5974,6 +5967,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test_mutates_self", "origin": "user", "params": [ @@ -6097,6 +6091,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test_mutates_self_opt", "origin": "user", "params": [ @@ -6227,6 +6222,7 @@ exports[`resolveDescriptors should resolve descriptors for fun-extends-opt-self "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test", "origin": "user", "params": [ @@ -6583,6 +6579,7 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "hello", "origin": "user", "params": [], @@ -6619,6 +6616,7 @@ exports[`resolveDescriptors should resolve descriptors for init-vars-analysis-un "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "hello2", "origin": "user", "params": [], @@ -6837,6 +6835,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "testFunc", "origin": "user", "params": [], @@ -6884,6 +6883,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "testFunc2", "origin": "user", "params": [], @@ -6964,6 +6964,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "testFunc3", "origin": "user", "params": [ @@ -7274,6 +7275,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "attributes": [ { "loc": get, + "methodId": null, "type": "get", }, ], @@ -7314,6 +7316,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "attributes": [ { "loc": get, + "methodId": null, "type": "get", }, ], @@ -7424,6 +7427,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "attributes": [ { "loc": get, + "methodId": null, "type": "get", }, ], @@ -7466,6 +7470,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "hello", "origin": "user", "params": [], @@ -7485,6 +7490,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "attributes": [ { "loc": get, + "methodId": null, "type": "get", }, ], @@ -7527,6 +7533,7 @@ exports[`resolveDescriptors should resolve descriptors for item-funs-with-errors "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "hello2", "origin": "user", "params": [], @@ -7764,6 +7771,7 @@ exports[`resolveDescriptors should resolve descriptors for item-method 1`] = ` "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "inc", "origin": "user", "params": [], @@ -8010,6 +8018,7 @@ native sample(a: Int): Int;, "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "sample", "origin": "user", "params": [ @@ -8116,6 +8125,7 @@ mutates extends native inc(self: Int): Int;, "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "inc", "origin": "user", "params": [], @@ -8322,6 +8332,7 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1` "attributes": [ { "loc": get, + "methodId": null, "type": "get", }, ], @@ -8649,6 +8660,7 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1` "attributes": [ { "loc": get, + "methodId": null, "type": "get", }, ], @@ -8890,6 +8902,7 @@ exports[`resolveDescriptors should resolve descriptors for map-value-as-coins 1` "isMutating": true, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "test", "origin": "user", "params": [], @@ -9070,6 +9083,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = ` "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "scopeUntil", "origin": "user", "params": [], @@ -9169,6 +9183,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = ` "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "scopeRepeat", "origin": "user", "params": [], @@ -9267,6 +9282,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = ` "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "scopeWhile", "origin": "user", "params": [], @@ -9367,6 +9383,7 @@ exports[`resolveDescriptors should resolve descriptors for scope-loops 2`] = ` "isMutating": false, "isOverride": false, "isVirtual": false, + "methodId": null, "name": "scopeIf", "origin": "user", "params": [], diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap index 6e92c641e..f83f3b84b 100644 --- a/src/types/__snapshots__/resolveStatements.spec.ts.snap +++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap @@ -90,6 +90,26 @@ Line 23, col 22: " `; +exports[`resolveStatements should fail statements for contract-getter-with-method-id-1 1`] = ` +":5:9: Invalid type "String" for binary operator "+" +Line 5, col 9: + 4 | contract Test { +> 5 | get(crc32("crc32") + "42") fun test(): Int { + ^~~~~~~~~~~~~~~~~~~~~ + 6 | return 0 +" +`; + +exports[`resolveStatements should fail statements for contract-getter-with-method-id-2 1`] = ` +":5:9: Getter's method id expression must be of type "Int" but it has type "Bool" +Line 5, col 9: + 4 | contract Test { +> 5 | get(true) fun test(): Int { + ^~~~ + 6 | return 0 +" +`; + exports[`resolveStatements should fail statements for contract-initof-wrong-arg 1`] = ` ":26:25: Invalid type "String" for argument "owner" Line 26, col 25: @@ -535,6 +555,39 @@ Line 13, col 12: " `; +exports[`resolveStatements should fail statements for getter-collision 1`] = ` +":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1' +Pick a different getter name or explicit method ID to avoid collisions +Line 8, col 13: + 7 | +> 8 | get fun pko() { + ^~~ + 9 | return; +" +`; + +exports[`resolveStatements should fail statements for getter-collision-trait 1`] = ` +":8:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1' +Pick a different getter name or explicit method ID to avoid collisions +Line 8, col 13: + 7 | +> 8 | get fun pko() { + ^~~ + 9 | return; +" +`; + +exports[`resolveStatements should fail statements for getter-collision-with-trait 1`] = ` +":4:13: Method ID collision: getter 'pko' has the same method ID 103057 as getter 'getter1' +Pick a different getter name or explicit method ID to avoid collisions +Line 4, col 13: + 3 | trait T { +> 4 | get fun pko() { + ^~~ + 5 | return; +" +`; + exports[`resolveStatements should fail statements for init-vars-analysis-if 1`] = ` ":10:5: Field "value" is not set Line 10, col 5: @@ -1580,6 +1633,53 @@ exports[`resolveStatements should resolve statements for assign-self-mutating-me ] `; +exports[`resolveStatements should resolve statements for contract-getter-with-method-id-1 1`] = ` +[ + [ + "0", + "Int", + ], +] +`; + +exports[`resolveStatements should resolve statements for contract-getter-with-method-id-2 1`] = ` +[ + [ + "16384", + "Int", + ], + [ + "MethodId", + "Int", + ], +] +`; + +exports[`resolveStatements should resolve statements for contract-getter-with-method-id-3 1`] = ` +[ + [ + "16384", + "Int", + ], + [ + "41", + "Int", + ], + [ + "16384 + 41", + "Int", + ], + [ + "self", + "ManualMethodId", + ], + [ + "self.methodId", + "Int", + ], +] +`; + exports[`resolveStatements should resolve statements for contract-receiver-bounced 1`] = ` [ [ diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts index 1247ba6be..f6eaafa3e 100644 --- a/src/types/resolveDescriptors.ts +++ b/src/types/resolveDescriptors.ts @@ -52,7 +52,6 @@ import { ItemOrigin } from "../grammar/grammar"; import { getExpType, resolveExpression } from "./resolveExpression"; import { emptyContext } from "./resolveStatements"; import { isAssignable } from "./subtyping"; -import { getMethodId } from "../utils/utils"; const store = createContextStore(); const staticFunctionsStore = createContextStore(); @@ -1003,6 +1002,7 @@ export function resolveDescriptors(ctx: CompilerContext) { isOverride: !!isOverride, isInline: !!isInline, isAbstract: !!isAbstract, + methodId: null, }; } @@ -1947,25 +1947,6 @@ export function resolveDescriptors(ctx: CompilerContext) { staticConstants.set(idText(a.name), buildConstantDescription(a)); } - // Check for collisions in getter method IDs - for (const t of types.values()) { - const methodIds: Map = new Map(); - for (const f of t.functions.values()) { - if (f.isGetter) { - const methodId = getMethodId(f.name); - const existing = methodIds.get(methodId); - if (existing) { - throwCompilationError( - `Method ID collision: getter '${f.name}' has the same method ID ${methodId} as getter '${existing}'\nPick a different getter name to avoid collisions`, - f.ast.name.loc, - ); - } else { - methodIds.set(methodId, f.name); - } - } - } - } - // // Register types and functions in context // diff --git a/src/types/resolveStatements.ts b/src/types/resolveStatements.ts index 91ff414dd..81ce982e9 100644 --- a/src/types/resolveStatements.ts +++ b/src/types/resolveStatements.ts @@ -15,6 +15,7 @@ import { isAssignable } from "./subtyping"; import { idTextErr, throwCompilationError, + throwConstEvalError, throwInternalCompilerError, } from "../errors"; import { @@ -26,7 +27,10 @@ import { getAllTypes, } from "./resolveDescriptors"; import { getExpType, resolveExpression } from "./resolveExpression"; -import { printTypeRef, TypeRef } from "./types"; +import { FunctionDescription, printTypeRef, TypeRef } from "./types"; +import { evalConstantExpression } from "../constEval"; +import { ensureInt } from "../interpreter"; +import { crc16 } from "../utils/crc16"; export type StatementContext = { root: SrcInfo; @@ -856,6 +860,7 @@ export function resolveStatements(ctx: CompilerContext) { } // Process functions + const methodIds: Map = new Map(); for (const f of t.functions.values()) { if ( f.ast.kind !== "native_function_decl" && @@ -864,12 +869,29 @@ export function resolveStatements(ctx: CompilerContext) { ) { // Build statement context let sctx = emptyContext(f.ast.loc, f.name, f.returns); + if (f.self === null) { throwInternalCompilerError( "Self is null where it should not be", ); } sctx = addVariable(selfId, f.self, ctx, sctx); + + // Check for collisions in getter method IDs + if (f.isGetter) { + const methodId = getMethodId(f, ctx, sctx); + const existing = methodIds.get(methodId); + if (existing) { + throwCompilationError( + `Method ID collision: getter '${f.name}' has the same method ID ${methodId} as getter '${existing}'\nPick a different getter name or explicit method ID to avoid collisions`, + f.ast.name.loc, + ); + } else { + f.methodId = methodId; + methodIds.set(methodId, f.name); + } + } + for (const a of f.params) { sctx = addVariable(a.name, a.type, ctx, sctx); } @@ -881,3 +903,63 @@ export function resolveStatements(ctx: CompilerContext) { return ctx; } + +function getMethodId( + funcDescr: FunctionDescription, + ctx: CompilerContext, + sctx: StatementContext, +): number { + const optMethodId = funcDescr.ast.attributes.find( + (attr) => attr.type === "get", + )?.methodId; + + if (optMethodId) { + ctx = resolveExpression(optMethodId, sctx, ctx); + const ty = getExpType(ctx, optMethodId); + if (!(ty.kind === "ref" && ty.name === "Int")) { + throwCompilationError( + `Getter's method id expression must be of type "Int" but it has type "${printTypeRef(ty)}"`, + optMethodId.loc, + ); + } + + const methodId = ensureInt( + evalConstantExpression(optMethodId, ctx), + optMethodId.loc, + ); + // method ids are 19-bit signed integers + if (methodId < -(2n ** 18n) || methodId >= 2n ** 18n) { + throwConstEvalError( + "method ids must fit 19-bit signed integer range", + true, + optMethodId!.loc, + ); + } + // method ids -4, -3, -2, -1, 0 ... 2^14 - 1 (inclusive) are kind of reserved by TVM + // for the upper bound see F12_n (CALL) TVM instruction + // and many small ids will be taken by internal procedures + // + // also, some ids are taken by the getters generated by Tact: + // supported_interfaces -> 113617 + // lazy_deployment_completed -> 115390 + // get_abi_ipfs -> 121275 + if (-4n <= methodId && methodId < 2n ** 14n) { + throwConstEvalError( + "method ids cannot overlap with the TVM reserved ids: -4, -3, -2, -1, 0 ... 2^14 - 1", + true, + optMethodId!.loc, + ); + } + const tactGeneratedGetterMethodIds = [113617n, 115390n, 121275n]; + if (tactGeneratedGetterMethodIds.includes(methodId)) { + throwConstEvalError( + `method ids cannot overlap with Tact reserved method ids: ${tactGeneratedGetterMethodIds.map((n) => n.toString()).join(", ")}`, + true, + optMethodId!.loc, + ); + } + return Number(methodId); + } else { + return (crc16(funcDescr.name) & 0xffff) | 0x10000; + } +} diff --git a/src/types/stmts-failed/contract-getter-with-method-id-1.tact b/src/types/stmts-failed/contract-getter-with-method-id-1.tact new file mode 100644 index 000000000..6a91191b2 --- /dev/null +++ b/src/types/stmts-failed/contract-getter-with-method-id-1.tact @@ -0,0 +1,8 @@ +primitive Int; +trait BaseTrait { } + +contract Test { + get(crc32("crc32") + "42") fun test(): Int { + return 0 + } +} diff --git a/src/types/stmts-failed/contract-getter-with-method-id-2.tact b/src/types/stmts-failed/contract-getter-with-method-id-2.tact new file mode 100644 index 000000000..0d9de630c --- /dev/null +++ b/src/types/stmts-failed/contract-getter-with-method-id-2.tact @@ -0,0 +1,8 @@ +primitive Int; +trait BaseTrait { } + +contract Test { + get(true) fun test(): Int { + return 0 + } +} diff --git a/src/types/test-failed/getter-collision-trait.tact b/src/types/stmts-failed/getter-collision-trait.tact similarity index 100% rename from src/types/test-failed/getter-collision-trait.tact rename to src/types/stmts-failed/getter-collision-trait.tact diff --git a/src/types/test-failed/getter-collision-with-trait.tact b/src/types/stmts-failed/getter-collision-with-trait.tact similarity index 100% rename from src/types/test-failed/getter-collision-with-trait.tact rename to src/types/stmts-failed/getter-collision-with-trait.tact diff --git a/src/types/test-failed/getter-collision.tact b/src/types/stmts-failed/getter-collision.tact similarity index 100% rename from src/types/test-failed/getter-collision.tact rename to src/types/stmts-failed/getter-collision.tact diff --git a/src/types/stmts/contract-getter-with-method-id-1.tact b/src/types/stmts/contract-getter-with-method-id-1.tact new file mode 100644 index 000000000..d66b5bdc7 --- /dev/null +++ b/src/types/stmts/contract-getter-with-method-id-1.tact @@ -0,0 +1,8 @@ +primitive Int; +trait BaseTrait { } + +contract Test { + get((crc32("crc32") + 42) & 0x3FFFF | 0x4000) fun test(): Int { + return 0 + } +} diff --git a/src/types/stmts/contract-getter-with-method-id-2.tact b/src/types/stmts/contract-getter-with-method-id-2.tact new file mode 100644 index 000000000..af00be486 --- /dev/null +++ b/src/types/stmts/contract-getter-with-method-id-2.tact @@ -0,0 +1,10 @@ +primitive Int; +trait BaseTrait { } + +const MethodId: Int = 16384; + +contract Test { + get(MethodId) fun test(): Int { + return MethodId + } +} diff --git a/src/types/stmts/contract-getter-with-method-id-3.tact b/src/types/stmts/contract-getter-with-method-id-3.tact new file mode 100644 index 000000000..9b3c54d1a --- /dev/null +++ b/src/types/stmts/contract-getter-with-method-id-3.tact @@ -0,0 +1,9 @@ +primitive Int; +trait BaseTrait { } + +contract ManualMethodId { + const methodId: Int = 16384 + 41; + get(self.methodId + 1) fun methodId(): Int { + return self.methodId; + } +} diff --git a/src/types/types.ts b/src/types/types.ts index f82fbc70e..8b34e74cf 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -144,6 +144,7 @@ export type FunctionDescription = { name: string; origin: ItemOrigin; isGetter: boolean; + methodId: number | null; isMutating: boolean; isOverride: boolean; isVirtual: boolean; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d88294dc8..8d3a45ec9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,3 @@ -import { crc16 } from "./crc16"; import { throwInternalCompilerError } from "../errors"; export function topologicalSort(src: T[], references: (src: T) => T[]) { @@ -24,7 +23,3 @@ export function topologicalSort(src: T[], references: (src: T) => T[]) { } return result; } - -export function getMethodId(name: string) { - return (crc16(name) & 0xffff) | 0x10000; -}