Skip to content

Commit

Permalink
fix(codegen): always store contract data if init() is used (#2077)
Browse files Browse the repository at this point in the history
  • Loading branch information
anton-trunov authored Feb 28, 2025
1 parent bd242c2 commit bbfe31c
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 53 deletions.
2 changes: 1 addition & 1 deletion dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/generator/writers/writeRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ function writeReceiverBody(
}
if (
enabledAlwaysSaveContractData(wCtx.ctx) ||
contract.init?.kind !== "contract-params" ||
rcv.effects.has("contractStorageWrite")
) {
writeStoreContractVariables(contract, wCtx);
Expand Down
26 changes: 13 additions & 13 deletions src/test/benchmarks/__snapshots__/benchmarks.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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`;
1 change: 1 addition & 0 deletions src/test/benchmarks/benchmarks.build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const main = async () => {
debug: false,
experimental: { inline: false },
safety: { nullChecks: false },
optimizations: { alwaysSaveContractData: false },
});
};

Expand Down
39 changes: 11 additions & 28 deletions src/test/benchmarks/contracts/escrow.tact
Original file line number Diff line number Diff line change
Expand Up @@ -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!!,
Expand Down
2 changes: 2 additions & 0 deletions src/test/benchmarks/escrow/escrow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ describe("Escrow Gas Tests", () => {
lastCtxId++,
seller.address,
guarantor.address,
null,
dealAmount,
royalty,
false,
assetAddress,
assetAddress ? stubJettonWalletCode : null,
),
Expand Down
12 changes: 11 additions & 1 deletion src/test/benchmarks/escrow/results.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
}
]
}
23 changes: 17 additions & 6 deletions src/test/contracts.build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
11 changes: 11 additions & 0 deletions src/test/e2e-emulated/contracts/storage-write-analysis.tact
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion src/test/e2e-emulated/debug.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
6 changes: 3 additions & 3 deletions src/test/e2e-emulated/stdlib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);

Expand Down
42 changes: 42 additions & 0 deletions src/test/e2e-emulated/storage-write-analysis.spec.ts
Original file line number Diff line number Diff line change
@@ -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<TreasuryContract>;
let contract: SandboxContract<Tester>;

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

0 comments on commit bbfe31c

Please sign in to comment.