Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 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
5 changes: 4 additions & 1 deletion src/cli/tact/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,17 @@ describe("tact --config config.json", () => {
});
});

/*
* This tests do not work in JavaScript Debug Terminal, as they use stderr.
* JS debug terminal adds redundant output to stderr, which breaks the tests.
*/
describe.only("tact -q foo.tact", () => {
testWin("-q shows errors ", async () => {
const path = await codegen.contract(
`quiet`,
`contract Test { x: Int = A }`,
);
const result = await tact(`-q ${path}`);

expect(
result.kind === "exited" &&
result.stderr.includes("Cannot find 'A'"),
Expand Down
7 changes: 7 additions & 0 deletions src/test/e2e-emulated/contracts/exit-codes-child.tact
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
contract ExitCodesChild {
receive() {}
get fun test() {
require(false, "second");
return;
}
}
13 changes: 13 additions & 0 deletions src/test/e2e-emulated/contracts/exit-codes-parent.tact
Original file line number Diff line number Diff line change
@@ -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;
}
}
79 changes: 79 additions & 0 deletions src/test/e2e-emulated/exit-codes.spec.ts
Original file line number Diff line number Diff line change
@@ -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<TreasuryContract>;
let parent: SandboxContract<ExitCodesParent>;
let childSameProj: SandboxContract<SameProjectChild>;

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);
});
});
3 changes: 2 additions & 1 deletion src/test/e2e-emulated/map-comparison.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
7 changes: 4 additions & 3 deletions src/test/e2e-emulated/strings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
4 changes: 2 additions & 2 deletions src/test/gas-consumption/__snapshots__/gas.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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`;

Expand Down
6 changes: 5 additions & 1 deletion src/test/send-modes/send-default-mode-no-flag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
39 changes: 26 additions & 13 deletions src/types/resolveErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Exception>();

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(
Expand All @@ -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 });
}
}
});
Expand Down
Loading