diff --git a/docs/src/content/docs/ref/core-debug.mdx b/docs/src/content/docs/ref/core-debug.mdx index c7157c8ece..60398b3cbc 100644 --- a/docs/src/content/docs/ref/core-debug.mdx +++ b/docs/src/content/docs/ref/core-debug.mdx @@ -17,15 +17,9 @@ fun require(condition: Bool, error: String); Checks the `condition` and throws an error with an [exit code](/book/exit-codes) generated from the `error` message if the `condition` is `false{:tact}`. Otherwise, does nothing. -This function is similar to [`throwUnless(){:tact}`](#throwunless), but instead of using the error code directly, it generates it based on the given error message [`String{:tact}`][p]. +This function is similar to [`throwUnless(){:tact}`](#throwunless), but instead of using the error code directly, it generates it sequentially starting from 1024 and incrementing it by one based on the number of previously generated exit codes. -The algorithm for generating the exit code works as follows: - -* First, the [SHA-256](https://en.wikipedia.org/wiki/SHA-2#Hash_standard) hash of the `error` message [`String{:tact}`][p] is obtained. -* Then, its value is read as a 32-bit [big-endian](https://en.wikipedia.org/wiki/Endianness) number modulo 63000 plus 1000, in that order. -* Finally, it's put into the [`.md` compilation report file](/book/compile#report), which resides with the other compilation artifacts in your project's `outputs/` or `build/` directories. - -The generated exit code is guaranteed to be outside the common 0–255 range reserved for TVM and Tact contract errors, which makes it possible to distinguish exit codes from `require(){:tact}` and any other [standard exit codes](/book/exit-codes). +Thus, the generated exit code is guaranteed to be outside the common 0–255 range reserved for TVM and Tact contract errors, which makes it possible to distinguish exit codes from `require(){:tact}` and any other [standard exit codes](/book/exit-codes). Usage examples: @@ -42,6 +36,20 @@ try { } ``` +:::tip[Before Tact 1.6.2] + + In previous versions of Tact, the exit code was generated from the given error message [`String{:tact}`][p]. The algorithm for this worked as follows: + + * First, the [SHA-256](https://en.wikipedia.org/wiki/SHA-2#Hash_standard) hash of the `error` message [`String{:tact}`][p] was obtained. + * Then, its value was read as a 32-bit [big-endian](https://en.wikipedia.org/wiki/Endianness) number modulo 63000 plus 1000, in that order. + * Finally, it was put into the [`.md` compilation report file](/book/compile#report), which resides with the other compilation artifacts in your project's `outputs/` or `build/` directories. + + The generated exit code was guaranteed to be outside the common 0–255 range reserved for TVM and Tact contract errors, which made it possible to distinguish exit codes from `require(){:tact}` and any other [standard exit codes](/book/exit-codes). + + Starting with Tact 1.6.2, the generated exit code is still placed in the compilation report and the guarantees of being outside the 0-255 range are still here. However, its generation algorithm has been drastically simplified to produce smaller values that save more gas when thrown. + +::: + ## dump

@@ -200,7 +208,7 @@ fun throwUnless(code: Int, condition: Bool); Similar to [`throw(){:tact}`](#throw), but throws an error `code` only if `condition` does **not** hold, i.e. if `condition` is not equal to `true{:tact}`. Doesn't throw otherwise. -This function is also similar to [`require(){:tact}`](#require), but uses the specified `code` directly instead of generating one based on a given error message [`String{:tact}`][p]. +This function is also similar to [`require(){:tact}`](#require), but uses the specified `code` directly instead of generating it sequentially. Attempts to specify a `code` outside the range $0-65535$ cause an exception with [exit code 5](/book/exit-codes#5): `Integer out of expected range`. diff --git a/src/stdlib/stdlib.ts b/src/stdlib/stdlib.ts index 5d45f18b25..62b606f099 100644 --- a/src/stdlib/stdlib.ts +++ b/src/stdlib/stdlib.ts @@ -1172,24 +1172,24 @@ files["std/internal/debug.tact"] = "dGlvbi4gQXZhaWxhYmxlIHNpbmNlIFRhY3QgMS42LjAuCi8vLwovLy8gU2ltaWxhciB0byBgdGhyb3coKWAsIGJ1dCB0aHJvd3MgYW4gZXJyb3IgYGNvZGVgIG9ubHkg" + "aWYgYGNvbmRpdGlvbmAgZG9lcyAqKm5vdCoqIGhvbGQsIGkuZS4gYGNvbmRpdGlvbmAgaXMgZXF1YWwgdG8gYHRydWV7OnRhY3R9YC4gRG9lc24ndCB0aHJvdyBvdGhl" + "cndpc2UuCi8vLwovLy8gVGhpcyBmdW5jdGlvbiBpcyBhbHNvIHNpbWlsYXIgdG8gYHJlcXVpcmUoKWAsIGJ1dCB1c2VzIHRoZSBzcGVjaWZpZWQgYGNvZGVgIGRpcmVj" + - "dGx5IGluc3RlYWQgb2YgZ2VuZXJhdGluZyBvbmUgYmFzZWQgb24gdGhlIGdpdmVuIGVycm9yIG1lc3NhZ2UgYFN0cmluZ2AuCi8vLwovLy8gQXR0ZW1wdHMgdG8gc3Bl" + - "Y2lmeSB0aGUgYGNvZGVgIG91dHNpZGUgb2YgMC02NTUzNSByYW5nZSBjYXVzZSBhbiBleGNlcHRpb24gd2l0aCBleGl0IGNvZGUgNTogYEludGVnZXIgb3V0IG9mIGV4" + - "cGVjdGVkIHJhbmdlYC4KLy8vCi8vLyBgYGB0YWN0Ci8vLyBjb250cmFjdCBPd25lcnNoaXAgewovLy8gICAgIG93bmVyOiBBZGRyZXNzOwovLy8KLy8vICAgICBpbml0" + - "KCkgewovLy8gICAgICAgICBzZWxmLm93bmVyID0gbXlBZGRyZXNzKCk7Ci8vLyAgICAgfQovLy8KLy8vICAgICByZWNlaXZlKCkgewovLy8gICAgICAgICAvLyBDaGVj" + - "ayB0aGUgc2VuZGVyIGlzIHRoZSBvd25lciBvZiB0aGUgY29udHJhY3QsCi8vLyAgICAgICAgIC8vIGFuZCB0aHJvdyBleGl0IGNvZGUgMTAyNCBpZiBpdCdzIG5vdAov" + - "Ly8gICAgICAgICB0aHJvd1VubGVzcygxMDI0LCBzZW5kZXIoKSA9PSBzZWxmLm93bmVyKTsKLy8vICAgICB9Ci8vLyB9Ci8vLyBgYGAKLy8vCi8vLyBTZWU6Ci8vLyAq" + - "IGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWRlYnVnLyN0aHJvd3VubGVzcwovLy8gKiBodHRwczovL2RvY3MudGFjdC1sYW5nLm9yZy9yZWYvY29y" + - "ZS1kZWJ1Zy8jcmVxdWlyZQovLy8gKiBodHRwczovL2RvY3MudGFjdC1sYW5nLm9yZy9ib29rL3N0YXRlbWVudHMvI3RyeS1jYXRjaAovLy8KQG5hbWUodGhyb3dfdW5s" + - "ZXNzKQpuYXRpdmUgdGhyb3dVbmxlc3MoY29kZTogSW50LCBjb25kaXRpb246IEJvb2wpOwoKLy8vIEdsb2JhbCBmdW5jdGlvbi4gKipEZXByZWNhdGVkKiogc2luY2Ug" + - "VGFjdCAxLjYuMC4KLy8vCi8vLyBVc2UgYHRocm93KClgIGluc3RlYWQuCi8vLwovLy8gU2VlOgovLy8gKiBodHRwczovL2RvY3MudGFjdC1sYW5nLm9yZy9yZWYvY29y" + - "ZS1kZWJ1Zy8jdGhyb3cKLy8vICogaHR0cHM6Ly9kb2NzLnRhY3QtbGFuZy5vcmcvcmVmL2NvcmUtZGVidWcvI25hdGl2ZXRocm93Ci8vLwpAbmFtZSh0aHJvdykKbmF0" + - "aXZlIG5hdGl2ZVRocm93KGNvZGU6IEludCk7CgovLy8gR2xvYmFsIGZ1bmN0aW9uLiAqKkRlcHJlY2F0ZWQqKiBzaW5jZSBUYWN0IDEuNi4wLgovLy8KLy8vIFVzZSBg" + - "dGhyb3dJZigpYCBpbnN0ZWFkLgovLy8KLy8vIFNlZToKLy8vICogaHR0cHM6Ly9kb2NzLnRhY3QtbGFuZy5vcmcvcmVmL2NvcmUtZGVidWcvI3Rocm93aWYKLy8vICog" + - "aHR0cHM6Ly9kb2NzLnRhY3QtbGFuZy5vcmcvcmVmL2NvcmUtZGVidWcvI25hdGl2ZXRocm93aWYKLy8vCkBuYW1lKHRocm93X2lmKQpuYXRpdmUgbmF0aXZlVGhyb3dJ" + - "Zihjb2RlOiBJbnQsIGNvbmRpdGlvbjogQm9vbCk7CgovLy8gR2xvYmFsIGZ1bmN0aW9uLiAqKkRlcHJlY2F0ZWQqKiBzaW5jZSBUYWN0IDEuNi4wLgovLy8KLy8vIFVz" + - "ZSBgdGhyb3dVbmxlc3MoKWAgaW5zdGVhZC4KLy8vCi8vLyBTZWU6Ci8vLyAqIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWRlYnVnLyN0aHJvd3Vu" + - "bGVzcwovLy8gKiBodHRwczovL2RvY3MudGFjdC1sYW5nLm9yZy9yZWYvY29yZS1kZWJ1Zy8jbmF0aXZldGhyb3d1bmxlc3MKLy8vCkBuYW1lKHRocm93X3VubGVzcykK" + - "bmF0aXZlIG5hdGl2ZVRocm93VW5sZXNzKGNvZGU6IEludCwgY29uZGl0aW9uOiBCb29sKTsK"; + "dGx5IGluc3RlYWQgb2YgZ2VuZXJhdGluZyBpdCBzZXF1ZW50aWFsbHkuCi8vLwovLy8gQXR0ZW1wdHMgdG8gc3BlY2lmeSB0aGUgYGNvZGVgIG91dHNpZGUgb2YgMC02" + + "NTUzNSByYW5nZSBjYXVzZSBhbiBleGNlcHRpb24gd2l0aCBleGl0IGNvZGUgNTogYEludGVnZXIgb3V0IG9mIGV4cGVjdGVkIHJhbmdlYC4KLy8vCi8vLyBgYGB0YWN0" + + "Ci8vLyBjb250cmFjdCBPd25lcnNoaXAgewovLy8gICAgIG93bmVyOiBBZGRyZXNzOwovLy8KLy8vICAgICBpbml0KCkgewovLy8gICAgICAgICBzZWxmLm93bmVyID0g" + + "bXlBZGRyZXNzKCk7Ci8vLyAgICAgfQovLy8KLy8vICAgICByZWNlaXZlKCkgewovLy8gICAgICAgICAvLyBDaGVjayB0aGUgc2VuZGVyIGlzIHRoZSBvd25lciBvZiB0" + + "aGUgY29udHJhY3QsCi8vLyAgICAgICAgIC8vIGFuZCB0aHJvdyBleGl0IGNvZGUgMTAyNCBpZiBpdCdzIG5vdAovLy8gICAgICAgICB0aHJvd1VubGVzcygxMDI0LCBz" + + "ZW5kZXIoKSA9PSBzZWxmLm93bmVyKTsKLy8vICAgICB9Ci8vLyB9Ci8vLyBgYGAKLy8vCi8vLyBTZWU6Ci8vLyAqIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3Jl" + + "Zi9jb3JlLWRlYnVnLyN0aHJvd3VubGVzcwovLy8gKiBodHRwczovL2RvY3MudGFjdC1sYW5nLm9yZy9yZWYvY29yZS1kZWJ1Zy8jcmVxdWlyZQovLy8gKiBodHRwczov" + + "L2RvY3MudGFjdC1sYW5nLm9yZy9ib29rL3N0YXRlbWVudHMvI3RyeS1jYXRjaAovLy8KQG5hbWUodGhyb3dfdW5sZXNzKQpuYXRpdmUgdGhyb3dVbmxlc3MoY29kZTog" + + "SW50LCBjb25kaXRpb246IEJvb2wpOwoKLy8vIEdsb2JhbCBmdW5jdGlvbi4gKipEZXByZWNhdGVkKiogc2luY2UgVGFjdCAxLjYuMC4KLy8vCi8vLyBVc2UgYHRocm93" + + "KClgIGluc3RlYWQuCi8vLwovLy8gU2VlOgovLy8gKiBodHRwczovL2RvY3MudGFjdC1sYW5nLm9yZy9yZWYvY29yZS1kZWJ1Zy8jdGhyb3cKLy8vICogaHR0cHM6Ly9k" + + "b2NzLnRhY3QtbGFuZy5vcmcvcmVmL2NvcmUtZGVidWcvI25hdGl2ZXRocm93Ci8vLwpAbmFtZSh0aHJvdykKbmF0aXZlIG5hdGl2ZVRocm93KGNvZGU6IEludCk7Cgov" + + "Ly8gR2xvYmFsIGZ1bmN0aW9uLiAqKkRlcHJlY2F0ZWQqKiBzaW5jZSBUYWN0IDEuNi4wLgovLy8KLy8vIFVzZSBgdGhyb3dJZigpYCBpbnN0ZWFkLgovLy8KLy8vIFNl" + + "ZToKLy8vICogaHR0cHM6Ly9kb2NzLnRhY3QtbGFuZy5vcmcvcmVmL2NvcmUtZGVidWcvI3Rocm93aWYKLy8vICogaHR0cHM6Ly9kb2NzLnRhY3QtbGFuZy5vcmcvcmVm" + + "L2NvcmUtZGVidWcvI25hdGl2ZXRocm93aWYKLy8vCkBuYW1lKHRocm93X2lmKQpuYXRpdmUgbmF0aXZlVGhyb3dJZihjb2RlOiBJbnQsIGNvbmRpdGlvbjogQm9vbCk7" + + "CgovLy8gR2xvYmFsIGZ1bmN0aW9uLiAqKkRlcHJlY2F0ZWQqKiBzaW5jZSBUYWN0IDEuNi4wLgovLy8KLy8vIFVzZSBgdGhyb3dVbmxlc3MoKWAgaW5zdGVhZC4KLy8v" + + "Ci8vLyBTZWU6Ci8vLyAqIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWRlYnVnLyN0aHJvd3VubGVzcwovLy8gKiBodHRwczovL2RvY3MudGFjdC1s" + + "YW5nLm9yZy9yZWYvY29yZS1kZWJ1Zy8jbmF0aXZldGhyb3d1bmxlc3MKLy8vCkBuYW1lKHRocm93X3VubGVzcykKbmF0aXZlIG5hdGl2ZVRocm93VW5sZXNzKGNvZGU6" + + "IEludCwgY29uZGl0aW9uOiBCb29sKTsK"; files["std/internal/math.tact"] = "Ly8gUHJlcGFyZSByYW5kb20KCi8vLyBHbG9iYWwgZnVuY3Rpb24uCi8vLwovLy8gUmFuZG9taXplcyB0aGUgcHNldWRvcmFuZG9tIG51bWJlciBnZW5lcmF0b3Igd2l0" + "aCB0aGUgc3BlY2lmaWVkIHVuc2lnbmVkIDI1Ni1iaXQgYEludGAgYHhgIGJ5IG1peGluZyBpdCB3aXRoIHRoZSBjdXJyZW50IHNlZWQuIFRoZSBuZXcgc2VlZCBpcyB0" + diff --git a/src/stdlib/stdlib/std/internal/debug.tact b/src/stdlib/stdlib/std/internal/debug.tact index 87484dba7b..02ef3eb4f6 100644 --- a/src/stdlib/stdlib/std/internal/debug.tact +++ b/src/stdlib/stdlib/std/internal/debug.tact @@ -63,7 +63,7 @@ native throwIf(code: Int, condition: Bool); /// /// Similar to `throw()`, but throws an error `code` only if `condition` does **not** hold, i.e. `condition` is equal to `true{:tact}`. Doesn't throw otherwise. /// -/// This function is also similar to `require()`, but uses the specified `code` directly instead of generating one based on the given error message `String`. +/// This function is also similar to `require()`, but uses the specified `code` directly instead of generating it sequentially. /// /// Attempts to specify the `code` outside of 0-65535 range cause an exception with exit code 5: `Integer out of expected range`. /// diff --git a/src/test/e2e-emulated/contracts/exit-codes-child.tact b/src/test/e2e-emulated/contracts/exit-codes-child.tact new file mode 100644 index 0000000000..afbe86923f --- /dev/null +++ b/src/test/e2e-emulated/contracts/exit-codes-child.tact @@ -0,0 +1,7 @@ +contract ExitCodesChild { + receive() {} + get fun test() { + require(false, "second"); + return; + } +} diff --git a/src/test/e2e-emulated/contracts/exit-codes-parent.tact b/src/test/e2e-emulated/contracts/exit-codes-parent.tact new file mode 100644 index 0000000000..4880d86035 --- /dev/null +++ b/src/test/e2e-emulated/contracts/exit-codes-parent.tact @@ -0,0 +1,13 @@ +import "./exit-codes-child"; + +contract ExitCodesParent { + receive() {} + get fun firstExitCode() { + require(false, "first"); + return; + } + get fun secondExitCode() { + require(false, "second"); + return; + } +} diff --git a/src/test/e2e-emulated/exit-codes.spec.ts b/src/test/e2e-emulated/exit-codes.spec.ts new file mode 100644 index 0000000000..caa99cda20 --- /dev/null +++ b/src/test/e2e-emulated/exit-codes.spec.ts @@ -0,0 +1,79 @@ +import { toNano } from "@ton/core"; +import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { Blockchain } from "@ton/sandbox"; +import { ExitCodesParent } from "./contracts/output/exit-codes-parent_ExitCodesParent"; +import { ExitCodesChild as SameProjectChild } from "./contracts/output/exit-codes-parent_ExitCodesChild"; +import { ExitCodesChild as SeparatedChild } from "./contracts/output/exit-codes-child_ExitCodesChild"; +import "@ton/test-utils"; + +describe("exit-codes", () => { + let blockchain: Blockchain; + let treasure: SandboxContract; + let parent: SandboxContract; + let childSameProj: SandboxContract; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.verbosity.print = false; + treasure = await blockchain.treasury("treasure"); + + parent = blockchain.openContract(await ExitCodesParent.fromInit()); + childSameProj = blockchain.openContract( + await SameProjectChild.fromInit(), + ); + + const deployResult = await parent.send( + treasure.getSender(), + { value: toNano("10") }, + null, + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: treasure.address, + to: parent.address, + success: true, + deploy: true, + }); + }); + + it("abi should be correct", async () => { + const parentProvider = blockchain.getContract(parent.address); + const firstExitCode = ExitCodesParent.errors["first"]; + const realFirstExitCode = ( + await (await parentProvider).get("firstExitCode") + ).exitCode; + expect(realFirstExitCode).toBe(firstExitCode); + + const secondExitCode = ExitCodesParent.errors["second"]; + const realSecondExitCode = ( + await (await parentProvider).get("secondExitCode") + ).exitCode; + expect(realSecondExitCode).toBe(secondExitCode); + + const childProvider = blockchain.getContract(childSameProj.address); + const childExitCode = SameProjectChild.errors["second"]; + const realChildExitCode = ( + await (await childProvider).get("secondExitCode") + ).exitCode; + expect(realChildExitCode).toBe(childExitCode); + }); + + it("should be same in same project", () => { + const firstParentExitCode = ExitCodesParent.errors["first"]; + const secondParentExitCode = ExitCodesParent.errors["second"]; + const childExitCode = SameProjectChild.errors["second"]; + + expect( + new Set([firstParentExitCode, secondParentExitCode, childExitCode]), + ).toEqual(new Set([1024, 1025])); + }); + + it("should be different in different projects", () => { + const parentExitCode = ExitCodesParent.errors["second"]; + const separateExitCode = SeparatedChild.errors["second"]; + expect(separateExitCode).toBe(1024); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + expect(parentExitCode === 1024 || parentExitCode === 1025).toBe(true); + }); +}); diff --git a/src/test/e2e-emulated/map-comparison.spec.ts b/src/test/e2e-emulated/map-comparison.spec.ts index 5205b8bf30..97aa5bb0fa 100644 --- a/src/test/e2e-emulated/map-comparison.spec.ts +++ b/src/test/e2e-emulated/map-comparison.spec.ts @@ -211,7 +211,8 @@ describe("map-comparison", () => { from: treasure.address, to: contract.address, success: false, - exitCode: 53111, + exitCode: + MapComparisonTestContract.errors["Maps are not equal"], }); result = await treasure.send({ diff --git a/src/test/e2e-emulated/strings.spec.ts b/src/test/e2e-emulated/strings.spec.ts index c76baa9ad8..3c4333c118 100644 --- a/src/test/e2e-emulated/strings.spec.ts +++ b/src/test/e2e-emulated/strings.spec.ts @@ -31,9 +31,10 @@ describe("strings", () => { }); it("should implement strings correctly", async () => { - expect(contract.abi.errors!["31733"]!.message).toStrictEqual( - "condition can`t be...", - ); + expect( + contract.abi.errors![StringsTester.errors["condition can`t be..."]]! + .message, + ).toStrictEqual("condition can`t be..."); // Check methods expect(await contract.getConstantString()).toBe("test string"); diff --git a/src/test/gas-consumption/__snapshots__/gas.spec.ts.snap b/src/test/gas-consumption/__snapshots__/gas.spec.ts.snap index 7cb697f8f1..86974e1ca7 100644 --- a/src/test/gas-consumption/__snapshots__/gas.spec.ts.snap +++ b/src/test/gas-consumption/__snapshots__/gas.spec.ts.snap @@ -18,9 +18,9 @@ exports[`benchmarks benchmark deployable trait vs raw deploy: gas used deploy tr exports[`benchmarks benchmark deployable trait vs raw deploy: gas used raw deploy 1`] = `1505`; -exports[`benchmarks benchmark functions: code size 1`] = `177`; +exports[`benchmarks benchmark functions: code size 1`] = `171`; -exports[`benchmarks benchmark functions: gas used 1`] = `1997`; +exports[`benchmarks benchmark functions: gas used 1`] = `1982`; exports[`benchmarks benchmark readFwdFee: code size 1`] = `121`; diff --git a/src/test/send-modes/send-default-mode-no-flag.spec.ts b/src/test/send-modes/send-default-mode-no-flag.spec.ts index f1fa0ab543..ffa582168b 100644 --- a/src/test/send-modes/send-default-mode-no-flag.spec.ts +++ b/src/test/send-modes/send-default-mode-no-flag.spec.ts @@ -565,7 +565,11 @@ describe("SendDefaultMode with no flags", () => { // according to "MessageModeTester.md" const calculatorCompPhase = getComputationPhase(calculatorTsx); expect(calculatorCompPhase.success).toBe(false); - expect(calculatorCompPhase.exitCode).toBe(25459); + expect(calculatorCompPhase.exitCode).toBe( + MessageModeTester.errors[ + "There must exist at least one number in the interval." + ], + ); // Check that the calculator's action phase did not execute, but its bounce phase did const calculatorTsxDescription = diff --git a/src/types/resolveErrors.ts b/src/types/resolveErrors.ts index 6f58bdac48..b43e6cc3b7 100644 --- a/src/types/resolveErrors.ts +++ b/src/types/resolveErrors.ts @@ -14,18 +14,31 @@ import { import { ensureSimplifiedString } from "../optimizer/interpreter"; import type { AstUtil } from "../ast/util"; import { getAstUtil } from "../ast/util"; -import { sha256, highest32ofSha256 } from "../utils/sha256"; -type Exception = { value: string; id: number }; +type Exception = { + /** + * Error message, supplied by the user. + */ + value: string; + /** + * Unique exit code for the error. + */ + id: number; +}; const exceptions = createContextStore(); -function stringId(src: string): number { - return Number(highest32ofSha256(sha256(src))); -} - -function exceptionId(src: string): number { - return (stringId(src) % 63000) + 1000; +/** + * Generates a unique exit code for an error. + * This is used to ensure that errors are unique. + * The exit codes are sequential, starting at 1024. + * Preferably, the exit codes should fit 11 bits, as it will result in cheaper opcodes. + * https://github.com/ton-blockchain/ton/blob/master/crypto/func/builtins.cpp#L984-L1003 + * + * This function works correctly, as string uniqueness is checked in the caller below. + */ +function getNextExitCode(ctx: CompilerContext): number { + return exceptions.all(ctx).size + 1024; } function resolveStringsInAST( @@ -38,21 +51,21 @@ function resolveStringsInAST( if (node.args.length !== 2) { return; } - const resolved = ensureSimplifiedString( + const message = ensureSimplifiedString( evalConstantExpression(node.args[1]!, ctx, util), ).value; - if (!exceptions.get(ctx, resolved)) { - const id = exceptionId(resolved); + if (!exceptions.get(ctx, message)) { + const id = getNextExitCode(ctx); if ( Array.from(exceptions.all(ctx).values()).find( (v) => v.id === id, ) ) { throwInternalCompilerError( - `Duplicate error id: "${resolved}"`, + `Duplicate error id: "${message}"`, ); } - ctx = exceptions.set(ctx, resolved, { value: resolved, id }); + ctx = exceptions.set(ctx, message, { value: message, id }); } } });