diff --git a/src/cli/unboc/__snapshots__/e2e.spec.ts.snap b/src/cli/unboc/__snapshots__/e2e.spec.ts.snap index d96b2e66d..03a68ae33 100644 --- a/src/cli/unboc/__snapshots__/e2e.spec.ts.snap +++ b/src/cli/unboc/__snapshots__/e2e.spec.ts.snap @@ -10,10 +10,9 @@ PROGRAM{ DECLPROC recv_internal 78250 DECLMETHOD ?fun_78250 DECLPROC ?fun_ref_0cd12ec96bb24bd7 - DECLPROC ?fun_ref_272cf0193c781008 DECLPROC ?fun_ref_92183b49329bb4e4 recv_internal PROC:<{ - SWAP + DROP CTOS TWO SDSKIPFIRST @@ -22,22 +21,15 @@ PROGRAM{ LDMSGADDR OVER s3 s4 XCHG - s6 s6 XCHG2 + s5 s5 XCHG2 4 TUPLE 1 SETGLOB - s0 s2 XCHG + SWAP 2 SETGLOB ?fun_ref_0cd12ec96bb24bd7 INLINECALLDICT - s0 s2 XCHG - ?fun_ref_272cf0193c781008 INLINECALLDICT - 130 THROWIFNOT DROP - NEWC - -1 PUSHINT - SWAP - 1 STI - ENDC - POPROOT + IFRET + 130 THROW }> ?fun_78250 PROC:<{ ?fun_ref_0cd12ec96bb24bd7 INLINECALLDICT @@ -55,14 +47,6 @@ PROGRAM{ IFJMP NULL }> - ?fun_ref_272cf0193c781008 PROCREF:<{ - NIP - <{ - -1 PUSHINT - }> PUSHCONT - IFJMP - ZERO - }> ?fun_ref_92183b49329bb4e4 PROCREF:<{ x{68656C6C6F20776F726C64} PUSHSLICE }> @@ -81,10 +65,9 @@ PROGRAM{ DECLPROC recv_internal 78250 DECLMETHOD ?fun_78250 DECLPROC ?fun_ref_0cd12ec96bb24bd7 - DECLPROC ?fun_ref_272cf0193c781008 DECLPROC ?fun_ref_92183b49329bb4e4 recv_internal PROC:<{ - SWAP // 0x0 1 + DROP // 0x3 0 CTOS // 0xD0 TWO // 0x7 2 SDSKIPFIRST // 0xD721 @@ -93,22 +76,15 @@ PROGRAM{ LDMSGADDR // 0xFA40 OVER // 0x2 1 s3 s4 XCHG // 0x10 3 4 - s6 s6 XCHG2 // 0x50 6 6 + s5 s5 XCHG2 // 0x50 5 5 4 TUPLE // 0x6F0 4 1 SETGLOB // 0xF87_ 0C_ - s0 s2 XCHG // 0x0 2 + SWAP // 0x0 1 2 SETGLOB // 0xF87_ 14_ ?fun_ref_0cd12ec96bb24bd7 INLINECALLDICT // 0x - s0 s2 XCHG // 0x0 2 - ?fun_ref_272cf0193c781008 INLINECALLDICT // 0x - 130 THROWIFNOT // 0xF2E4_ 105_ DROP // 0x3 0 - NEWC // 0xC8 - -1 PUSHINT // 0x7 F - SWAP // 0x0 1 - 1 STI // 0xCA 00 - ENDC // 0xC9 - POPROOT // 0xED5 4 + IFRET // 0xDC + 130 THROW // 0xF2C4_ 105_ }> ?fun_78250 PROC:<{ ?fun_ref_0cd12ec96bb24bd7 INLINECALLDICT // 0x @@ -126,14 +102,6 @@ PROGRAM{ IFJMP // 0xE0 NULL // 0x6D }> - ?fun_ref_272cf0193c781008 PROCREF:<{ - NIP // 0x3 1 - <{ - -1 PUSHINT // 0x7 F - }> PUSHCONT // 0x9 7F - IFJMP // 0xE0 - ZERO // 0x7 0 - }> ?fun_ref_92183b49329bb4e4 PROCREF:<{ x{68656C6C6F20776F726C64} PUSHSLICE // 0x8B 68656C6C6F20776F726C64 }> @@ -152,10 +120,9 @@ PROGRAM{ DECLPROC recv_internal 78250 DECLMETHOD ?fun_78250 DECLPROC ?fun_ref_0cd12ec96bb24bd7 - DECLPROC ?fun_ref_272cf0193c781008 DECLPROC ?fun_ref_92183b49329bb4e4 recv_internal PROC:<{ - s0 s1 XCHG + s0 POP CTOS 2 PUSHINT SDSKIPFIRST @@ -164,22 +131,15 @@ PROGRAM{ LDMSGADDR s1 PUSH s3 s4 XCHG - s6 s6 XCHG2 + s5 s5 XCHG2 4 TUPLE 1 SETGLOB - s0 s2 XCHG + s0 s1 XCHG 2 SETGLOB ?fun_ref_0cd12ec96bb24bd7 INLINECALLDICT - s0 s2 XCHG - ?fun_ref_272cf0193c781008 INLINECALLDICT - 130 THROWIFNOT s0 POP - NEWC - -1 PUSHINT - s0 s1 XCHG - 1 STI - ENDC - c4 POPCTR + IFRET + 130 THROW }> ?fun_78250 PROC:<{ ?fun_ref_0cd12ec96bb24bd7 INLINECALLDICT @@ -197,14 +157,6 @@ PROGRAM{ IFJMP NULL }> - ?fun_ref_272cf0193c781008 PROCREF:<{ - s1 POP - <{ - -1 PUSHINT - }> PUSHCONT - IFJMP - 0 PUSHINT - }> ?fun_ref_92183b49329bb4e4 PROCREF:<{ x{68656C6C6F20776F726C64} PUSHSLICE }> @@ -223,7 +175,7 @@ PROGRAM{ DECLPROC recv_internal 78250 DECLMETHOD ?fun_78250 recv_internal PROC:<{ - SWAP + DROP CTOS TWO SDSKIPFIRST @@ -232,10 +184,10 @@ PROGRAM{ LDMSGADDR OVER s3 s4 XCHG - s6 s6 XCHG2 + s5 s5 XCHG2 4 TUPLE 1 SETGLOB - s0 s2 XCHG + SWAP 2 SETGLOB <{ PUSHROOT @@ -248,23 +200,9 @@ PROGRAM{ IFJMP NULL }>c CALLREF - s0 s2 XCHG - <{ - NIP - <{ - -1 PUSHINT - }> PUSHCONT - IFJMP - ZERO - }>c CALLREF - 130 THROWIFNOT DROP - NEWC - -1 PUSHINT - SWAP - 1 STI - ENDC - POPROOT + IFRET + 130 THROW }> ?fun_78250 PROC:<{ <{ diff --git a/src/generator/writers/ops.ts b/src/generator/writers/ops.ts index d25edfb6a..0e5f8be8c 100644 --- a/src/generator/writers/ops.ts +++ b/src/generator/writers/ops.ts @@ -63,23 +63,6 @@ export const ops = { used(`$${type}$_contract_load`, ctx), contractStore: (type: string, ctx: WriterContext) => used(`$${type}$_contract_store`, ctx), - contractRouter: (type: string, kind: "internal" | "external") => - `$${type}$_contract_router_${kind}`, // Not rendered as dependency - - // Router operations - receiveEmpty: (type: string, kind: "internal" | "external") => - `%$${type}$_${kind}_empty`, - receiveType: (type: string, kind: "internal" | "external", msg: string) => - `$${type}$_${kind}_binary_${msg}`, - receiveAnyText: (type: string, kind: "internal" | "external") => - `$${type}$_${kind}_any_text`, - receiveText: (type: string, kind: "internal" | "external", hash: string) => - `$${type}$_${kind}_text_${hash}`, - receiveAny: (type: string, kind: "internal" | "external") => - `$${type}$_${kind}_any`, - receiveTypeBounce: (type: string, msg: string) => - `$${type}$_receive_binary_bounce_${msg}`, - receiveBounceAny: (type: string) => `$${type}$_receive_bounce_fallback`, // Functions extension: (type: string, name: string) => `$${type}$_fun_${name}`, diff --git a/src/generator/writers/writeContract.ts b/src/generator/writers/writeContract.ts index ce2978686..3b59554c1 100644 --- a/src/generator/writers/writeContract.ts +++ b/src/generator/writers/writeContract.ts @@ -1,4 +1,3 @@ -import { contractErrors } from "../../abi/errors"; import { enabledInline, enabledInterfacesGetter, @@ -15,8 +14,14 @@ import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; import { writeValue } from "./writeExpression"; import { writeGetter, writeStatement } from "./writeFunction"; import { writeInterfaces } from "./writeInterfaces"; -import { writeReceiver, writeRouter } from "./writeRouter"; +import { + groupContractReceivers, + writeBouncedRouter, + writeNonBouncedRouter, +} from "./writeRouter"; import type { ItemOrigin } from "../../imports/source"; +import { resolveFuncTypeFromAbiUnpack } from "./resolveFuncTypeFromAbiUnpack"; +import { getAllocation } from "../../storage/resolveAllocation"; export function writeStorageOps( type: TypeDescription, @@ -252,148 +257,124 @@ export function writeInit( } export function writeMainContract( - type: TypeDescription, + contract: TypeDescription, abiLink: string, - ctx: WriterContext, + wCtx: WriterContext, ) { // Main field - ctx.main(() => { - // Comments - ctx.append(`;;`); - ctx.append(`;; Receivers of a Contract ${type.name}`); - ctx.append(`;;`); - ctx.append(``); - - // Write receivers - for (const r of type.receivers) { - writeReceiver(type, r, ctx); - } - - // Comments - ctx.append(`;;`); - ctx.append(`;; Get methods of a Contract ${type.name}`); - ctx.append(`;;`); - ctx.append(``); + wCtx.main(() => { + wCtx.append(`;;`); + wCtx.append(`;; Get methods of a Contract ${contract.name}`); + wCtx.append(`;;`); + wCtx.append(); // Getters - for (const f of type.functions.values()) { + for (const f of contract.functions.values()) { if (f.isGetter) { - writeGetter(f, ctx); + writeGetter(f, wCtx); } } // Interfaces - if (enabledInterfacesGetter(ctx.ctx)) { - writeInterfaces(type, ctx); + if (enabledInterfacesGetter(wCtx.ctx)) { + writeInterfaces(contract, wCtx); } // ABI - if (enabledIpfsAbiGetter(ctx.ctx)) { - ctx.append(`_ get_abi_ipfs() method_id {`); - ctx.inIndent(() => { - ctx.append(`return "${abiLink}";`); + if (enabledIpfsAbiGetter(wCtx.ctx)) { + wCtx.append(`_ get_abi_ipfs() method_id {`); + wCtx.inIndent(() => { + wCtx.append(`return "${abiLink}";`); }); - ctx.append(`}`); - ctx.append(); + wCtx.append(`}`); + wCtx.append(); } - if (enabledLazyDeploymentCompletedGetter(ctx.ctx)) { + if (enabledLazyDeploymentCompletedGetter(wCtx.ctx)) { // Deployed - ctx.append(`_ lazy_deployment_completed() method_id {`); - ctx.inIndent(() => { - ctx.append(`return get_data().begin_parse().load_int(1);`); + wCtx.append(`_ lazy_deployment_completed() method_id {`); + wCtx.inIndent(() => { + wCtx.append(`return get_data().begin_parse().load_int(1);`); }); - ctx.append(`}`); - ctx.append(); + wCtx.append(`}`); + wCtx.append(); } - // Comments - ctx.append(`;;`); - ctx.append(`;; Routing of a Contract ${type.name}`); - ctx.append(`;;`); - ctx.append(``); + wCtx.append(`;;`); + wCtx.append(`;; Routing of a Contract ${contract.name}`); + wCtx.append(`;;`); + wCtx.append(); - // Render body - const hasExternal = type.receivers.find((v) => - v.selector.kind.startsWith("external-"), - ); - - writeRouter(type, ctx); + const contractReceivers = groupContractReceivers(contract); // Render internal receiver - ctx.append( - `() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {`, - ); - ctx.inIndent(() => { - // Load context - ctx.append(); - ctx.append(`;; Context`); - ctx.append(`var cs = in_msg_cell.begin_parse();`); - ctx.append(`cs~skip_bits(2);`); // skip int_msg_info$0 ihr_disabled:Bool - ctx.append(`var msg_bounceable = cs~load_int(1);`); // bounce:Bool - ctx.append(`var msg_bounced = cs~load_int(1);`); // bounced:Bool - ctx.append(`slice msg_sender_addr = cs~load_msg_addr();`); - ctx.append( - `__tact_context = (msg_bounceable, msg_sender_addr, msg_value, cs);`, - ); - ctx.append(`__tact_context_sender = msg_sender_addr;`); - ctx.append(); - - // Load self - ctx.append(`;; Load contract data`); - ctx.append(`var self = ${ops.contractLoad(type.name, ctx)}();`); - ctx.append(); + wCtx.inBlock( + "() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure", + () => { + wCtx.append(); + wCtx.append(`;; Context`); + wCtx.append(`var cs = in_msg_cell.begin_parse();`); + wCtx.append(`cs~skip_bits(2);`); // skip int_msg_info$0 ihr_disabled:Bool + wCtx.append(`var msg_bounceable = cs~load_int(1);`); // bounce:Bool + wCtx.append(`var msg_bounced = cs~load_int(1);`); // bounced:Bool + wCtx.append(`slice msg_sender_addr = cs~load_msg_addr();`); + wCtx.append( + `__tact_context = (msg_bounceable, msg_sender_addr, msg_value, cs);`, + ); + wCtx.append(`__tact_context_sender = msg_sender_addr;`); + wCtx.append(); - // Process operation - ctx.append(`;; Handle operation`); - ctx.append( - `int handled = self~${ops.contractRouter(type.name, "internal")}(in_msg, msg_bounced);`, - ); - ctx.append(); + // Load self + wCtx.append(`;; Load contract data`); + const contractVariables = resolveFuncTypeFromAbiUnpack( + "$self", + getAllocation(wCtx.ctx, contract.name).ops, + wCtx, + ); + wCtx.append( + `var ${contractVariables} = ${ops.contractLoad(contract.name, wCtx)}();`, + ); + wCtx.append(); - // Throw if not handled - ctx.append(`;; Throw if not handled`); - ctx.append( - `throw_unless(${contractErrors.invalidMessage.id}, handled);`, - ); - ctx.append(); + writeBouncedRouter(contractReceivers.bounced, contract, wCtx); - // Persist state - ctx.append(`;; Persist state`); - ctx.append(`${ops.contractStore(type.name, ctx)}(self);`); - }); - ctx.append("}"); - ctx.append(); + writeNonBouncedRouter( + contractReceivers.internal, + contract, + wCtx, + ); + }, + ); + wCtx.append(); // Render external receiver + const hasExternal = !( + contractReceivers.external.binary.length === 0 && + contractReceivers.external.comment.length === 0 && + typeof contractReceivers.external.commentFallback === "undefined" && + typeof contractReceivers.external.empty === "undefined" && + typeof contractReceivers.external.fallback === "undefined" + ); if (hasExternal) { - ctx.append(`() recv_external(slice in_msg) impure {`); - ctx.inIndent(() => { + wCtx.inBlock("() recv_external(slice in_msg) impure", () => { // Load self - ctx.append(`;; Load contract data`); - ctx.append(`var self = ${ops.contractLoad(type.name, ctx)}();`); - ctx.append(); - - // Process operation - ctx.append(`;; Handle operation`); - ctx.append( - `int handled = self~${ops.contractRouter(type.name, "external")}(in_msg);`, + wCtx.append(`;; Load contract data`); + const contractVariables = resolveFuncTypeFromAbiUnpack( + "$self", + getAllocation(wCtx.ctx, contract.name).ops, + wCtx, ); - ctx.append(); - - // Throw if not handled - ctx.append(`;; Throw if not handled`); - ctx.append( - `throw_unless(${contractErrors.invalidMessage.id}, handled);`, + wCtx.append( + `var ${contractVariables} = ${ops.contractLoad(contract.name, wCtx)}();`, ); - ctx.append(); + wCtx.append(); - // Persist state - ctx.append(`;; Persist state`); - ctx.append(`${ops.contractStore(type.name, ctx)}(self);`); + writeNonBouncedRouter( + contractReceivers.external, + contract, + wCtx, + ); }); - ctx.append("}"); - ctx.append(); } }); } diff --git a/src/generator/writers/writeRouter.ts b/src/generator/writers/writeRouter.ts index 53e8d1726..e4fd84d25 100644 --- a/src/generator/writers/writeRouter.ts +++ b/src/generator/writers/writeRouter.ts @@ -1,19 +1,25 @@ import { beginCell } from "@ton/core"; import { getType } from "../../types/resolveDescriptors"; -import type { ReceiverDescription, TypeDescription } from "../../types/types"; +import type { + FallbackReceiverSelector, + ReceiverDescription, + TypeDescription, +} from "../../types/types"; import type { WriterContext } from "../Writer"; import { funcIdOf } from "./id"; import { ops } from "./ops"; -import { resolveFuncType } from "./resolveFuncType"; import { resolveFuncTypeUnpack } from "./resolveFuncTypeUnpack"; import { writeStatement } from "./writeFunction"; -import type { AstNumber } from "../../ast/ast"; +import type { AstNumber, AstReceiver, AstStatement } from "../../ast/ast"; import { throwCompilationError, throwInternal, throwInternalCompilerError, } from "../../error/errors"; import type { SrcInfo } from "../../grammar"; +import { contractErrors } from "../../abi/errors"; +import { resolveFuncTypeFromAbiUnpack } from "./resolveFuncTypeFromAbiUnpack"; +import { getAllocation } from "../../storage/resolveAllocation"; type ContractReceivers = { readonly internal: Receivers; @@ -28,83 +34,24 @@ type Receivers = { empty: ReceiverDescription | undefined; binary: ReceiverDescription[]; comment: ReceiverDescription[]; - commentFallback: ReceiverDescription | undefined; - fallback: ReceiverDescription | undefined; + commentFallback: FallbackReceiver | undefined; + fallback: FallbackReceiver | undefined; +}; + +type FallbackReceiver = { + selector: FallbackReceiverSelector; + ast: AstReceiver; }; type BouncedReceivers = { binary: ReceiverDescription[]; - fallback: ReceiverDescription | undefined; + fallback: FallbackReceiver | undefined; }; -export function writeRouter( - contract: TypeDescription, - wCtx: WriterContext, -): void { - const contractReceivers: ContractReceivers = - groupContractReceivers(contract); - const contractFuncType = resolveFuncType(contract, wCtx); - writeInternalRouter( - contractReceivers.internal, - contractReceivers.bounced, - contract.name, - contractFuncType, - wCtx, - ); - writeExternalRouter( - contractReceivers.external, - contract.name, - contractFuncType, - wCtx, - ); -} - -function writeInternalRouter( - internalReceivers: Receivers, - bouncedReceivers: BouncedReceivers, - contractName: string, - contractFuncType: string, - wCtx: WriterContext, -): void { - wCtx.inBlock( - `(${contractFuncType}, int) ${ops.contractRouter(contractName, "internal")}(${contractFuncType} self, slice in_msg, int msg_bounced) impure inline_ref`, - () => { - writeBouncedRouter(bouncedReceivers, contractName, wCtx); - writeNonBouncedRouter(internalReceivers, contractName, wCtx); - }, - ); -} - -function writeExternalRouter( - externalReceivers: Receivers, - contractName: string, - contractFuncType: string, - wCtx: WriterContext, -): void { - // Special case: no external receivers at all - if ( - externalReceivers.binary.length === 0 && - externalReceivers.comment.length === 0 && - typeof externalReceivers.commentFallback === "undefined" && - typeof externalReceivers.empty === "undefined" && - typeof externalReceivers.fallback === "undefined" - ) { - // do not write the signature of recv_external - return; - } - - wCtx.inBlock( - `(${contractFuncType}, int) ${ops.contractRouter(contractName, "external")}(${contractFuncType} self, slice in_msg) impure inline_ref`, - () => { - writeNonBouncedRouter(externalReceivers, contractName, wCtx); - }, - ); -} - // empty string receiver (`receive("")`) is not allowed -function writeNonBouncedRouter( +export function writeNonBouncedRouter( receivers: Receivers, - contractName: string, + contract: TypeDescription, wCtx: WriterContext, ): void { // - Special case: there are no receivers at all @@ -115,7 +62,7 @@ function writeNonBouncedRouter( typeof receivers.commentFallback === "undefined" && typeof receivers.fallback === "undefined" ) { - wCtx.append("return (self, false);"); + wCtx.append(`throw(${contractErrors.invalidMessage.id});`); return; } @@ -126,23 +73,13 @@ function writeNonBouncedRouter( receivers.comment.length === 0 && typeof receivers.commentFallback === "undefined" ) { - wCtx.append( - `self~${ops.receiveAny(contractName, receivers.kind)}(in_msg);`, - ); - wCtx.append("return (self, true);"); + writeFallbackReceiver(receivers.fallback, contract, "in_msg", wCtx); return; } const writeBinaryReceivers = (msgOpcodeRemoved: boolean) => { receivers.binary.forEach((binRcv) => { - writeBinaryReceiver( - binRcv, - receivers.kind, - msgOpcodeRemoved, - contractName, - wCtx, - ); - + writeBinaryReceiver(binRcv, msgOpcodeRemoved, contract, wCtx); wCtx.append(); }); }; @@ -158,7 +95,7 @@ function writeNonBouncedRouter( writeBinaryReceivers(true); - wCtx.append("return (self, false);"); + wCtx.append(`throw(${contractErrors.invalidMessage.id});`); return; } @@ -191,12 +128,10 @@ function writeNonBouncedRouter( } if (typeof receivers.empty !== "undefined") { + const emptyRcv = receivers.empty; wCtx.append(";; Receive empty message"); wCtx.inBlock("if ((op == 0) & (in_msg_length <= 32))", () => { - wCtx.append( - `self~${ops.receiveEmpty(contractName, receivers.kind)}();`, - ); - wCtx.append("return (self, true);"); + writeReceiverBody(emptyRcv.ast.statements, contract, wCtx); }); } @@ -206,26 +141,23 @@ function writeNonBouncedRouter( receivers.kind, opcodeReader === "~load_uint", typeof receivers.fallback !== "undefined", - contractName, + contract, wCtx, ); if (typeof receivers.fallback !== "undefined") { wCtx.append(";; Receiver fallback"); - wCtx.append( - `self~${ops.receiveAny(contractName, receivers.kind)}(in_msg);`, - ); - wCtx.append("return (self, true);"); + writeFallbackReceiver(receivers.fallback, contract, "in_msg", wCtx); } else { - wCtx.append("return (self, false);"); + wCtx.append(`;; Throw if not handled`); + wCtx.append(`throw(${contractErrors.invalidMessage.id});`); } } function writeBinaryReceiver( binaryReceiver: ReceiverDescription, - kind: "internal" | "external", msgOpcodeRemoved: boolean, - contractName: string, + contract: TypeDescription, wCtx: WriterContext, ): void { const selector = binaryReceiver.selector; @@ -250,26 +182,26 @@ function writeBinaryReceiver( if (!msgOpcodeRemoved) { wCtx.append("in_msg~skip_bits(32);"); } - // Read message - wCtx.append( - `var msg = in_msg~${ops.reader(selector.type, "no-opcode", wCtx)}();`, + const msgFields = resolveFuncTypeUnpack( + selector.type, + funcIdOf(selector.name), + wCtx, ); - // Execute function wCtx.append( - `self~${ops.receiveType(contractName, kind, selector.type)}(msg);`, + `var ${msgFields} = in_msg~${ops.reader(selector.type, "no-opcode", wCtx)}();`, ); - // Exit - wCtx.append("return (self, true);"); + + writeReceiverBody(binaryReceiver.ast.statements, contract, wCtx); }); } function writeCommentReceivers( commentReceivers: ReceiverDescription[], - commentFallbackReceiver: ReceiverDescription | undefined, + commentFallbackReceiver: FallbackReceiver | undefined, kind: "internal" | "external", msgOpcodeRemoved: boolean, fallbackReceiverExists: boolean, - contractName: string, + contract: TypeDescription, wCtx: WriterContext, ): void { // - Special case: no text receivers at all @@ -279,14 +211,19 @@ function writeCommentReceivers( ) { return; } - const writeFallbackTextReceiver = () => { + const writeFallbackTextReceiver = ( + commentFallbackReceiver: FallbackReceiver, + ) => { const writeFallbackTextReceiverInternal = () => { wCtx.append(";; Fallback Text Receiver"); const inMsg = msgOpcodeRemoved ? "in_msg" : "in_msg.skip_bits(32)"; - wCtx.append( - `self~${ops.receiveAnyText(contractName, kind)}(${inMsg});`, + writeFallbackReceiver( + commentFallbackReceiver, + contract, + inMsg, + wCtx, ); - wCtx.append("return (self, true);"); + wCtx.append("return ();"); }; // We optimize fallback @@ -303,7 +240,7 @@ function writeCommentReceivers( typeof commentFallbackReceiver !== "undefined" && commentReceivers.length === 0 ) { - writeFallbackTextReceiver(); + writeFallbackTextReceiver(commentFallbackReceiver); return; } @@ -323,23 +260,15 @@ function writeCommentReceivers( !msgOpcodeRemoved, commentRcv.ast.loc, ); - const hashForReceiverFunctionName = commentPseudoOpcode( - commentRcv.selector.comment, - true, - commentRcv.ast.loc, - ); wCtx.append(`;; Receive "${commentRcv.selector.comment}" message`); wCtx.inBlock(`if (text_op == 0x${hash})`, () => { - wCtx.append( - `self~${ops.receiveText(contractName, kind, hashForReceiverFunctionName)}();`, - ); - wCtx.append("return (self, true);"); + writeReceiverBody(commentRcv.ast.statements, contract, wCtx); }); }); if (typeof commentFallbackReceiver !== "undefined") { - writeFallbackTextReceiver(); + writeFallbackTextReceiver(commentFallbackReceiver); } }; @@ -352,7 +281,9 @@ function writeCommentReceivers( } } -function groupContractReceivers(contract: TypeDescription): ContractReceivers { +export function groupContractReceivers( + contract: TypeDescription, +): ContractReceivers { const contractReceivers: ContractReceivers = { internal: { kind: "internal", @@ -389,10 +320,16 @@ function groupContractReceivers(contract: TypeDescription): ContractReceivers { contractReceivers.internal.comment.push(receiver); break; case "internal-comment-fallback": - contractReceivers.internal.commentFallback = receiver; + contractReceivers.internal.commentFallback = { + selector, + ast: receiver.ast, + }; break; case "internal-fallback": - contractReceivers.internal.fallback = receiver; + contractReceivers.internal.fallback = { + selector, + ast: receiver.ast, + }; break; case "external-empty": contractReceivers.external.empty = receiver; @@ -404,25 +341,34 @@ function groupContractReceivers(contract: TypeDescription): ContractReceivers { contractReceivers.external.comment.push(receiver); break; case "external-comment-fallback": - contractReceivers.external.commentFallback = receiver; + contractReceivers.external.commentFallback = { + selector, + ast: receiver.ast, + }; break; case "external-fallback": - contractReceivers.external.fallback = receiver; + contractReceivers.external.fallback = { + selector, + ast: receiver.ast, + }; break; case "bounce-binary": contractReceivers.bounced.binary.push(receiver); break; case "bounce-fallback": - contractReceivers.bounced.fallback = receiver; + contractReceivers.bounced.fallback = { + selector, + ast: receiver.ast, + }; break; } } return contractReceivers; } -function writeBouncedRouter( +export function writeBouncedRouter( bouncedReceivers: BouncedReceivers, - contractName: string, + contract: TypeDescription, wCtx: WriterContext, ): void { wCtx.append(";; Handle bounced messages"); @@ -432,7 +378,7 @@ function writeBouncedRouter( typeof bouncedReceivers.fallback === "undefined" && bouncedReceivers.binary.length === 0 ) { - wCtx.append("if (msg_bounced) { return (self, true); }"); + wCtx.append("if (msg_bounced) { return (); }"); return; } @@ -441,12 +387,12 @@ function writeBouncedRouter( typeof bouncedReceivers.fallback !== "undefined" && bouncedReceivers.binary.length === 0 ) { + const bouncedFallback = bouncedReceivers.fallback; wCtx.inBlock("if (msg_bounced)", () => { wCtx.append(";; Fallback bounce receiver"); wCtx.append(";; Skip 0xFFFFFFFF prefix of the bounced message"); wCtx.append("in_msg~skip_bits(32);"); - wCtx.append(`self~${ops.receiveBounceAny(contractName)}(in_msg);`); - wCtx.append("return (self, true);"); + writeFallbackReceiver(bouncedFallback, contract, "in_msg", wCtx); }); return; } @@ -469,25 +415,40 @@ function writeBouncedRouter( writeBouncedReceiver( bouncedRcv, opcodeReader === "~load_uint", - contractName, + contract, wCtx, ); wCtx.append(); }); if (typeof bouncedReceivers.fallback !== "undefined") { wCtx.append(";; Fallback bounce receiver"); - wCtx.append(`self~${ops.receiveBounceAny(contractName)}(in_msg);`); + writeFallbackReceiver( + bouncedReceivers.fallback, + contract, + "in_msg", + wCtx, + ); } // it's cheaper in terms of gas to just exit with code zero even if the // bounced message wasn't recognized, this is a common behavior of TON contracts - wCtx.append("return (self, true);"); + wCtx.append("return ();"); }); } +function writeFallbackReceiver( + fbRcv: FallbackReceiver, + contract: TypeDescription, + inMsg: string, + wCtx: WriterContext, +): void { + wCtx.append(`slice ${funcIdOf(fbRcv.selector.name)} = ${inMsg};`); + writeReceiverBody(fbRcv.ast.statements, contract, wCtx); +} + function writeBouncedReceiver( bouncedReceiver: ReceiverDescription, msgOpcodeRemoved: boolean, - contractName: string, + contract: TypeDescription, wCtx: WriterContext, ): void { const selector = bouncedReceiver.selector; @@ -503,21 +464,35 @@ function writeBouncedReceiver( if (!msgOpcodeRemoved) { wCtx.append("in_msg~skip_bits(32);"); } - // Read message - wCtx.append( - `var msg = in_msg~${selector.bounced ? ops.readerBounced(selector.type, wCtx) : ops.reader(selector.type, "no-opcode", wCtx)}();`, + const msgFields = resolveFuncTypeUnpack( + selector.type, + funcIdOf(selector.name), + wCtx, + false, + selector.bounced, ); - // Execute function - wCtx.append( - `self~${ops.receiveTypeBounce(contractName, selector.type)}(msg);`, - ); + const msgReader = selector.bounced + ? ops.readerBounced(selector.type, wCtx) + : ops.reader(selector.type, "no-opcode", wCtx); + wCtx.append(`var ${msgFields} = in_msg~${msgReader}();`); - // Exit - wCtx.append("return (self, true);"); + writeReceiverBody(bouncedReceiver.ast.statements, contract, wCtx); }); } +function writeReceiverBody( + statements: readonly AstStatement[], + contract: TypeDescription, + wCtx: WriterContext, +): void { + for (const stmt of statements) { + writeStatement(stmt, null, null, wCtx); + } + writeStoreContractVariables(contract, wCtx); + wCtx.append("return ();"); +} + function messageOpcode(n: AstNumber): string { // FunC does not support binary and octal numerals switch (n.base) { @@ -530,224 +505,19 @@ function messageOpcode(n: AstNumber): string { } } -export function writeReceiver( - self: TypeDescription, - f: ReceiverDescription, - ctx: WriterContext, -) { - const selector = f.selector; - const selfRes = resolveFuncTypeUnpack(self, funcIdOf("self"), ctx); - const selfType = resolveFuncType(self, ctx); - const selfUnpack = `var ${resolveFuncTypeUnpack(self, funcIdOf("self"), ctx)} = ${funcIdOf("self")};`; - - // Binary receiver - if ( - selector.kind === "internal-binary" || - selector.kind === "external-binary" - ) { - const args = [ - selfType + " " + funcIdOf("self"), - resolveFuncType(selector.type, ctx) + " " + funcIdOf(selector.name), - ]; - ctx.append( - `((${selfType}), ()) ${ops.receiveType(self.name, selector.kind === "internal-binary" ? "internal" : "external", selector.type)}(${args.join(", ")}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - ctx.append( - `var ${resolveFuncTypeUnpack(selector.type, funcIdOf(selector.name), ctx)} = ${funcIdOf(selector.name)};`, - ); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } - - // Empty receiver - if ( - selector.kind === "internal-empty" || - selector.kind === "external-empty" - ) { - ctx.append( - `((${selfType}), ()) ${ops.receiveEmpty(self.name, selector.kind === "internal-empty" ? "internal" : "external")}(${selfType + " " + funcIdOf("self")}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } - - // Comment receiver - if ( - selector.kind === "internal-comment" || - selector.kind === "external-comment" - ) { - const hash = commentPseudoOpcode(selector.comment, true, f.ast.loc); - ctx.append( - `(${selfType}, ()) ${ops.receiveText(self.name, selector.kind === "internal-comment" ? "internal" : "external", hash)}(${selfType + " " + funcIdOf("self")}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } - - // Fallback - if ( - selector.kind === "internal-comment-fallback" || - selector.kind === "external-comment-fallback" - ) { - ctx.append( - `(${selfType}, ()) ${ops.receiveAnyText(self.name, selector.kind === "internal-comment-fallback" ? "internal" : "external")}(${[selfType + " " + funcIdOf("self"), "slice " + funcIdOf(selector.name)].join(", ")}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } - - // Fallback - if ( - selector.kind === "internal-fallback" || - selector.kind === "external-fallback" - ) { - const kind = - selector.kind === "internal-fallback" ? "internal" : "external"; - ctx.append( - `(${selfType}, ()) ${ops.receiveAny(self.name, kind)}(${selfType} ${funcIdOf("self")}, slice ${funcIdOf(selector.name)}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } - - // Bounced - if (selector.kind === "bounce-fallback") { - ctx.append( - `(${selfType}, ()) ${ops.receiveBounceAny(self.name)}(${selfType} ${funcIdOf("self")}, slice ${funcIdOf(selector.name)}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } - - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (selector.kind === "bounce-binary") { - const args = [ - selfType + " " + funcIdOf("self"), - resolveFuncType(selector.type, ctx, false, selector.bounced) + - " " + - funcIdOf(selector.name), - ]; - ctx.append( - `((${selfType}), ()) ${ops.receiveTypeBounce(self.name, selector.type)}(${args.join(", ")}) impure inline {`, - ); - ctx.inIndent(() => { - ctx.append(selfUnpack); - ctx.append( - `var ${resolveFuncTypeUnpack(selector.type, funcIdOf(selector.name), ctx, false, selector.bounced)} = ${funcIdOf(selector.name)};`, - ); - - for (const s of f.ast.statements) { - writeStatement(s, selfRes, null, ctx); - } - - if ( - f.ast.statements.length === 0 || - f.ast.statements[f.ast.statements.length - 1]!.kind !== - "statement_return" - ) { - ctx.append(`return (${selfRes}, ());`); - } - }); - ctx.append(`}`); - ctx.append(); - return; - } +function writeStoreContractVariables( + contract: TypeDescription, + wCtx: WriterContext, +): void { + const contractVariables = resolveFuncTypeFromAbiUnpack( + "$self", + getAllocation(wCtx.ctx, contract.name).ops, + wCtx, + ); + wCtx.append(`;; Persist state`); + wCtx.append( + `${ops.contractStore(contract.name, wCtx)}(${contractVariables});`, + ); } export function commentPseudoOpcode( diff --git a/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap b/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap index 303351db1..4935b2e4a 100644 --- a/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap +++ b/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap @@ -6,28 +6,28 @@ exports[`benchmarks benchmark cells creation: gas used emptySlice 1`] = `935n`; exports[`benchmarks benchmark contractAddressExt: gas used contractAddressExt 1`] = `2846n`; -exports[`benchmarks benchmark functions: code size 1`] = `180`; +exports[`benchmarks benchmark functions: code size 1`] = `188`; -exports[`benchmarks benchmark functions: gas used 1`] = `2527`; +exports[`benchmarks benchmark functions: gas used 1`] = `2344`; -exports[`benchmarks benchmark readFwdFee: code size 1`] = `139`; +exports[`benchmarks benchmark readFwdFee: code size 1`] = `125`; -exports[`benchmarks benchmark readFwdFee: gas used 1`] = `2575`; +exports[`benchmarks benchmark readFwdFee: gas used 1`] = `2184`; -exports[`benchmarks benchmark sha256: gas hash string big 1`] = `2788`; +exports[`benchmarks benchmark sha256: gas hash string big 1`] = `2670`; -exports[`benchmarks benchmark sha256: gas hash string big repeated 1`] = `2789`; +exports[`benchmarks benchmark sha256: gas hash string big repeated 1`] = `2671`; -exports[`benchmarks benchmark sha256: gas hash string big repeated more 1`] = `2791`; +exports[`benchmarks benchmark sha256: gas hash string big repeated more 1`] = `2673`; -exports[`benchmarks benchmark sha256: gas hash string slice 1`] = `2265`; +exports[`benchmarks benchmark sha256: gas hash string slice 1`] = `2147`; -exports[`benchmarks benchmark sha256: gas hash string slice repeated 1`] = `2265`; +exports[`benchmarks benchmark sha256: gas hash string slice repeated 1`] = `2147`; -exports[`benchmarks benchmark sha256: gas hash string small 1`] = `2265`; +exports[`benchmarks benchmark sha256: gas hash string small 1`] = `2147`; -exports[`benchmarks benchmark sha256: gas hash string small repeated 1`] = `2265`; +exports[`benchmarks benchmark sha256: gas hash string small repeated 1`] = `2147`; -exports[`benchmarks benchmark sha256: gas hash string small repeated more 1`] = `2265`; +exports[`benchmarks benchmark sha256: gas hash string small repeated more 1`] = `2147`; -exports[`benchmarks benchmark sha256: gas hash string string repeated more 1`] = `2265`; +exports[`benchmarks benchmark sha256: gas hash string string repeated more 1`] = `2147`; diff --git a/src/test/benchmarks/escrow/results.json b/src/test/benchmarks/escrow/results.json index 4166d214b..357b60f16 100644 --- a/src/test/benchmarks/escrow/results.json +++ b/src/test/benchmarks/escrow/results.json @@ -69,6 +69,16 @@ "cancelTon": "8454" }, "pr": "https://github.com/tact-lang/tact/pull/1934" + }, + { + "label": "1.5.3 with inlined router", + "gas": { + "fundingTon": "6236", + "changeCode": "5474", + "approveTon": "11014", + "cancelTon": "8637" + }, + "pr": "https://github.com/tact-lang/tact/pull/1968" } ] } diff --git a/src/test/benchmarks/jetton/results.json b/src/test/benchmarks/jetton/results.json index 47c7fc71e..542ffe32f 100644 --- a/src/test/benchmarks/jetton/results.json +++ b/src/test/benchmarks/jetton/results.json @@ -161,6 +161,15 @@ "discovery": "9044" }, "pr": "https://github.com/tact-lang/tact/pull/1934" + }, + { + "label": "1.5.3 with inlined router", + "gas": { + "transfer": "17696", + "burn": "14106", + "discovery": "8850" + }, + "pr": "https://github.com/tact-lang/tact/pull/1968" } ] } diff --git a/src/test/benchmarks/util.ts b/src/test/benchmarks/util.ts index b76e6dba9..beb742e51 100644 --- a/src/test/benchmarks/util.ts +++ b/src/test/benchmarks/util.ts @@ -87,7 +87,7 @@ export function printBenchmarkTable(results: BenchmarkResult[]): void { } const table = new Table({ - head: ["Run", ...METRICS, "Commit"], + head: ["Run", ...METRICS, "PR #"], style: { head: ["cyan"], border: ["gray"], diff --git a/src/types/types.ts b/src/types/types.ts index adcb11005..ed7993993 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -168,7 +168,7 @@ type EmptyReceiverSelector = kind: "external-empty"; }; -type FallbackReceiverSelector = +export type FallbackReceiverSelector = | { kind: "internal-comment-fallback"; name: A.AstId;