diff --git a/dev-docs/CHANGELOG.md b/dev-docs/CHANGELOG.md index c0529dbde..99738ecda 100644 --- a/dev-docs/CHANGELOG.md +++ b/dev-docs/CHANGELOG.md @@ -72,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Struct serialization and parsing functions are now inlined more aggressively to save gas: PR [#2016](https://github.com/tact-lang/tact/pull/2016) - `NOP` instructions and empty asm functions are now properly optimized: PR [#1959](https://github.com/tact-lang/tact/pull/1959) - Contracts are now compiled with custom optimized function selector with a shortcut for `recv_internal` and `recv_external`: PR [#2038](https://github.com/tact-lang/tact/pull/2038) -- Contract receivers do not update the contract data cell at the end of execution if the receiver does not modify the contract storage: PR [#2067](https://github.com/tact-lang/tact/pull/2067) +- Contract receivers do not update the contract data cell at the end of execution if the receiver does not modify the contract storage: PR [#2067](https://github.com/tact-lang/tact/pull/2067), PR [#2077](https://github.com/tact-lang/tact/pull/2077) ### Fixed diff --git a/src/generator/writers/writeRouter.ts b/src/generator/writers/writeRouter.ts index 1fd74827d..7ae562fb2 100644 --- a/src/generator/writers/writeRouter.ts +++ b/src/generator/writers/writeRouter.ts @@ -503,6 +503,7 @@ function writeReceiverBody( } if ( enabledAlwaysSaveContractData(wCtx.ctx) || + contract.init?.kind !== "contract-params" || rcv.effects.has("contractStorageWrite") ) { writeStoreContractVariables(contract, wCtx); diff --git a/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap b/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap index 7d352ad35..c80b1f34a 100644 --- a/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap +++ b/src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap @@ -14,32 +14,32 @@ exports[`benchmarks benchmark codeOf vs myCode(): gas used myCode 1`] = `893n`; exports[`benchmarks benchmark contractAddressExt: gas used contractAddressExt 1`] = `2551n`; -exports[`benchmarks benchmark deployable trait vs raw deploy: gas used deploy trait 1`] = `3844`; +exports[`benchmarks benchmark deployable trait vs raw deploy: gas used deploy trait 1`] = `4468`; -exports[`benchmarks benchmark deployable trait vs raw deploy: gas used raw deploy 1`] = `845`; +exports[`benchmarks benchmark deployable trait vs raw deploy: gas used raw deploy 1`] = `1505`; exports[`benchmarks benchmark functions: code size 1`] = `184`; exports[`benchmarks benchmark functions: gas used 1`] = `2128`; -exports[`benchmarks benchmark readFwdFee: code size 1`] = `112`; +exports[`benchmarks benchmark readFwdFee: code size 1`] = `121`; -exports[`benchmarks benchmark readFwdFee: gas used 1`] = `1344`; +exports[`benchmarks benchmark readFwdFee: gas used 1`] = `1968`; -exports[`benchmarks benchmark sha256: gas hash string big 1`] = `2394`; +exports[`benchmarks benchmark sha256: gas hash string big 1`] = `2539`; -exports[`benchmarks benchmark sha256: gas hash string big repeated 1`] = `2395`; +exports[`benchmarks benchmark sha256: gas hash string big repeated 1`] = `2540`; -exports[`benchmarks benchmark sha256: gas hash string big repeated more 1`] = `2397`; +exports[`benchmarks benchmark sha256: gas hash string big repeated more 1`] = `2542`; -exports[`benchmarks benchmark sha256: gas hash string slice 1`] = `2394`; +exports[`benchmarks benchmark sha256: gas hash string slice 1`] = `2539`; -exports[`benchmarks benchmark sha256: gas hash string slice repeated 1`] = `2395`; +exports[`benchmarks benchmark sha256: gas hash string slice repeated 1`] = `2540`; -exports[`benchmarks benchmark sha256: gas hash string slice repeated more 1`] = `2397`; +exports[`benchmarks benchmark sha256: gas hash string slice repeated more 1`] = `2542`; -exports[`benchmarks benchmark sha256: gas hash string small 1`] = `1859`; +exports[`benchmarks benchmark sha256: gas hash string small 1`] = `1969`; -exports[`benchmarks benchmark sha256: gas hash string small repeated 1`] = `1859`; +exports[`benchmarks benchmark sha256: gas hash string small repeated 1`] = `1969`; -exports[`benchmarks benchmark sha256: gas hash string small repeated more 1`] = `1859`; +exports[`benchmarks benchmark sha256: gas hash string small repeated more 1`] = `1969`; diff --git a/src/test/benchmarks/benchmarks.build.ts b/src/test/benchmarks/benchmarks.build.ts index 3bea12b95..cc168bab1 100644 --- a/src/test/benchmarks/benchmarks.build.ts +++ b/src/test/benchmarks/benchmarks.build.ts @@ -19,6 +19,7 @@ const main = async () => { debug: false, experimental: { inline: false }, safety: { nullChecks: false }, + optimizations: { alwaysSaveContractData: false }, }); }; diff --git a/src/test/benchmarks/contracts/escrow.tact b/src/test/benchmarks/contracts/escrow.tact index 6b94b5745..3dd65d867 100644 --- a/src/test/benchmarks/contracts/escrow.tact +++ b/src/test/benchmarks/contracts/escrow.tact @@ -29,40 +29,23 @@ struct EscrowData { buyerAddress: Address?; // null if not funded, set after funding } -contract Escrow { - id: Int as uint32; - sellerAddress: Address; - guarantorAddress: Address; - buyerAddress: Address?; - dealAmount: Int as coins; - guarantorRoyaltyPercent: Int as uint32; - isFunded: Bool; - assetAddress: Address?; - jettonWalletCode: Cell?; +contract Escrow ( + id: Int as uint32, + sellerAddress: Address, + guarantorAddress: Address, + buyerAddress: Address?, + dealAmount: Int as coins, + guarantorRoyaltyPercent: Int as uint32, + isFunded: Bool, + assetAddress: Address?, + jettonWalletCode: Cell?, +) { const GUARANTOR_PERCENT_CONST: Int = 100000; const GUARANTOR_PERCENT_MAX: Int = 90000; const JETTON_TRANSFER_GAS: Int = ton("0.05"); const TON_TRANSFER_GAS: Int = ton("0.015"); - init(id: Int, - sellerAddress: Address, - guarantorAddress: Address, - dealAmount: Int, - guarantorRoyaltyPercent: Int, - assetAddress: Address?, - jettonWalletCode: Cell?){ - self.id = id; - self.sellerAddress = sellerAddress; - self.guarantorAddress = guarantorAddress; - self.dealAmount = dealAmount; - self.guarantorRoyaltyPercent = guarantorRoyaltyPercent; - self.assetAddress = assetAddress; - self.jettonWalletCode = jettonWalletCode; - self.isFunded = false; - self.buyerAddress = null; - } - fun sendJettons(receiver: Address, amount: Int, mode: Int) { let escrowJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.assetAddress!!, diff --git a/src/test/benchmarks/escrow/escrow.spec.ts b/src/test/benchmarks/escrow/escrow.spec.ts index 98870569f..9569c85a1 100644 --- a/src/test/benchmarks/escrow/escrow.spec.ts +++ b/src/test/benchmarks/escrow/escrow.spec.ts @@ -135,8 +135,10 @@ describe("Escrow Gas Tests", () => { lastCtxId++, seller.address, guarantor.address, + null, dealAmount, royalty, + false, assetAddress, assetAddress ? stubJettonWalletCode : null, ), diff --git a/src/test/benchmarks/escrow/results.json b/src/test/benchmarks/escrow/results.json index 14abc7149..396fc8498 100644 --- a/src/test/benchmarks/escrow/results.json +++ b/src/test/benchmarks/escrow/results.json @@ -122,7 +122,7 @@ }, { "label": "1.5.3 with optimized contract storage saving", - "pr": "https://github.com/tact-lang/tact/pull/TODO", + "pr": "https://github.com/tact-lang/tact/pull/2067", "gas": { "fundingTon": "4917", "changeCode": "5123", @@ -139,6 +139,16 @@ "approveTon": "7010", "cancelTon": "4829" } + }, + { + "label": "1.5.3 with contract parameters (no lazy init)", + "pr": "https://github.com/tact-lang/tact/pull/2077", + "gas": { + "fundingTon": "4768", + "changeCode": "4892", + "approveTon": "6968", + "cancelTon": "4997" + } } ] } diff --git a/src/test/contracts.build.ts b/src/test/contracts.build.ts index bfbe145c1..3039974f7 100644 --- a/src/test/contracts.build.ts +++ b/src/test/contracts.build.ts @@ -5,12 +5,23 @@ const main = async () => { // Disable version number in packages __DANGER__disableVersionNumber(); - await allInFolder(__dirname, [ - "e2e-emulated/contracts/*.tact", - "codegen/all-contracts.tact", - "exit-codes/contracts/*.tact", - "send-modes/contracts/*.tact", - ]); + const options = { + debug: true, + experimental: { inline: false }, + safety: { nullChecks: false }, + optimizations: { alwaysSaveContractData: false }, + }; + + await allInFolder( + __dirname, + [ + "e2e-emulated/contracts/*.tact", + "codegen/all-contracts.tact", + "exit-codes/contracts/*.tact", + "send-modes/contracts/*.tact", + ], + options, + ); }; void main(); diff --git a/src/test/e2e-emulated/contracts/storage-write-analysis.tact b/src/test/e2e-emulated/contracts/storage-write-analysis.tact new file mode 100644 index 000000000..8049f8c1c --- /dev/null +++ b/src/test/e2e-emulated/contracts/storage-write-analysis.tact @@ -0,0 +1,11 @@ +contract Tester { + + init() { + // if storage write analysis is implemented correctly, this will send + // just one message back to self, otherwise it will spam with messages + // until it runs out of gas + message(MessageParameters { to: myAddress(), value: 0, mode: SendRemainingValue }) + } + + receive() { } // does not modify the contract state +} diff --git a/src/test/e2e-emulated/debug.spec.ts b/src/test/e2e-emulated/debug.spec.ts index fb6d3db68..6e5b4ea55 100644 --- a/src/test/e2e-emulated/debug.spec.ts +++ b/src/test/e2e-emulated/debug.spec.ts @@ -48,7 +48,7 @@ describe("debug", () => { expect(debugLogs).toStrictEqual(`File ${filePath}:10:9: dumpStack() -stack(1 values) : 10000000000 +stack(2 values) : 10000000000 () File ${filePath}:11:9: dump("Hello world!") Hello world! diff --git a/src/test/e2e-emulated/stdlib.spec.ts b/src/test/e2e-emulated/stdlib.spec.ts index 5f46c7f48..27f4a4d41 100644 --- a/src/test/e2e-emulated/stdlib.spec.ts +++ b/src/test/e2e-emulated/stdlib.spec.ts @@ -57,7 +57,7 @@ describe("stdlib", () => { .toString(), ).toBe(beginCell().storeBit(true).endCell().toString()); - expect(Number(await contract.getTvm_2023_07Upgrade())).toEqual(1183); + expect(Number(await contract.getTvm_2023_07Upgrade())).toEqual(1243); expect(await contract.getTvm_2024_04Upgrade()).toEqual(82009144n); expect( @@ -178,8 +178,8 @@ describe("stdlib", () => { expect(await contract.getBlockLt()).toBe(0n); - expect(Number(await contract.getSetGasLimit(5000n))).toBe(3725); // 5000 just to make sure it's enough, 3725 is how much it actually costs - await expect(contract.getSetGasLimit(3724n)).rejects.toThrow("-14"); // 3724 gas is not enough for sure + expect(Number(await contract.getSetGasLimit(5000n))).toBe(3785); // 5000 just to make sure it's enough, 3785 is how much it actually costs + await expect(contract.getSetGasLimit(3784n)).rejects.toThrow("-14"); // 3784 gas is not enough for sure expect(await contract.getGetSeed()).toBe(0n); diff --git a/src/test/e2e-emulated/storage-write-analysis.spec.ts b/src/test/e2e-emulated/storage-write-analysis.spec.ts new file mode 100644 index 000000000..7425531af --- /dev/null +++ b/src/test/e2e-emulated/storage-write-analysis.spec.ts @@ -0,0 +1,42 @@ +import { toNano } from "@ton/core"; +import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { Blockchain } from "@ton/sandbox"; +import { Tester } from "./contracts/output/storage-write-analysis_Tester"; +import "@ton/test-utils"; + +describe("storage-write-analysis", () => { + let blockchain: Blockchain; + let treasure: SandboxContract; + let contract: SandboxContract; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + blockchain.verbosity.print = false; + treasure = await blockchain.treasury("treasure"); + + contract = blockchain.openContract(await Tester.fromInit()); + + const deployResult = await contract.send( + treasure.getSender(), + { value: toNano("1") }, + null, + ); + expect(deployResult.transactions).toHaveTransaction({ + from: treasure.address, + to: contract.address, + actionResultCode: 0, + success: true, + deploy: true, + }); + expect(deployResult.transactions).toHaveTransaction({ + from: contract.address, + to: contract.address, + success: true, + }); + expect(deployResult.transactions.length).toStrictEqual(1 + 1 + 1); // wallet + deploy + one message back to self in init + }); + + it("should persist contract storage when init is called before the empty receiver", async () => { + // the deployment has the test + }); +});