From b6c65a4adacf2aae3b99385a27a59e9ff0575d30 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Sun, 15 Sep 2024 11:22:39 +0200 Subject: [PATCH] test: Tact-reserved and general compute phase exit codes (#823) With the correction of error list generated for the `.md` compilation report --- cspell.json | 6 + src/generator/createABI.ts | 21 +- .../local-type-inference.spec.ts.snap | 38 ++- .../exit-codes/compute-phase-errors.spec.ts | 151 +++++++++++ .../contracts/compute-phase-errors.fc | 8 + .../contracts/compute-phase-errors.tact | 246 ++++++++++++++++++ .../exit-codes/contracts/repeat-range.tact | 2 +- .../tact-reserved-contract-errors.tact | 106 ++++++++ src/test/exit-codes/repeat-range.spec.ts | 6 +- .../tact-reserved-contract-errors.spec.ts | 101 +++++++ tact.config.json | 13 + 11 files changed, 692 insertions(+), 6 deletions(-) create mode 100644 src/test/exit-codes/compute-phase-errors.spec.ts create mode 100644 src/test/exit-codes/contracts/compute-phase-errors.fc create mode 100644 src/test/exit-codes/contracts/compute-phase-errors.tact create mode 100644 src/test/exit-codes/contracts/tact-reserved-contract-errors.tact create mode 100644 src/test/exit-codes/tact-reserved-contract-errors.spec.ts diff --git a/cspell.json b/cspell.json index a4634aec9..b20decb1b 100644 --- a/cspell.json +++ b/cspell.json @@ -77,6 +77,7 @@ "lvalues", "masterchain", "maxint", + "Merkle", "minmax", "mintable", "mktemp", @@ -92,6 +93,7 @@ "Offchain", "Parens", "pinst", + "PLDDICT", "PLDIX", "PLDREF", "PLDSLICEX", @@ -99,6 +101,7 @@ "POSIX", "postpack", "prando", + "PUSHINT", "PUSHREF", "PUSHSLICE", "RANDU", @@ -120,7 +123,9 @@ "SENDRAWMSG", "SENDMSG", "seqno", + "SETCONTARGS", "SETINDEXVARQ", + "SETNUMARGS", "shiki", "SREFS", "SREMPTY", @@ -177,6 +182,7 @@ "src/test/e2e-emulated/contracts/strings.tact", "src/test/compilation-fail/fail-const-eval.spec.ts", "src/test/e2e-emulated/getter-names-conflict.spec.ts", + "src/test/exit-codes/contracts/compute-phase-errors.tact", "stdlib/stdlib.fc" ] } diff --git a/src/generator/createABI.ts b/src/generator/createABI.ts index 406606370..45f6ae6e1 100644 --- a/src/generator/createABI.ts +++ b/src/generator/createABI.ts @@ -146,11 +146,30 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI { errors["8"] = { message: "Cell overflow" }; errors["9"] = { message: "Cell underflow" }; errors["10"] = { message: "Dictionary error" }; + errors["11"] = { message: "'Unknown' error" }; + errors["12"] = { message: "Fatal error" }; errors["13"] = { message: "Out of gas error" }; - errors["32"] = { message: "Method ID not found" }; + errors["14"] = { message: "Virtualization error" }; + errors["32"] = { message: "Action list is invalid" }; + errors["33"] = { message: "Action list is too long" }; errors["34"] = { message: "Action is invalid or not supported" }; + errors["35"] = { message: "Invalid source address in outbound message" }; + errors["36"] = { + message: "Invalid destination address in outbound message", + }; errors["37"] = { message: "Not enough TON" }; errors["38"] = { message: "Not enough extra-currencies" }; + errors["39"] = { + message: "Outbound message does not fit into a cell after rewriting", + }; + errors["40"] = { message: "Cannot process a message" }; + errors["41"] = { message: "Library reference is null" }; + errors["42"] = { message: "Library change action error" }; + errors["43"] = { + message: + "Exceeded maximum number of cells in the library or the maximum depth of the Merkle tree", + }; + errors["50"] = { message: "Account state size exceeded limits" }; for (const e of Object.values(contractErrors)) { errors[e.id] = { message: e.message }; } 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 fb21a98da..9d3c1e7d9 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 @@ -6,6 +6,12 @@ exports[`local-type-inference should automatically set types for let statements "10": { "message": "Dictionary error", }, + "11": { + "message": "'Unknown' error", + }, + "12": { + "message": "Fatal error", + }, "128": { "message": "Null reference exception", }, @@ -39,6 +45,9 @@ exports[`local-type-inference should automatically set types for let statements "137": { "message": "Masterchain support is not enabled for this contract", }, + "14": { + "message": "Virtualization error", + }, "2": { "message": "Stack underflow", }, @@ -46,23 +55,50 @@ exports[`local-type-inference should automatically set types for let statements "message": "Stack overflow", }, "32": { - "message": "Method ID not found", + "message": "Action list is invalid", + }, + "33": { + "message": "Action list is too long", }, "34": { "message": "Action is invalid or not supported", }, + "35": { + "message": "Invalid source address in outbound message", + }, + "36": { + "message": "Invalid destination address in outbound message", + }, "37": { "message": "Not enough TON", }, "38": { "message": "Not enough extra-currencies", }, + "39": { + "message": "Outbound message does not fit into a cell after rewriting", + }, "4": { "message": "Integer overflow", }, + "40": { + "message": "Cannot process a message", + }, + "41": { + "message": "Library reference is null", + }, + "42": { + "message": "Library change action error", + }, + "43": { + "message": "Exceeded maximum number of cells in the library or the maximum depth of the Merkle tree", + }, "5": { "message": "Integer out of expected range", }, + "50": { + "message": "Account state size exceeded limits", + }, "6": { "message": "Invalid opcode", }, diff --git a/src/test/exit-codes/compute-phase-errors.spec.ts b/src/test/exit-codes/compute-phase-errors.spec.ts new file mode 100644 index 000000000..15f0c0199 --- /dev/null +++ b/src/test/exit-codes/compute-phase-errors.spec.ts @@ -0,0 +1,151 @@ +import { toNano } from "@ton/core"; +import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { + ComputePhaseErrorsTester as TestContract, + ExitCode4, +} from "./contracts/output/compute-phase-errors_ComputePhaseErrorsTester"; +import "@ton/test-utils"; + +describe("compute phase errors", () => { + let blockchain: Blockchain; + let treasure: SandboxContract; + let contract: SandboxContract; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.verbosity.print = false; + treasure = await blockchain.treasury("treasure", { + resetBalanceIfZero: true, + }); + + contract = blockchain.openContract(await TestContract.fromInit()); + + const deployResult = await contract.send( + treasure.getSender(), + { value: toNano("10000") }, + null, + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: treasure.address, + to: contract.address, + success: true, + deploy: true, + }); + }); + + // 0: success + it("should test exit code 0", async () => { + await testComputePhaseExitCode(0, contract, treasure); + }); + + // 1: alt. success code + it("should test exit code 1", async () => { + await testComputePhaseExitCode(1, contract, treasure); + }); + + // 2: stack underflow + it("should test exit code 2", async () => { + await testComputePhaseExitCode(2, contract, treasure); + }); + + // 3: Stack overflow + it("should test exit code 3", async () => { + await testComputePhaseExitCode(3, contract, treasure); + }); + + // 4: Integer overflow + it("should test exit code 4", async () => { + await testComputePhaseExitCode(4, contract, treasure); + }); + + // 5: Integer out of range + it("should test exit code 5", async () => { + await testComputePhaseExitCode(5, contract, treasure); + }); + + // 6: Invalid opcode + it("should test exit code 6", async () => { + await testComputePhaseExitCode(8, contract, treasure); + }); + + // 7: Type check error + it("should test exit code 7", async () => { + await testComputePhaseExitCode(7, contract, treasure); + }); + + // 8: Cell overflow + it("should test exit code 8", async () => { + await testComputePhaseExitCode(8, contract, treasure); + }); + + // 9: Cell underflow + it("should test exit code 9", async () => { + await testComputePhaseExitCode(9, contract, treasure); + }); + + // 10: Dictionary error + it("should test exit code 10", async () => { + await testComputePhaseExitCode(10, contract, treasure); + }); + + // 11: "Unknown" error + // NOTE: Thrown in various unrelated cases + it("should test exit code 11", async () => { + await testComputePhaseExitCode(11, contract, treasure); + }); + + // 12: Fatal error + // NOTE: thrown by TVM in situations deemed impossible + + // 13 (actually, -14): Out of gas + it("should test exit code 13", async () => { + await testComputePhaseExitCode(13, contract, treasure); + }); + + // 14: Virtualization error + // NOTE: Reserved, but never thrown +}); + +async function testComputePhaseExitCode( + code: number, + contract: SandboxContract, + treasure: SandboxContract, +) { + expect(code).toBeGreaterThanOrEqual(0); + expect(code).toBeLessThan(128); + expect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]).toContain(code); + type testedExitCodes = + | "0" + | "1" + | "2" + | "3" + | ExitCode4 + | "5" + | "6" + | "7" + | "8" + | "9" + | "10" + | "11" + | "13"; + + const sendResult = await contract.send( + treasure.getSender(), + { value: toNano("10") }, + code === 4 + ? { + $$type: "ExitCode4", + val0: BigInt(0), + val1: BigInt(1), + } + : (code.toString(10) as testedExitCodes), + ); + + expect(sendResult.transactions).toHaveTransaction({ + from: treasure.address, + to: contract.address, + success: code === 0 || code === 1 ? true : false, + exitCode: code === 13 ? -14 : code, + }); +} diff --git a/src/test/exit-codes/contracts/compute-phase-errors.fc b/src/test/exit-codes/contracts/compute-phase-errors.fc new file mode 100644 index 000000000..d3ef07352 --- /dev/null +++ b/src/test/exit-codes/contracts/compute-phase-errors.fc @@ -0,0 +1,8 @@ +() stack_overflow() impure asm """ + <{ + }>CONT // c + 0 SETNUMARGS // c' + 2 PUSHINT // c' 2 + SWAP // 2 c' + 1 -1 SETCONTARGS +"""; diff --git a/src/test/exit-codes/contracts/compute-phase-errors.tact b/src/test/exit-codes/contracts/compute-phase-errors.tact new file mode 100644 index 000000000..e27f8395c --- /dev/null +++ b/src/test/exit-codes/contracts/compute-phase-errors.tact @@ -0,0 +1,246 @@ +import "./compute-phase-errors.fc"; + +contract ComputePhaseErrorsTester { + // Used for storing temporary values + tmpI: Int = 0; + tmpC: Cell = cell("te6cckEBAQEAAgAAAEysuc0="); // empty cell + + /// To handle deployment + receive() {} + + /// Exit code 0 + receive("0") { + throw(0); // Yes, that still counts as a success + } + + /// Exit code 1 + receive("1") { + throw(1); // Yes, that still counts as a success + } + + /// Exit code 2 + receive("2") { + // Removes 100 elements from the stack, causing an underflow + repeat (100) { + drop(); + } + } + + /// Exit code 3 + receive("3") { + stackOverflow(); + } + + /// Exit code 4 + receive(msg: ExitCode4) { + // Setup + let targetCode = 4; + let failed = true; + + // Addition + try { + self.tmpI = pow(2, 255) - 1 + pow(2, 255) + msg.val1; + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Addition didn't cause an integer overflow"); + + // Subtraction + try { + self.tmpI = -pow(2, 255) - pow(2, 255) - msg.val1; + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Subtraction didn't cause an integer overflow"); + + // Negation + try { + self.tmpI = -(-pow(2, 255) - pow(2, 255) + msg.val0); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Negation didn't cause an integer overflow"); + + // Multiplication + try { + self.tmpI = pow(2, 255) * (msg.val1 + 1); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Multiplication didn't cause an integer overflow"); + + // Division + try { + self.tmpI = (-pow(2, 255) - pow(2, 255)) / (-msg.val1); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Division didn't cause an integer overflow"); + + // Division by zero + try { + self.tmpI = 1 / msg.val0; + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Division by zero didn't cause an integer overflow"); + + // Modulo by zero + try { + self.tmpI = 1 % msg.val0; + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Modulo by zero didn't cause an integer overflow"); + + // As we got here, everything above caused the target exit code, + // so let's throw it explicitly now for the tests on Blueprint's side + throw(targetCode); + } + + /// Exit code 5 + receive("5") { + // Builder.storeUint() function can only use up to 256 bits, + // so 512 is too much and the negative value cannot be stored either + self.tmpC = beginCell().storeUint(-1, 512).endCell(); + } + + // Exit code 6 + receive("6") { + invalidOpcode(); + } + + /// Exit code 7 + receive("7") { + typeCheckError().get(0)!!; + } + + /// Exit code 8 + receive("8") { + // Setup + let targetCode = 8; + let failed = true; + + // Cell overflow (data) + try { + self.tmpC = beginCell() + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 24) // 1024 bits! + .endCell(); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "1024 bits didn't cause the cell overflow"); + + // Cell overflow (refs) + try { + self.tmpC = beginCell() + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .storeRef(emptyCell()) // 5 refs! + .endCell(); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "5 refs didn't cause the cell overflow"); + + // As we got here, everything above caused the target exit code, + // so let's throw it explicitly now for the tests on Blueprint's side + throw(targetCode); + } + + /// Exit code 9 + receive("9") { + // Setup + let targetCode = 9; + let failed = true; + + // Cell underflow (data) + try { + self.tmpI = emptySlice().loadInt(1); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Loading 1 bit from an empty Slice didn't cause the cell underflow"); + + // Cell underflow (refs) + try { + self.tmpC = emptySlice().loadRef(); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Loading 1 ref from an empty Slice didn't cause the cell underflow"); + + // As we got here, everything above caused the target exit code, + // so let's throw it explicitly now for the tests on Blueprint's side + throw(targetCode); + } + + /// Exit code 10 + receive("10") { + // The Int to Int dictionary is being misinterpreted as a map + let m: map = toMapIntCell(cellWithDictIntInt.beginParse()); + + // And the error happens only when we touch it + self.tmpC = m.get(0)!!; + } + + /// Exit code 11 + receive("11") { + // Unlike nativeSendMessage which uses SENDRAWMSG, this one uses SENDMSG, + // and therefore fails in Compute time when the message is ill-formed + nativeSendMessageReturnForwardFee(emptyCell(), 0); + } + + /// Exit code 13 (-14, to be precise) + receive("13") { + let counter = 0; + repeat (pow(2, 31) - 1) { + counter += 1; + } + } +} + +/// Exit code 4 +message(4) ExitCode4 { + val0: Int as uint2 = 0; + val1: Int as uint2 = 1; +} + +/// Pre-computed Int to Int dictionary with two entries — 0: 0 and 1: 1 +const cellWithDictIntInt: Cell = cell("te6cckEBBAEAUAABAcABAgPQCAIDAEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMLMbT1U="); + +/// Tries to preload a dictionary from a Slice as a map +asm fun toMapIntInt(x: Slice): map { PLDDICT } + +/// Tries to preload a dictionary from a Slice as a map +asm fun toMapIntCell(x: Slice): map { PLDDICT } + +/// Non-existent opcode +asm fun invalidOpcode() { x{D7FF} @addop } + +/// DROP +asm fun drop() { DROP } + +/// Stack overflow +@name(stack_overflow) +native stackOverflow(); + +/// Type check error +asm fun typeCheckError(): map { 42 PUSHINT } diff --git a/src/test/exit-codes/contracts/repeat-range.tact b/src/test/exit-codes/contracts/repeat-range.tact index 8c4bab42b..1b6136a68 100644 --- a/src/test/exit-codes/contracts/repeat-range.tact +++ b/src/test/exit-codes/contracts/repeat-range.tact @@ -1,4 +1,4 @@ -contract RepeatRange { +contract RepeatRangeTester { /// To handle deployment receive() {} diff --git a/src/test/exit-codes/contracts/tact-reserved-contract-errors.tact b/src/test/exit-codes/contracts/tact-reserved-contract-errors.tact new file mode 100644 index 000000000..b9e1edf83 --- /dev/null +++ b/src/test/exit-codes/contracts/tact-reserved-contract-errors.tact @@ -0,0 +1,106 @@ +import "@stdlib/ownable"; +import "@stdlib/dns"; + +message(1478) SpanishInquisition {} + +contract ReservedContractErrorsTester with Ownable { + /// To make Ownable work + owner: Address; + + /// Setups address of this contract to be its owner + init() { self.owner = myAddress() } + + /// To handle deployment + receive() {} + + /// Exit code 128 + receive("128") { + let gotcha: String? = null; + dump(gotcha!!); + } + + /// Exit code 130 + receive("130") { + send(SendParameters{ + to: myAddress(), // Send a message back to this contract + value: 0, + mode: SendRemainingValue | SendIgnoreErrors, + body: SpanishInquisition{}.toCell(), // Nobody expects it! + }); + } + + /// Exit code 132 + receive("132") { + self.requireOwner(); + } + + /// Exit code 134 + receive("134") { + // Setup + let targetCode = 134; + let failed = true; + + // Case 1 + try { + // 0 is code of NUL in ASCII and it is not valid Base64 + dump(beginCell() + .storeUint(0, 8) + .asSlice() + .fromBase64()); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Slice.fromBase64() didn't error on invalid Base64"); + + // Case 2 + try { + // 0 is code of NUL in ASCII and it is not valid Base64 + dump("\x00".fromBase64()); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "String.fromBase64() didn't error on invalid Base64"); + + // Case 3 + try { + dump((42).toFloatString(-1)); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Int.toFloatString() didn't error on digits -1"); + + // Case 4 + try { + dump((42).toFloatString(78)); + failed = false; + } catch (exitCode) { + nativeThrowIf(exitCode, exitCode != targetCode); + } + require(failed, "Int.toFloatString() didn't error on digits 78"); + + // As we got here, everything above caused the target exit code, + // so let's throw it explicitly now for the tests on Blueprint's side + throw(targetCode); + } + + /// Exit code 136 + receive("136") { + let unsupportedChainId = 1; + dump( + // Zero address in unsupported workchain + newAddress(unsupportedChainId, 0) + ); + } + + /// Exit code 137 + receive("137") { + let masterchainId = -1; + dump( + // Zero address in masterchain without the config option set + newAddress(masterchainId, 0) + ); + } +} diff --git a/src/test/exit-codes/repeat-range.spec.ts b/src/test/exit-codes/repeat-range.spec.ts index 7db8d09a3..e3451f316 100644 --- a/src/test/exit-codes/repeat-range.spec.ts +++ b/src/test/exit-codes/repeat-range.spec.ts @@ -1,12 +1,12 @@ import { toNano } from "@ton/core"; import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; -import { RepeatRange } from "./contracts/output/repeat-range_RepeatRange"; +import { RepeatRangeTester as TestContract } from "./contracts/output/repeat-range_RepeatRangeTester"; import "@ton/test-utils"; describe("repeat range", () => { let blockchain: Blockchain; let treasure: SandboxContract; - let contract: SandboxContract; + let contract: SandboxContract; beforeEach(async () => { blockchain = await Blockchain.create(); @@ -15,7 +15,7 @@ describe("repeat range", () => { resetBalanceIfZero: true, }); - contract = blockchain.openContract(await RepeatRange.fromInit()); + contract = blockchain.openContract(await TestContract.fromInit()); const deployResult = await contract.send( treasure.getSender(), diff --git a/src/test/exit-codes/tact-reserved-contract-errors.spec.ts b/src/test/exit-codes/tact-reserved-contract-errors.spec.ts new file mode 100644 index 000000000..d29ecddff --- /dev/null +++ b/src/test/exit-codes/tact-reserved-contract-errors.spec.ts @@ -0,0 +1,101 @@ +import { toNano } from "@ton/core"; +import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { ReservedContractErrorsTester as TestContract } from "./contracts/output/tact-reserved-contract-errors_ReservedContractErrorsTester"; +import "@ton/test-utils"; + +describe("Tact-reserved contract errors", () => { + let blockchain: Blockchain; + let treasure: SandboxContract; + let contract: SandboxContract; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.verbosity.print = false; + treasure = await blockchain.treasury("treasure", { + resetBalanceIfZero: true, + }); + + contract = blockchain.openContract(await TestContract.fromInit()); + + const deployResult = await contract.send( + treasure.getSender(), + { value: toNano("100000") }, + null, + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: treasure.address, + to: contract.address, + success: true, + deploy: true, + }); + }); + + // 128: Null reference exception + it("should test exit code 128", async () => { + await testReservedExitCode(128, contract, treasure); + }); + + // 129: Invalid serialization prefix + // NOTE: Reserved, but due to a number of prior checks it cannot be thrown unless one hijacks + // the contract code before deployment and changes the opcodes of the Messages expected + // to be received in the contract + + // 130: Invalid incoming message + it("should test exit code 130", async () => { + await testReservedExitCode(130, contract, treasure); + }); + + // 131: Constraints error + // NOTE: Reserved, but never thrown anywhere, can't repro + + // 132: Access denied + it("should test exit code 132", async () => { + await testReservedExitCode(132, contract, treasure); + }); + + // 133: Contract stopped + // NOTE: Reserved, but never thrown anywhere, can't repro + + // 134: Invalid argument + it("should test exit code 134", async () => { + await testReservedExitCode(134, contract, treasure); + }); + + // 135: Code of a contract was not found + // NOTE: Reserved, but one has to replace the contract code to trigger it + + // 136: Invalid address + it("should test exit code 136", async () => { + await testReservedExitCode(136, contract, treasure); + }); + + // 137: Masterchain support is not enabled for this contract + it("should test exit code 137", async () => { + await testReservedExitCode(137, contract, treasure); + }); +}); + +async function testReservedExitCode( + code: number, + contract: SandboxContract, + treasure: SandboxContract, +) { + expect(code).toBeGreaterThanOrEqual(128); + expect(code).toBeLessThan(256); + expect([128, 130, 132, 134, 136, 137]).toContain(code); + type testedExitCodes = "128" | "130" | "132" | "134" | "136" | "137"; + + const sendResult = await contract.send( + treasure.getSender(), + { value: toNano("10") }, + code.toString(10) as testedExitCodes, + ); + + expect(sendResult.transactions).toHaveTransaction({ + from: code === 130 ? contract.address : treasure.address, + to: contract.address, + success: false, + exitCode: code, + }); +} diff --git a/tact.config.json b/tact.config.json index 9dfbc0523..fd858093e 100644 --- a/tact.config.json +++ b/tact.config.json @@ -420,6 +420,19 @@ "name": "asm-functions", "path": "./src/test/e2e-emulated/contracts/asm-functions.tact", "output": "./src/test/e2e-emulated/contracts/output" + }, + { + "name": "tact-reserved-contract-errors", + "path": "./src/test/exit-codes/contracts/tact-reserved-contract-errors.tact", + "output": "./src/test/exit-codes/contracts/output", + "options": { + "debug": true + } + }, + { + "name": "compute-phase-errors", + "path": "./src/test/exit-codes/contracts/compute-phase-errors.tact", + "output": "./src/test/exit-codes/contracts/output" } ] }