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 });
}
}
});