From 4ad61ab2aa7fd4fb68e4352bf2006488087595f2 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Thu, 6 Mar 2025 10:54:08 +0300 Subject: [PATCH 01/16] rename to tact style --- ...nter_discoverable.tact => jetton-minter-discoverable.tact} | 2 +- .../contracts/{jetton_wallet.tact => jetton-wallet.tact} | 0 src/benchmarks/jetton/jetton.spec.ts | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/benchmarks/contracts/{jetton_minter_discoverable.tact => jetton-minter-discoverable.tact} (99%) rename src/benchmarks/contracts/{jetton_wallet.tact => jetton-wallet.tact} (100%) diff --git a/src/benchmarks/contracts/jetton_minter_discoverable.tact b/src/benchmarks/contracts/jetton-minter-discoverable.tact similarity index 99% rename from src/benchmarks/contracts/jetton_minter_discoverable.tact rename to src/benchmarks/contracts/jetton-minter-discoverable.tact index 4fe013bef..426a9183b 100644 --- a/src/benchmarks/contracts/jetton_minter_discoverable.tact +++ b/src/benchmarks/contracts/jetton-minter-discoverable.tact @@ -1,6 +1,6 @@ // https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md -import "./jetton_wallet"; +import "./jetton-wallet"; import "./messages"; const ProvideAddressGasConsumption: Int = ton("0.01"); diff --git a/src/benchmarks/contracts/jetton_wallet.tact b/src/benchmarks/contracts/jetton-wallet.tact similarity index 100% rename from src/benchmarks/contracts/jetton_wallet.tact rename to src/benchmarks/contracts/jetton-wallet.tact diff --git a/src/benchmarks/jetton/jetton.spec.ts b/src/benchmarks/jetton/jetton.spec.ts index 33e04b95b..4533348a6 100644 --- a/src/benchmarks/jetton/jetton.spec.ts +++ b/src/benchmarks/jetton/jetton.spec.ts @@ -18,7 +18,7 @@ import { storeJettonBurn, storeJettonTransfer, storeMint, -} from "../contracts/output/jetton_minter_discoverable_JettonMinter"; +} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; import "@ton/test-utils"; import benchmarkCodeSizeResults from "./results_code_size.json"; @@ -26,7 +26,7 @@ import type { JettonBurn, JettonTransfer, JettonUpdateContent, -} from "../contracts/output/jetton_minter_discoverable_JettonMinter"; +} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; import { generateCodeSizeResults, From 2fb30d6b10903f798ecbfa82e7993aa4799fe1dc Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Thu, 6 Mar 2025 11:33:05 +0300 Subject: [PATCH 02/16] add notcoin separate contracts --- .../contracts/jetton-minter-notcoin.tact | 145 +++++ .../contracts/jetton-wallet-notcoin.tact | 127 ++++ src/benchmarks/contracts/messages.tact | 18 + src/benchmarks/notcoin/notcoin.spec.ts | 607 ++++++++++++++++++ src/benchmarks/notcoin/results_code_size.json | 54 ++ src/benchmarks/notcoin/results_gas.json | 301 +++++++++ 6 files changed, 1252 insertions(+) create mode 100644 src/benchmarks/contracts/jetton-minter-notcoin.tact create mode 100644 src/benchmarks/contracts/jetton-wallet-notcoin.tact create mode 100644 src/benchmarks/notcoin/notcoin.spec.ts create mode 100644 src/benchmarks/notcoin/results_code_size.json create mode 100644 src/benchmarks/notcoin/results_gas.json diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact new file mode 100644 index 000000000..be0d3b73d --- /dev/null +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -0,0 +1,145 @@ +// https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md + +import "./messages"; +import "./jetton-wallet-notcoin"; + +const ProvideAddressGasConsumption: Int = ton("0.01"); +const Workchain: Int = 0; + +struct JettonMasterState { + totalSupply: Int as coins; + mintable: Bool; + adminAddress: Address; + jettonContent: Cell; + jettonWalletCode: Cell; +} + +contract JettonMinterNotcoin( + totalSupply: Int as coins, + owner: Address, + nextOwner: Address, + jettonContent: Cell, +) { + receive(msg: JettonBurnNotification) { + let sender = parseStdAddress(sender().asSlice()); + let wallet = getJettonBasechainWalletByOwner(msg.sender); + + throwUnless(74, sender.workchain == Workchain && sender.address == wallet.hash!!); + + self.totalSupply -= msg.amount; + + if (msg.responseDestination.isNotNone()) { + message(MessageParameters { + to: msg.responseDestination, + body: JettonExcesses{ queryId: msg.queryId }.toCell(), + value: 0, + bounce: false, + mode: SendRemainingValue | SendIgnoreErrors, // ignore errors, because supply has already been updated + }); + } + } + + receive(msg: ProvideWalletAddress) { + // we use message fwdFee for estimation of forward_payload costs + let ctx = context(); + let fwdFee = ctx.readForwardFee(); + throwUnless(75, ctx.value > fwdFee + ProvideAddressGasConsumption); + + let ownerWorkchain: Int = parseStdAddress(msg.ownerAddress.asSlice()).workchain; + + let targetJettonWallet: BasechainAddress = + (ownerWorkchain == Workchain) ? + contractBasechainAddress(initOf JettonWalletNotcoin(0, msg.ownerAddress, myAddress())) + : emptyBasechainAddress(); + + message(MessageParameters { + body: makeTakeWalletAddressMsg(targetJettonWallet, msg), + to: sender(), + value: 0, + mode: SendRemainingValue | SendBounceIfActionFail, + }); + } + + receive(msg: JettonUpdateContent) { + throwUnless(73, sender() == self.owner); + self.jettonContent = msg.content; + } + + receive(msg: Mint) { + throwUnless(73, sender() == self.owner); + self.totalSupply += msg.mintMessage.amount; + + let fwdFee = getOriginalFwdFee(context().readForwardFee(), false); + // force workchain + + // check amount + + deploy(DeployParameters{ + value: 0, + bounce: true, + mode: SendRemainingValue, + body: msg.mintMessage.toCell(), + init: getJettonWalletInit(msg.receiver) + }); + } + + receive(msg: ChangeAdmin) { + throwUnless(73, sender() == self.owner); + self.nextOwner = msg.nextAdmin; + } + + receive(msg: ClaimAdmin) { + throwUnless(73, sender() == self.nextOwner); + self.owner = self.nextOwner; + self.nextOwner = emptyAddress(); + } + + receive(msg: DropAdmin) { + throwUnless(73, sender() == self.owner); + self.owner = emptyAddress(); + self.nextOwner = emptyAddress(); + } + + // accept tons + receive(msg: TopUp) {} + + get fun get_jetton_data(): JettonMasterState { + return JettonMasterState { + totalSupply: self.totalSupply, + mintable: true, + adminAddress: self.owner, + jettonContent: self.jettonContent, + jettonWalletCode: codeOf JettonWalletNotcoin, + } + } + + get fun get_wallet_address(ownerAddress: Address): Address { + return getJettonWalletByOwner(ownerAddress); + } +} + +asm fun emptyAddress(): Address { b{00} PUSHSLICE } + +inline fun makeTakeWalletAddressMsg(targetJettonWallet: BasechainAddress, msg: ProvideWalletAddress): Cell { + return + beginCell() + .storeUint(TakeWalletAddressOpcode, 32) + .storeUint(msg.queryId, 64) + .storeBasechainAddress(targetJettonWallet) + .storeMaybeRef(msg.includeAddress ? beginCell().storeAddress(msg.ownerAddress).endCell() : null) + .endCell(); +} + +inline fun getJettonWalletInit(address: Address): StateInit { + return initOf JettonWalletNotcoin(0, address, myAddress()); +} + +inline fun getJettonWalletByOwner(jettonWalletOwner: Address): Address { + return contractAddress(getJettonWalletInit(jettonWalletOwner)); +} + +inline fun getJettonBasechainWalletByOwner(jettonWalletOwner: Address): BasechainAddress { + return contractBasechainAddress(getJettonWalletInit(jettonWalletOwner)); +} + +inline extends fun isNotNone(self: Address): Bool { return self.asSlice().preloadUint(2) != 0 } diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact new file mode 100644 index 000000000..7efac0b65 --- /dev/null +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -0,0 +1,127 @@ +import "./messages"; + +contract JettonWalletNotcoin( + balance: Int as coins, + owner: Address, + master: Address, +) { + const minTonsForStorage: Int = ton("0.01"); + const gasConsumption: Int = ton("0.015"); + + receive(msg: JettonTransfer) { + throwUnless(333, parseStdAddress(msg.destination.asSlice()).workchain == 0); + throwUnless(705, sender() == self.owner); + + self.balance -= msg.amount; + throwUnless(706, self.balance >= 0); + throwUnless(708, msg.forwardPayload.bits() >= 1); + + let ctx = context(); + let fwdCount = 1 + msg.forwardTonAmount & 1; + throwUnless(709, ctx.value > + msg.forwardTonAmount + + fwdCount * ctx.readForwardFee() + + (2 * self.gasConsumption + self.minTonsForStorage) + ); + + deploy(DeployParameters{ + value: 0, + mode: SendRemainingValue, + bounce: true, + body: JettonTransferInternal{ + queryId: msg.queryId, + amount: msg.amount, + sender: self.owner, + responseDestination: msg.responseDestination, + forwardTonAmount: msg.forwardTonAmount, + forwardPayload: msg.forwardPayload + }.toCell(), + init: initOf JettonWalletNotcoin(0, msg.destination, self.master), + }); + } + + receive(msg: JettonTransferInternal) { + self.balance += msg.amount; + + // This message should come only from master, or from other JettonWallet + let wallet: StateInit = initOf JettonWalletNotcoin(0, msg.sender, self.master); + if (!wallet.hasSameBasechainAddress(sender())) { + throwUnless(707, self.master == sender()); + } + + let ctx: Context = context(); + let msgValue: Int = ctx.value; + let tonBalanceBeforeMsg = myBalance() - msgValue; + let storageFee = self.minTonsForStorage - min(tonBalanceBeforeMsg, self.minTonsForStorage); + msgValue -= (storageFee + self.gasConsumption); + + if (msg.forwardTonAmount > 0) { + let fwdFee: Int = ctx.readForwardFee(); + msgValue -= msg.forwardTonAmount + fwdFee; + message(MessageParameters{ + to: self.owner, + value: msg.forwardTonAmount, + mode: SendPayGasSeparately, + bounce: false, + body: JettonNotification{ // 0x7362d09c -- Remind the new Owner + queryId: msg.queryId, + amount: msg.amount, + sender: msg.sender, + forwardPayload: msg.forwardPayload, + }.toCell(), + }); + } + + // 0xd53276db -- Cashback to the original Sender + if (msg.responseDestination != null && msgValue > 0) { + message(MessageParameters{ + to: msg.responseDestination!!, + value: msgValue, + mode: SendIgnoreErrors, + bounce: false, + body: JettonExcesses{ queryId: msg.queryId }.toCell(), + }); + } + } + + receive(msg: JettonBurn) { + throwUnless(705, sender() == self.owner); + + self.balance -= msg.amount; + throwUnless(706, self.balance >= 0); + + let ctx = context(); + let fwdFee: Int = ctx.readForwardFee(); + throwUnless(707, ctx.value > (fwdFee + 2 * self.gasConsumption)); + + message(MessageParameters{ + to: self.master, + value: 0, + mode: SendRemainingValue, + bounce: true, + body: JettonBurnNotification{ + queryId: msg.queryId, + amount: msg.amount, + sender: self.owner, + responseDestination: msg.responseDestination, + }.toCell(), + }); + } + + bounced(msg: bounced) { + self.balance += msg.amount; + } + + bounced(msg: bounced) { + self.balance += msg.amount; + } + + get fun get_wallet_data(): JettonWalletData { + return JettonWalletData{ + balance: self.balance, + owner: self.owner, + master: self.master, + code: myCode() + }; + } +} diff --git a/src/benchmarks/contracts/messages.tact b/src/benchmarks/contracts/messages.tact index 6031c950d..63c693f69 100644 --- a/src/benchmarks/contracts/messages.tact +++ b/src/benchmarks/contracts/messages.tact @@ -90,3 +90,21 @@ message(3) ChangeOwner { queryId: Int as uint64; newOwner: Address; } + +// notcoin +message(0xd372158c) TopUp { + queryId: Int as uint64; +} + +message(0x6501f354) ChangeAdmin { + queryId: Int as uint64; + nextAdmin: Address; +} + +message(0xfb88e119) ClaimAdmin { + queryId: Int as uint64; +} + +message(0x7431f221) DropAdmin { + queryId: Int as uint64; +} \ No newline at end of file diff --git a/src/benchmarks/notcoin/notcoin.spec.ts b/src/benchmarks/notcoin/notcoin.spec.ts new file mode 100644 index 000000000..4533348a6 --- /dev/null +++ b/src/benchmarks/notcoin/notcoin.spec.ts @@ -0,0 +1,607 @@ +import "@ton/test-utils"; +import { + Address, + beginCell, + Builder, + Cell, + contractAddress, + SendMode, + toNano, +} from "@ton/core"; +import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; +import { Blockchain } from "@ton/sandbox"; + +import { + type Mint, + type ProvideWalletAddress, + JettonMinter, + storeJettonBurn, + storeJettonTransfer, + storeMint, +} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; + +import "@ton/test-utils"; +import benchmarkCodeSizeResults from "./results_code_size.json"; +import type { + JettonBurn, + JettonTransfer, + JettonUpdateContent, +} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; + +import { + generateCodeSizeResults, + generateResults, + getStateSizeForAccount, + getUsedGas, + printBenchmarkTable, +} from "../util"; +import benchmarkResults from "./results_gas.json"; +import { join, resolve } from "path"; +import { readFileSync } from "fs"; +import { storeProvideWalletAddress } from "../contracts/output/escrow_Escrow"; +import { posixNormalize } from "../../utils/filePath"; +import { type Step, writeLog } from "../../test/utils/write-vm-log"; + +const loadFunCJettonsBoc = () => { + const bocMinter = readFileSync( + posixNormalize( + resolve( + __dirname, + "../contracts/func/output/jetton-minter-discoverable.boc", + ), + ), + ); + + const bocWallet = readFileSync( + posixNormalize( + resolve(__dirname, "../contracts/func/output/jetton-wallet.boc"), + ), + ); + + return { bocMinter, bocWallet }; +}; + +const loadNotcoinJettonsBoc = () => { + const bocMinter = readFileSync( + posixNormalize( + resolve( + __dirname, + "../contracts/func/output/jetton-minter-not.boc", + ), + ), + ); + + const bocWallet = readFileSync( + posixNormalize( + resolve( + __dirname, + "../contracts/func/output/jetton-wallet-not.boc", + ), + ), + ); + + return { bocMinter, bocWallet }; +}; + +const deployFuncJettonMinter = async ( + via: SandboxContract, +) => { + const jettonData = loadFunCJettonsBoc(); + const minterCell = Cell.fromBoc(jettonData.bocMinter)[0]!; + const walletCell = Cell.fromBoc(jettonData.bocWallet)[0]!; + + const stateInitMinter = beginCell() + .storeCoins(0) + .storeAddress(via.address) + .storeRef(beginCell().storeUint(1, 1).endCell()) // as salt + .storeRef(walletCell) + .endCell(); + + const init = { code: minterCell, data: stateInitMinter }; + + const minterAddress = contractAddress(0, init); + + return { + minterAddress, + result: await via.send({ + to: minterAddress, + value: toNano("0.1"), + init, + body: beginCell().endCell(), + sendMode: SendMode.PAY_GAS_SEPARATELY, + }), + }; +}; + +const deployNotcoinJettonMinter = async ( + via: SandboxContract, +) => { + const jettonData = loadNotcoinJettonsBoc(); + const minterCell = Cell.fromBoc(jettonData.bocMinter)[0]!; + const walletCell = Cell.fromBoc(jettonData.bocWallet)[0]!; + + const stateInitMinter = beginCell() + .storeCoins(0) + .storeAddress(via.address) + .storeAddress(null) + .storeRef(walletCell) + .storeRef(beginCell().storeUint(1, 1).endCell()) // as salt + .endCell(); + + const init = { code: minterCell, data: stateInitMinter }; + + const minterAddress = contractAddress(0, init); + + return { + minterNotcoinAddress: minterAddress, + result: await via.send({ + to: minterAddress, + value: toNano("0.1"), + init, + body: beginCell() + .storeUint(0xd372158c, 32) + .storeUint(0, 64) + .endCell(), + sendMode: SendMode.PAY_GAS_SEPARATELY, + }), + }; +}; + +const sendDiscoveryRaw = async ( + minterAddress: Address, + via: SandboxContract, + address: Address, + includeAddress: boolean, + value: bigint, +) => { + const msg: ProvideWalletAddress = { + $$type: "ProvideWalletAddress", + queryId: 0n, + ownerAddress: address, + includeAddress: includeAddress, + }; + + const msgCell = beginCell().store(storeProvideWalletAddress(msg)).endCell(); + + return await via.send({ + to: minterAddress, + value, + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +const sendTransferRaw = async ( + jettonWalletAddress: Address, + via: SandboxContract, + value: bigint, + jetton_amount: bigint, + to: Address, + responseAddress: Address, + customPayload: Cell | null, + forward_ton_amount: bigint, + forwardPayload: Cell | null, +) => { + const parsedForwardPayload = + forwardPayload != null + ? forwardPayload.beginParse() + : new Builder().storeUint(0, 1).endCell().beginParse(); //Either bit equals 0 + + const msg: JettonTransfer = { + $$type: "JettonTransfer", + queryId: 0n, + amount: jetton_amount, + destination: to, + responseDestination: responseAddress, + customPayload: customPayload, + forwardTonAmount: forward_ton_amount, + forwardPayload: parsedForwardPayload, + }; + + const msgCell = beginCell().store(storeJettonTransfer(msg)).endCell(); + + return await via.send({ + to: jettonWalletAddress, + value, + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +const sendMintRaw = async ( + jettonMinterAddress: Address, + via: SandboxContract, + to: Address, + jetton_amount: bigint, + forward_ton_amount: bigint, + total_ton_amount: bigint, +) => { + if (total_ton_amount <= forward_ton_amount) { + throw new Error( + "Total TON amount should be greater than the forward amount", + ); + } + + const msg: Mint = { + $$type: "Mint", + queryId: 0n, + receiver: to, + tonAmount: total_ton_amount, + mintMessage: { + $$type: "JettonTransferInternal", + queryId: 0n, + amount: jetton_amount, + responseDestination: jettonMinterAddress, + forwardTonAmount: forward_ton_amount, + sender: jettonMinterAddress, + forwardPayload: beginCell().storeUint(0, 1).endCell().beginParse(), + }, + }; + + const msgCell = beginCell().store(storeMint(msg)).endCell(); + + return await via.send({ + to: jettonMinterAddress, + value: total_ton_amount + toNano("0.015"), + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +const sendBurnRaw = async ( + jettonWalletAddress: Address, + via: SandboxContract, + value: bigint, + jetton_amount: bigint, + responseAddress: Address, + customPayload: Cell | null, +) => { + const msg: JettonBurn = { + $$type: "JettonBurn", + queryId: 0n, + amount: jetton_amount, + responseDestination: responseAddress, + customPayload: customPayload, + }; + + const msgCell = beginCell().store(storeJettonBurn(msg)).endCell(); + + return await via.send({ + to: jettonWalletAddress, + value, + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +const getJettonWalletRaw = async ( + minterAddress: Address, + blockchain: Blockchain, + walletAddress: Address, +) => { + const walletAddressResult = await blockchain + .provider(minterAddress) + .get(`get_wallet_address`, [ + { + type: "slice", + cell: beginCell().storeAddress(walletAddress).endCell(), + }, + ]); + + return walletAddressResult.stack.readAddress(); +}; + +describe("Jetton", () => { + let blockchain: Blockchain; + let jettonMinter: SandboxContract; + let jettonMinterFuncAddress: Address; + let jettonMinterNotcoinAddress: Address; + let deployer: SandboxContract; + let step: Step; + + let notDeployer: SandboxContract; + + let defaultContent: Cell; + + const results = generateResults(benchmarkResults); + const codeSizeResults = generateCodeSizeResults(benchmarkCodeSizeResults); + const expectedCodeSize = codeSizeResults.at(-1)!; + const funcCodeSize = codeSizeResults.at(0)!; + + const expectedResult = results.at(-1)!; + const funcResult = results.at(0)!; + const notcoinResult = results.at(1)!; + + beforeAll(async () => { + blockchain = await Blockchain.create(); + step = writeLog({ + path: join(__dirname, "output", "log.yaml"), + blockchain, + }); + + deployer = await blockchain.treasury("deployer"); + notDeployer = await blockchain.treasury("notDeployer"); + + // deploy func + const { result: deployFuncJettonMinterResult, minterAddress } = + await deployFuncJettonMinter(deployer); + + expect(deployFuncJettonMinterResult.transactions).toHaveTransaction({ + from: deployer.address, + to: minterAddress, + success: true, + deploy: true, + }); + + jettonMinterFuncAddress = minterAddress; + + // deploy notcoin + const { + result: deployNotcoinJettonMinterResult, + minterNotcoinAddress, + } = await deployNotcoinJettonMinter(deployer); + + expect(deployNotcoinJettonMinterResult.transactions).toHaveTransaction({ + from: deployer.address, + to: minterNotcoinAddress, + success: true, + deploy: true, + }); + + jettonMinterNotcoinAddress = minterNotcoinAddress; + + defaultContent = beginCell().endCell(); + const msg: JettonUpdateContent = { + $$type: "JettonUpdateContent", + queryId: 0n, + content: new Cell(), + }; + + jettonMinter = blockchain.openContract( + await JettonMinter.fromInit(0n, deployer.address, defaultContent), + ); + const deployResult = await jettonMinter.send( + deployer.getSender(), + { value: toNano("0.1") }, + msg, + ); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: jettonMinter.address, + deploy: true, + success: true, + }); + }); + + afterAll(() => { + printBenchmarkTable(results, codeSizeResults, { + implementationName: "FunC", + printMode: "full", + }); + + printBenchmarkTable(results.slice(1), undefined, { + implementationName: "NotCoin", + printMode: "first-last", + }); + }); + + it("transfer", async () => { + const runMintTest = async (minterAddress: Address) => { + const mintResult = await step("mint", () => + sendMintRaw( + minterAddress, + deployer, + deployer.address, + toNano(100000), + toNano("0.05"), + toNano("1"), + ), + ); + + const deployerJettonWalletAddress = await getJettonWalletRaw( + minterAddress, + blockchain, + deployer.address, + ); + + expect(mintResult.transactions).toHaveTransaction({ + from: minterAddress, + to: deployerJettonWalletAddress, + success: true, + endStatus: "active", + }); + + const someAddress = Address.parse( + "EQD__________________________________________0vo", + ); + + const sendResult = await step("transfer", () => + sendTransferRaw( + deployerJettonWalletAddress, + deployer, + toNano(1), + 1n, + someAddress, + deployer.address, + null, + 0n, + null, + ), + ); + + expect(sendResult.transactions).not.toHaveTransaction({ + success: false, + }); + + expect(sendResult.transactions).toHaveTransaction({ + from: deployerJettonWalletAddress, + success: true, + exitCode: 0, + }); + + return getUsedGas(sendResult, "internal"); + }; + + const transferGasUsedTact = await runMintTest(jettonMinter.address); + + const transferGasUsedFunC = await runMintTest(jettonMinterFuncAddress); + + const transferGasUsedNotcoin = await runMintTest( + jettonMinterNotcoinAddress, + ); + + expect(transferGasUsedTact).toEqual(expectedResult.gas["transfer"]); + + expect(transferGasUsedFunC).toEqual(funcResult.gas["transfer"]); + + expect(transferGasUsedNotcoin).toEqual(notcoinResult.gas["transfer"]); + }); + + it("burn", async () => { + const runBurnTest = async (minterAddress: Address) => { + const deployerJettonWalletAddress = await getJettonWalletRaw( + minterAddress, + blockchain, + deployer.address, + ); + + const burnAmount = toNano("0.01"); + + const burnResult = await step("burn", () => + sendBurnRaw( + deployerJettonWalletAddress, + deployer, + toNano(10), + burnAmount, + deployer.address, + null, + ), + ); + + expect(burnResult.transactions).toHaveTransaction({ + from: deployerJettonWalletAddress, + to: minterAddress, + exitCode: 0, + }); + + return getUsedGas(burnResult, "internal"); + }; + + const burnGasUsedTact = await runBurnTest(jettonMinter.address); + + const burnGasUsedFunC = await runBurnTest(jettonMinterFuncAddress); + + const burnGasUsedNotcoin = await runBurnTest( + jettonMinterNotcoinAddress, + ); + + expect(burnGasUsedTact).toEqual(expectedResult.gas["burn"]); + + expect(burnGasUsedFunC).toEqual(funcResult.gas["burn"]); + + expect(burnGasUsedNotcoin).toEqual(notcoinResult.gas["burn"]); + }); + + it("discovery", async () => { + const runDiscoveryTest = async (minterAddress: Address) => { + const discoveryResult = await step("discovery", () => + sendDiscoveryRaw( + minterAddress, + deployer, + notDeployer.address, + false, + toNano(10), + ), + ); + + expect(discoveryResult.transactions).toHaveTransaction({ + from: deployer.address, + to: minterAddress, + success: true, + }); + + return getUsedGas(discoveryResult, "internal"); + }; + + const discoveryGasUsedTact = await runDiscoveryTest( + jettonMinter.address, + ); + + const discoveryGasUsedFunC = await runDiscoveryTest( + jettonMinterFuncAddress, + ); + + const discoveryGasUsedNotcoin = await runDiscoveryTest( + jettonMinterNotcoinAddress, + ); + + expect(discoveryGasUsedTact).toEqual(expectedResult.gas["discovery"]); + + expect(discoveryGasUsedFunC).toEqual(funcResult.gas["discovery"]); + + expect(discoveryGasUsedNotcoin).toEqual(notcoinResult.gas["discovery"]); + }); + + it("minter cells", async () => { + expect( + (await getStateSizeForAccount(blockchain, jettonMinter.address)) + .cells, + ).toEqual(expectedCodeSize.size["minter cells"]); + expect( + (await getStateSizeForAccount(blockchain, jettonMinterFuncAddress)) + .cells, + ).toEqual(funcCodeSize.size["minter cells"]); + }); + + it("minter bits", async () => { + expect( + (await getStateSizeForAccount(blockchain, jettonMinter.address)) + .bits, + ).toEqual(expectedCodeSize.size["minter bits"]); + expect( + (await getStateSizeForAccount(blockchain, jettonMinterFuncAddress)) + .bits, + ).toEqual(funcCodeSize.size["minter bits"]); + }); + + it("wallet cells", async () => { + const walletAddress = await getJettonWalletRaw( + jettonMinter.address, + blockchain, + deployer.address, + ); + expect( + (await getStateSizeForAccount(blockchain, walletAddress)).cells, + ).toEqual(expectedCodeSize.size["wallet cells"]); + + const walletAddressFunc = await getJettonWalletRaw( + jettonMinterFuncAddress, + blockchain, + deployer.address, + ); + expect( + (await getStateSizeForAccount(blockchain, walletAddressFunc)).cells, + ).toEqual(funcCodeSize.size["wallet cells"]); + }); + + it("wallet bits", async () => { + const walletAddress = await getJettonWalletRaw( + jettonMinter.address, + blockchain, + deployer.address, + ); + expect( + (await getStateSizeForAccount(blockchain, walletAddress)).bits, + ).toEqual(expectedCodeSize.size["wallet bits"]); + + const walletAddressFunc = await getJettonWalletRaw( + jettonMinterFuncAddress, + blockchain, + deployer.address, + ); + expect( + (await getStateSizeForAccount(blockchain, walletAddressFunc)).bits, + ).toEqual(funcCodeSize.size["wallet bits"]); + }); +}); diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json new file mode 100644 index 000000000..8a6de227d --- /dev/null +++ b/src/benchmarks/notcoin/results_code_size.json @@ -0,0 +1,54 @@ +{ + "results": [ + { + "label": "FunC", + "size": { + "minter cells": "34", + "minter bits": "11449", + "wallet cells": "37", + "wallet bits": "12752" + }, + "pr": null + }, + { + "label": "1.5.4 with NOPs optimized", + "pr": "https://github.com/tact-lang/tact/pull/1959", + "size": { + "minter cells": "32", + "minter bits": "14445", + "wallet cells": "15", + "wallet bits": "7942" + } + }, + { + "label": "1.5.3 with reworked JettonWallet", + "pr": "https://github.com/tact-lang/tact/pull/2059", + "size": { + "minter cells": "33", + "minter bits": "14945", + "wallet cells": "16", + "wallet bits": "8348" + } + }, + { + "label": "1.6.0 with contract load data inlined", + "pr": "https://github.com/tact-lang/tact/pull/2088", + "size": { + "minter cells": "29", + "minter bits": "14769", + "wallet cells": "15", + "wallet bits": "8244" + } + }, + { + "label": "1.6.0 with more efficient ProvideWalletAddress response", + "pr": "https://github.com/tact-lang/tact/pull/2229", + "size": { + "minter cells": "29", + "minter bits": "14649", + "wallet cells": "15", + "wallet bits": "8244" + } + } + ] +} diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json new file mode 100644 index 000000000..6bd59e231 --- /dev/null +++ b/src/benchmarks/notcoin/results_gas.json @@ -0,0 +1,301 @@ +{ + "results": [ + { + "label": "FunC", + "gas": { + "transfer": "16864", + "burn": "12414", + "discovery": "6554" + }, + "pr": "https://github.com/ton-blockchain/token-contract" + }, + { + "label": "NotCoin", + "gas": { + "transfer": "16984", + "burn": "12732", + "discovery": "6545" + }, + "pr": "https://github.com/OpenBuilders/notcoin-contract" + }, + { + "label": "1.5.3", + "gas": { + "transfer": "40678", + "burn": "25852", + "discovery": "15265" + }, + "pr": "https://github.com/tact-lang/tact/pull/1390" + }, + { + "label": "1.5.3 without address validation", + "gas": { + "transfer": "31470", + "burn": "26594", + "discovery": "15408" + }, + "pr": null + }, + { + "label": "1.5.3 without self-code in system cell", + "gas": { + "transfer": "26876", + "burn": "17898", + "discovery": "11135" + }, + "pr": null + }, + { + "label": "1.5.3 with removed JettonWallet duplicate", + "gas": { + "transfer": "26852", + "burn": "17728", + "discovery": "11055" + }, + "pr": null + }, + { + "label": "1.5.3 with #1586 and #1589", + "gas": { + "transfer": "26568", + "burn": "17718", + "discovery": "11063" + }, + "pr": null + }, + { + "label": "1.5.3 with faster readForwardFee", + "gas": { + "transfer": "26121", + "burn": "17718", + "discovery": "11063" + }, + "pr": null + }, + { + "label": "1.5.3 without run-time null checks for `!!`", + "gas": { + "transfer": "25860", + "burn": "17686", + "discovery": "11039" + }, + "pr": null + }, + { + "label": "1.5.3 with optimized send()", + "gas": { + "transfer": "21464", + "burn": "16029", + "discovery": "10093" + }, + "pr": null + }, + { + "label": "1.5.3 with improved storeBool", + "gas": { + "transfer": "21402", + "burn": "16029", + "discovery": "10067" + }, + "pr": "https://github.com/tact-lang/tact/pull/1702" + }, + { + "label": "1.5.3 with more optimized send", + "gas": { + "transfer": "20714", + "burn": "15909", + "discovery": "10003" + }, + "pr": "https://github.com/tact-lang/tact/pull/1800" + }, + { + "label": "1.5.3 with address functions in Tact", + "gas": { + "transfer": "19944", + "burn": "15565", + "discovery": "9613" + }, + "pr": "https://github.com/tact-lang/tact/pull/1766" + }, + { + "label": "1.5.3 without double checking of message opcodes", + "gas": { + "transfer": "19598", + "burn": "15311", + "discovery": "9458" + }, + "pr": "https://github.com/tact-lang/tact/pull/1854" + }, + { + "label": "1.5.3 with deploy instead of send", + "gas": { + "transfer": "18517", + "burn": "15311", + "discovery": "9458" + }, + "pr": "https://github.com/tact-lang/tact/pull/1832" + }, + { + "label": "1.5.3 with inlining of __tact_load_address", + "gas": { + "transfer": "18337", + "burn": "15113", + "discovery": "9386" + }, + "pr": "https://github.com/tact-lang/tact/pull/1895" + }, + { + "label": "1.5.3 with optimized routing", + "gas": { + "transfer": "18147", + "burn": "14941", + "discovery": "9319" + }, + "pr": "https://github.com/tact-lang/tact/pull/1841" + }, + { + "label": "1.5.3 with Jetton Minter optimizations and fixes", + "gas": { + "transfer": "17990", + "burn": "14532", + "discovery": "9064" + }, + "pr": "https://github.com/tact-lang/tact/pull/1931" + }, + { + "label": "1.5.3 with optimized flag handling", + "gas": { + "transfer": "17950", + "burn": "14492", + "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" + }, + { + "label": "1.5.3 with optimized initOf", + "gas": { + "transfer": "17696", + "burn": "13544", + "discovery": "8288" + }, + "pr": "https://github.com/tact-lang/tact/pull/1907" + }, + { + "label": "1.5.3 with message() instead of send()", + "pr": "https://github.com/tact-lang/tact/pull/1999", + "gas": { + "transfer": "17315", + "burn": "12892", + "discovery": "8025" + } + }, + { + "label": "1.5.3 with more inlining for small contracts and structs", + "pr": "https://github.com/tact-lang/tact/pull/2016", + "gas": { + "transfer": "16472", + "burn": "12463", + "discovery": "7868" + } + }, + { + "label": "1.5.3 with optimized NOP instructions", + "pr": "https://github.com/tact-lang/tact/pull/1959", + "gas": { + "transfer": "16436", + "burn": "12427", + "discovery": "7750" + } + }, + { + "label": "1.5.3 with no lazy init", + "pr": "https://github.com/tact-lang/tact/pull/1985", + "gas": { + "transfer": "16092", + "burn": "12123", + "discovery": "7683" + } + }, + { + "label": "1.5.3 with BasechainAddress", + "pr": "https://github.com/tact-lang/tact/pull/2035", + "gas": { + "transfer": "16092", + "burn": "12123", + "discovery": "7052" + } + }, + { + "label": "1.5.3 with fixed routing", + "pr": "https://github.com/tact-lang/tact/pull/1949", + "gas": { + "transfer": "16036", + "burn": "12067", + "discovery": "7024" + } + }, + { + "label": "1.5.3 with custom selector", + "pr": "https://github.com/tact-lang/tact/pull/2038", + "gas": { + "transfer": "15896", + "burn": "11927", + "discovery": "6954" + } + }, + { + "label": "1.5.3 with optimized contract storage saving", + "pr": null, + "gas": { + "transfer": "15896", + "burn": "11927", + "discovery": "6250" + } + }, + { + "label": "1.5.3 with reworked JettonWallet", + "pr": "https://github.com/tact-lang/tact/pull/2059", + "gas": { + "transfer": "15778", + "burn": "12541", + "discovery": "6230" + } + }, + { + "label": "1.5.3 with more optimizations for JettonMinter + global inlining + basechain address", + "pr": "https://github.com/tact-lang/tact/pull/2088", + "gas": { + "transfer": "15778", + "burn": "11773", + "discovery": "6207" + } + }, + { + "label": "1.6.0 with more efficient address validation", + "pr": "https://github.com/tact-lang/tact/pull/2187", + "gas": { + "transfer": "15020", + "burn": "11773", + "discovery": "6207" + } + }, + { + "label": "1.6.0 with more efficient ProvideWalletAddress response", + "pr": "https://github.com/tact-lang/tact/pull/2229", + "gas": { + "transfer": "15020", + "burn": "11773", + "discovery": "6076" + } + } + ] +} From 76dc62e78012c82c6356e7edf3e9da05613a5fdf Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Thu, 6 Mar 2025 18:42:49 +0300 Subject: [PATCH 03/16] changed benches and minter gas logic --- .../contracts/jetton-minter-notcoin.tact | 22 +- src/benchmarks/notcoin/notcoin.spec.ts | 118 ++----- src/benchmarks/notcoin/results_code_size.json | 44 +-- src/benchmarks/notcoin/results_gas.json | 287 +----------------- 4 files changed, 58 insertions(+), 413 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index be0d3b73d..480982465 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -14,6 +14,9 @@ struct JettonMasterState { jettonWalletCode: Cell; } +const SEND_TRANSFER_GAS_CONSUMPTION: Int = 10065; +const RECEIVE_TRANSFER_GAS_CONSUMPTION: Int = 10435; + contract JettonMinterNotcoin( totalSupply: Int as coins, owner: Address, @@ -66,13 +69,15 @@ contract JettonMinterNotcoin( } receive(msg: Mint) { - throwUnless(73, sender() == self.owner); + let ctx = context(); + throwUnless(73, ctx.sender == self.owner); self.totalSupply += msg.mintMessage.amount; - let fwdFee = getOriginalFwdFee(context().readForwardFee(), false); + let fwdFee = getOriginalFwdFee(ctx.readForwardFee(), false); // force workchain // check amount + checkAmountIsEnoughToTransfer(ctx.value, msg.mintMessage.forwardTonAmount, fwdFee); deploy(DeployParameters{ value: 0, @@ -143,3 +148,16 @@ inline fun getJettonBasechainWalletByOwner(jettonWalletOwner: Address): Basechai } inline extends fun isNotNone(self: Address): Bool { return self.asSlice().preloadUint(2) != 0 } + +inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, fwdFee: Int) { + let fwdCount = 1 + forwardTonAmount & 1; + + throwUnless( + 705, + msgValue > fwdCount * fwdFee + + getSimpleForwardFee(29, 15000, false) + + getComputeFee(SEND_TRANSFER_GAS_CONSUMPTION, false) + + getComputeFee(RECEIVE_TRANSFER_GAS_CONSUMPTION, false) + + getStorageFee(15, 8000, 5 * 365 * 24 * 3600, false) + ); +} diff --git a/src/benchmarks/notcoin/notcoin.spec.ts b/src/benchmarks/notcoin/notcoin.spec.ts index 4533348a6..583dd5dee 100644 --- a/src/benchmarks/notcoin/notcoin.spec.ts +++ b/src/benchmarks/notcoin/notcoin.spec.ts @@ -14,11 +14,11 @@ import { Blockchain } from "@ton/sandbox"; import { type Mint, type ProvideWalletAddress, - JettonMinter, + JettonMinterNotcoin, storeJettonBurn, storeJettonTransfer, storeMint, -} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; +} from "../contracts/output/jetton-minter-notcoin_JettonMinterNotcoin"; import "@ton/test-utils"; import benchmarkCodeSizeResults from "./results_code_size.json"; @@ -26,7 +26,7 @@ import type { JettonBurn, JettonTransfer, JettonUpdateContent, -} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; +} from "../contracts/output/jetton-minter-notcoin_JettonMinterNotcoin"; import { generateCodeSizeResults, @@ -42,25 +42,6 @@ import { storeProvideWalletAddress } from "../contracts/output/escrow_Escrow"; import { posixNormalize } from "../../utils/filePath"; import { type Step, writeLog } from "../../test/utils/write-vm-log"; -const loadFunCJettonsBoc = () => { - const bocMinter = readFileSync( - posixNormalize( - resolve( - __dirname, - "../contracts/func/output/jetton-minter-discoverable.boc", - ), - ), - ); - - const bocWallet = readFileSync( - posixNormalize( - resolve(__dirname, "../contracts/func/output/jetton-wallet.boc"), - ), - ); - - return { bocMinter, bocWallet }; -}; - const loadNotcoinJettonsBoc = () => { const bocMinter = readFileSync( posixNormalize( @@ -83,36 +64,6 @@ const loadNotcoinJettonsBoc = () => { return { bocMinter, bocWallet }; }; -const deployFuncJettonMinter = async ( - via: SandboxContract, -) => { - const jettonData = loadFunCJettonsBoc(); - const minterCell = Cell.fromBoc(jettonData.bocMinter)[0]!; - const walletCell = Cell.fromBoc(jettonData.bocWallet)[0]!; - - const stateInitMinter = beginCell() - .storeCoins(0) - .storeAddress(via.address) - .storeRef(beginCell().storeUint(1, 1).endCell()) // as salt - .storeRef(walletCell) - .endCell(); - - const init = { code: minterCell, data: stateInitMinter }; - - const minterAddress = contractAddress(0, init); - - return { - minterAddress, - result: await via.send({ - to: minterAddress, - value: toNano("0.1"), - init, - body: beginCell().endCell(), - sendMode: SendMode.PAY_GAS_SEPARATELY, - }), - }; -}; - const deployNotcoinJettonMinter = async ( via: SandboxContract, ) => { @@ -291,10 +242,9 @@ const getJettonWalletRaw = async ( return walletAddressResult.stack.readAddress(); }; -describe("Jetton", () => { +describe("NotCoin", () => { let blockchain: Blockchain; - let jettonMinter: SandboxContract; - let jettonMinterFuncAddress: Address; + let jettonMinter: SandboxContract; let jettonMinterNotcoinAddress: Address; let deployer: SandboxContract; let step: Step; @@ -305,12 +255,12 @@ describe("Jetton", () => { const results = generateResults(benchmarkResults); const codeSizeResults = generateCodeSizeResults(benchmarkCodeSizeResults); + const expectedCodeSize = codeSizeResults.at(-1)!; const funcCodeSize = codeSizeResults.at(0)!; const expectedResult = results.at(-1)!; - const funcResult = results.at(0)!; - const notcoinResult = results.at(1)!; + const notcoinResult = results.at(0)!; beforeAll(async () => { blockchain = await Blockchain.create(); @@ -322,19 +272,6 @@ describe("Jetton", () => { deployer = await blockchain.treasury("deployer"); notDeployer = await blockchain.treasury("notDeployer"); - // deploy func - const { result: deployFuncJettonMinterResult, minterAddress } = - await deployFuncJettonMinter(deployer); - - expect(deployFuncJettonMinterResult.transactions).toHaveTransaction({ - from: deployer.address, - to: minterAddress, - success: true, - deploy: true, - }); - - jettonMinterFuncAddress = minterAddress; - // deploy notcoin const { result: deployNotcoinJettonMinterResult, @@ -358,7 +295,12 @@ describe("Jetton", () => { }; jettonMinter = blockchain.openContract( - await JettonMinter.fromInit(0n, deployer.address, defaultContent), + await JettonMinterNotcoin.fromInit( + 0n, + deployer.address, + deployer.address, + defaultContent, + ), ); const deployResult = await jettonMinter.send( deployer.getSender(), @@ -445,16 +387,12 @@ describe("Jetton", () => { const transferGasUsedTact = await runMintTest(jettonMinter.address); - const transferGasUsedFunC = await runMintTest(jettonMinterFuncAddress); - const transferGasUsedNotcoin = await runMintTest( jettonMinterNotcoinAddress, ); expect(transferGasUsedTact).toEqual(expectedResult.gas["transfer"]); - expect(transferGasUsedFunC).toEqual(funcResult.gas["transfer"]); - expect(transferGasUsedNotcoin).toEqual(notcoinResult.gas["transfer"]); }); @@ -490,16 +428,12 @@ describe("Jetton", () => { const burnGasUsedTact = await runBurnTest(jettonMinter.address); - const burnGasUsedFunC = await runBurnTest(jettonMinterFuncAddress); - const burnGasUsedNotcoin = await runBurnTest( jettonMinterNotcoinAddress, ); expect(burnGasUsedTact).toEqual(expectedResult.gas["burn"]); - expect(burnGasUsedFunC).toEqual(funcResult.gas["burn"]); - expect(burnGasUsedNotcoin).toEqual(notcoinResult.gas["burn"]); }); @@ -528,18 +462,12 @@ describe("Jetton", () => { jettonMinter.address, ); - const discoveryGasUsedFunC = await runDiscoveryTest( - jettonMinterFuncAddress, - ); - const discoveryGasUsedNotcoin = await runDiscoveryTest( jettonMinterNotcoinAddress, ); expect(discoveryGasUsedTact).toEqual(expectedResult.gas["discovery"]); - expect(discoveryGasUsedFunC).toEqual(funcResult.gas["discovery"]); - expect(discoveryGasUsedNotcoin).toEqual(notcoinResult.gas["discovery"]); }); @@ -549,8 +477,12 @@ describe("Jetton", () => { .cells, ).toEqual(expectedCodeSize.size["minter cells"]); expect( - (await getStateSizeForAccount(blockchain, jettonMinterFuncAddress)) - .cells, + ( + await getStateSizeForAccount( + blockchain, + jettonMinterNotcoinAddress, + ) + ).cells, ).toEqual(funcCodeSize.size["minter cells"]); }); @@ -560,8 +492,12 @@ describe("Jetton", () => { .bits, ).toEqual(expectedCodeSize.size["minter bits"]); expect( - (await getStateSizeForAccount(blockchain, jettonMinterFuncAddress)) - .bits, + ( + await getStateSizeForAccount( + blockchain, + jettonMinterNotcoinAddress, + ) + ).bits, ).toEqual(funcCodeSize.size["minter bits"]); }); @@ -576,7 +512,7 @@ describe("Jetton", () => { ).toEqual(expectedCodeSize.size["wallet cells"]); const walletAddressFunc = await getJettonWalletRaw( - jettonMinterFuncAddress, + jettonMinterNotcoinAddress, blockchain, deployer.address, ); @@ -596,7 +532,7 @@ describe("Jetton", () => { ).toEqual(expectedCodeSize.size["wallet bits"]); const walletAddressFunc = await getJettonWalletRaw( - jettonMinterFuncAddress, + jettonMinterNotcoinAddress, blockchain, deployer.address, ); diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index 8a6de227d..40cc8badb 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -3,49 +3,19 @@ { "label": "FunC", "size": { - "minter cells": "34", - "minter bits": "11449", - "wallet cells": "37", - "wallet bits": "12752" + "minter cells": "38", + "minter bits": "15898", + "wallet cells": "16", + "wallet bits": "7384" }, "pr": null }, { - "label": "1.5.4 with NOPs optimized", - "pr": "https://github.com/tact-lang/tact/pull/1959", - "size": { - "minter cells": "32", - "minter bits": "14445", - "wallet cells": "15", - "wallet bits": "7942" - } - }, - { - "label": "1.5.3 with reworked JettonWallet", - "pr": "https://github.com/tact-lang/tact/pull/2059", - "size": { - "minter cells": "33", - "minter bits": "14945", - "wallet cells": "16", - "wallet bits": "8348" - } - }, - { - "label": "1.6.0 with contract load data inlined", - "pr": "https://github.com/tact-lang/tact/pull/2088", - "size": { - "minter cells": "29", - "minter bits": "14769", - "wallet cells": "15", - "wallet bits": "8244" - } - }, - { - "label": "1.6.0 with more efficient ProvideWalletAddress response", + "label": "1.6.2", "pr": "https://github.com/tact-lang/tact/pull/2229", "size": { - "minter cells": "29", - "minter bits": "14649", + "minter cells": "30", + "minter bits": "16572", "wallet cells": "15", "wallet bits": "8244" } diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index 6bd59e231..133186053 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -2,299 +2,20 @@ "results": [ { "label": "FunC", - "gas": { - "transfer": "16864", - "burn": "12414", - "discovery": "6554" - }, - "pr": "https://github.com/ton-blockchain/token-contract" - }, - { - "label": "NotCoin", "gas": { "transfer": "16984", "burn": "12732", "discovery": "6545" }, - "pr": "https://github.com/OpenBuilders/notcoin-contract" - }, - { - "label": "1.5.3", - "gas": { - "transfer": "40678", - "burn": "25852", - "discovery": "15265" - }, - "pr": "https://github.com/tact-lang/tact/pull/1390" - }, - { - "label": "1.5.3 without address validation", - "gas": { - "transfer": "31470", - "burn": "26594", - "discovery": "15408" - }, - "pr": null - }, - { - "label": "1.5.3 without self-code in system cell", - "gas": { - "transfer": "26876", - "burn": "17898", - "discovery": "11135" - }, - "pr": null - }, - { - "label": "1.5.3 with removed JettonWallet duplicate", - "gas": { - "transfer": "26852", - "burn": "17728", - "discovery": "11055" - }, - "pr": null - }, - { - "label": "1.5.3 with #1586 and #1589", - "gas": { - "transfer": "26568", - "burn": "17718", - "discovery": "11063" - }, - "pr": null - }, - { - "label": "1.5.3 with faster readForwardFee", - "gas": { - "transfer": "26121", - "burn": "17718", - "discovery": "11063" - }, - "pr": null - }, - { - "label": "1.5.3 without run-time null checks for `!!`", - "gas": { - "transfer": "25860", - "burn": "17686", - "discovery": "11039" - }, - "pr": null - }, - { - "label": "1.5.3 with optimized send()", - "gas": { - "transfer": "21464", - "burn": "16029", - "discovery": "10093" - }, - "pr": null - }, - { - "label": "1.5.3 with improved storeBool", - "gas": { - "transfer": "21402", - "burn": "16029", - "discovery": "10067" - }, - "pr": "https://github.com/tact-lang/tact/pull/1702" - }, - { - "label": "1.5.3 with more optimized send", - "gas": { - "transfer": "20714", - "burn": "15909", - "discovery": "10003" - }, - "pr": "https://github.com/tact-lang/tact/pull/1800" - }, - { - "label": "1.5.3 with address functions in Tact", - "gas": { - "transfer": "19944", - "burn": "15565", - "discovery": "9613" - }, - "pr": "https://github.com/tact-lang/tact/pull/1766" - }, - { - "label": "1.5.3 without double checking of message opcodes", - "gas": { - "transfer": "19598", - "burn": "15311", - "discovery": "9458" - }, - "pr": "https://github.com/tact-lang/tact/pull/1854" - }, - { - "label": "1.5.3 with deploy instead of send", - "gas": { - "transfer": "18517", - "burn": "15311", - "discovery": "9458" - }, - "pr": "https://github.com/tact-lang/tact/pull/1832" - }, - { - "label": "1.5.3 with inlining of __tact_load_address", - "gas": { - "transfer": "18337", - "burn": "15113", - "discovery": "9386" - }, - "pr": "https://github.com/tact-lang/tact/pull/1895" - }, - { - "label": "1.5.3 with optimized routing", - "gas": { - "transfer": "18147", - "burn": "14941", - "discovery": "9319" - }, - "pr": "https://github.com/tact-lang/tact/pull/1841" - }, - { - "label": "1.5.3 with Jetton Minter optimizations and fixes", - "gas": { - "transfer": "17990", - "burn": "14532", - "discovery": "9064" - }, - "pr": "https://github.com/tact-lang/tact/pull/1931" - }, - { - "label": "1.5.3 with optimized flag handling", - "gas": { - "transfer": "17950", - "burn": "14492", - "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" - }, - { - "label": "1.5.3 with optimized initOf", - "gas": { - "transfer": "17696", - "burn": "13544", - "discovery": "8288" - }, - "pr": "https://github.com/tact-lang/tact/pull/1907" - }, - { - "label": "1.5.3 with message() instead of send()", - "pr": "https://github.com/tact-lang/tact/pull/1999", - "gas": { - "transfer": "17315", - "burn": "12892", - "discovery": "8025" - } - }, - { - "label": "1.5.3 with more inlining for small contracts and structs", - "pr": "https://github.com/tact-lang/tact/pull/2016", - "gas": { - "transfer": "16472", - "burn": "12463", - "discovery": "7868" - } - }, - { - "label": "1.5.3 with optimized NOP instructions", - "pr": "https://github.com/tact-lang/tact/pull/1959", - "gas": { - "transfer": "16436", - "burn": "12427", - "discovery": "7750" - } - }, - { - "label": "1.5.3 with no lazy init", - "pr": "https://github.com/tact-lang/tact/pull/1985", - "gas": { - "transfer": "16092", - "burn": "12123", - "discovery": "7683" - } - }, - { - "label": "1.5.3 with BasechainAddress", - "pr": "https://github.com/tact-lang/tact/pull/2035", - "gas": { - "transfer": "16092", - "burn": "12123", - "discovery": "7052" - } - }, - { - "label": "1.5.3 with fixed routing", - "pr": "https://github.com/tact-lang/tact/pull/1949", - "gas": { - "transfer": "16036", - "burn": "12067", - "discovery": "7024" - } - }, - { - "label": "1.5.3 with custom selector", - "pr": "https://github.com/tact-lang/tact/pull/2038", - "gas": { - "transfer": "15896", - "burn": "11927", - "discovery": "6954" - } - }, - { - "label": "1.5.3 with optimized contract storage saving", - "pr": null, - "gas": { - "transfer": "15896", - "burn": "11927", - "discovery": "6250" - } - }, - { - "label": "1.5.3 with reworked JettonWallet", - "pr": "https://github.com/tact-lang/tact/pull/2059", - "gas": { - "transfer": "15778", - "burn": "12541", - "discovery": "6230" - } - }, - { - "label": "1.5.3 with more optimizations for JettonMinter + global inlining + basechain address", - "pr": "https://github.com/tact-lang/tact/pull/2088", - "gas": { - "transfer": "15778", - "burn": "11773", - "discovery": "6207" - } - }, - { - "label": "1.6.0 with more efficient address validation", - "pr": "https://github.com/tact-lang/tact/pull/2187", - "gas": { - "transfer": "15020", - "burn": "11773", - "discovery": "6207" - } + "pr": "https://github.com/ton-blockchain/token-contract" }, { - "label": "1.6.0 with more efficient ProvideWalletAddress response", + "label": "1.6.2", "pr": "https://github.com/tact-lang/tact/pull/2229", "gas": { "transfer": "15020", - "burn": "11773", - "discovery": "6076" + "burn": "11859", + "discovery": "6102" } } ] From cd41c03a44eb0d16694d1619ce1f0567b3828d16 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 11:41:32 +0300 Subject: [PATCH 04/16] add wallet gas checks --- .../contracts/jetton-minter-notcoin.tact | 18 +------------ .../contracts/jetton-wallet-notcoin.tact | 27 ++++++++++++++----- src/benchmarks/notcoin/results_code_size.json | 4 +-- src/benchmarks/notcoin/results_gas.json | 2 +- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index 480982465..aa0662ca2 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -14,9 +14,6 @@ struct JettonMasterState { jettonWalletCode: Cell; } -const SEND_TRANSFER_GAS_CONSUMPTION: Int = 10065; -const RECEIVE_TRANSFER_GAS_CONSUMPTION: Int = 10435; - contract JettonMinterNotcoin( totalSupply: Int as coins, owner: Address, @@ -147,17 +144,4 @@ inline fun getJettonBasechainWalletByOwner(jettonWalletOwner: Address): Basechai return contractBasechainAddress(getJettonWalletInit(jettonWalletOwner)); } -inline extends fun isNotNone(self: Address): Bool { return self.asSlice().preloadUint(2) != 0 } - -inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, fwdFee: Int) { - let fwdCount = 1 + forwardTonAmount & 1; - - throwUnless( - 705, - msgValue > fwdCount * fwdFee + - getSimpleForwardFee(29, 15000, false) + - getComputeFee(SEND_TRANSFER_GAS_CONSUMPTION, false) + - getComputeFee(RECEIVE_TRANSFER_GAS_CONSUMPTION, false) + - getStorageFee(15, 8000, 5 * 365 * 24 * 3600, false) - ); -} +inline extends fun isNotNone(self: Address): Bool { return self.asSlice().preloadUint(2) != 0 } \ No newline at end of file diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index 7efac0b65..6ebccf1fb 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -1,5 +1,21 @@ import "./messages"; +const SEND_TRANSFER_GAS_CONSUMPTION: Int = 10065; +const RECEIVE_TRANSFER_GAS_CONSUMPTION: Int = 10435; + +inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, fwdFee: Int) { + let fwdCount = 1 + forwardTonAmount & 1; + + throwUnless( + 705, + msgValue > fwdCount * fwdFee + + getSimpleForwardFee(29, 15000, false) + + getComputeFee(SEND_TRANSFER_GAS_CONSUMPTION, false) + + getComputeFee(RECEIVE_TRANSFER_GAS_CONSUMPTION, false) + + getStorageFee(15, 8000, 5 * 365 * 24 * 3600, false) + ); +} + contract JettonWalletNotcoin( balance: Int as coins, owner: Address, @@ -17,12 +33,9 @@ contract JettonWalletNotcoin( throwUnless(708, msg.forwardPayload.bits() >= 1); let ctx = context(); - let fwdCount = 1 + msg.forwardTonAmount & 1; - throwUnless(709, ctx.value > - msg.forwardTonAmount + - fwdCount * ctx.readForwardFee() + - (2 * self.gasConsumption + self.minTonsForStorage) - ); + + let fwdFee = getOriginalFwdFee(ctx.readForwardFee(), false); + checkAmountIsEnoughToTransfer(ctx.value, msg.forwardTonAmount, fwdFee); deploy(DeployParameters{ value: 0, @@ -125,3 +138,5 @@ contract JettonWalletNotcoin( }; } } + + diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index 40cc8badb..556e7b93e 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -15,9 +15,9 @@ "pr": "https://github.com/tact-lang/tact/pull/2229", "size": { "minter cells": "30", - "minter bits": "16572", + "minter bits": "16852", "wallet cells": "15", - "wallet bits": "8244" + "wallet bits": "8524" } } ] diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index 133186053..1ea66f476 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -13,7 +13,7 @@ "label": "1.6.2", "pr": "https://github.com/tact-lang/tact/pull/2229", "gas": { - "transfer": "15020", + "transfer": "15482", "burn": "11859", "discovery": "6102" } From faa6ca81377803d8af9f28bd1edc0fe481a04849 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 12:34:41 +0300 Subject: [PATCH 05/16] add constants and burn compute fees --- .../contracts/jetton-minter-notcoin.tact | 1 - .../contracts/jetton-wallet-notcoin.tact | 56 ++++++++++++------- src/benchmarks/notcoin/notcoin.spec.ts | 5 -- src/benchmarks/notcoin/results_code_size.json | 4 +- src/benchmarks/notcoin/results_gas.json | 4 +- 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index aa0662ca2..8f885e84c 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -73,7 +73,6 @@ contract JettonMinterNotcoin( let fwdFee = getOriginalFwdFee(ctx.readForwardFee(), false); // force workchain - // check amount checkAmountIsEnoughToTransfer(ctx.value, msg.mintMessage.forwardTonAmount, fwdFee); deploy(DeployParameters{ diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index 6ebccf1fb..d652ddee3 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -1,7 +1,22 @@ import "./messages"; +// gas const SEND_TRANSFER_GAS_CONSUMPTION: Int = 10065; const RECEIVE_TRANSFER_GAS_CONSUMPTION: Int = 10435; +const SEND_BURN_GAS_CONSUMPTION: Int = 5891; +const RECEIVE_BURN_GAS_CONSUMPTION: Int = 6757; + +// storage +const MIN_STORAGE_DURATION: Int = 5 * 365 * 24 * 3600; // 5 years + +const JETTON_WALLET_BITS: Int = 1033; +const JETTON_WALLET_CELLS: Int = 3; + +const JETTON_WALLET_INITSTATE_BITS: Int = 931; +const JETTON_WALLET_INITSTATE_CELLS: Int = 3; + +const BURN_NOTIFICATION_BITS: Int = 754; +const BURN_NOTIFICATION_CELLS: Int = 1; inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, fwdFee: Int) { let fwdCount = 1 + forwardTonAmount & 1; @@ -9,10 +24,20 @@ inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, f throwUnless( 705, msgValue > fwdCount * fwdFee + - getSimpleForwardFee(29, 15000, false) + + getSimpleForwardFee(JETTON_WALLET_INITSTATE_CELLS, JETTON_WALLET_INITSTATE_BITS, false) + getComputeFee(SEND_TRANSFER_GAS_CONSUMPTION, false) + getComputeFee(RECEIVE_TRANSFER_GAS_CONSUMPTION, false) + - getStorageFee(15, 8000, 5 * 365 * 24 * 3600, false) + getStorageFee(JETTON_WALLET_CELLS, JETTON_WALLET_BITS, MIN_STORAGE_DURATION, false) + ); +} + +inline fun checkAmountIsEnoughToBurn(msgValue: Int) { + throwUnless( + 705, + msgValue > + getForwardFee(BURN_NOTIFICATION_CELLS, BURN_NOTIFICATION_BITS, false) + + getComputeFee(SEND_BURN_GAS_CONSUMPTION, false) + + getComputeFee(RECEIVE_BURN_GAS_CONSUMPTION, false) ); } @@ -21,9 +46,6 @@ contract JettonWalletNotcoin( owner: Address, master: Address, ) { - const minTonsForStorage: Int = ton("0.01"); - const gasConsumption: Int = ton("0.015"); - receive(msg: JettonTransfer) { throwUnless(333, parseStdAddress(msg.destination.asSlice()).workchain == 0); throwUnless(705, sender() == self.owner); @@ -62,15 +84,7 @@ contract JettonWalletNotcoin( throwUnless(707, self.master == sender()); } - let ctx: Context = context(); - let msgValue: Int = ctx.value; - let tonBalanceBeforeMsg = myBalance() - msgValue; - let storageFee = self.minTonsForStorage - min(tonBalanceBeforeMsg, self.minTonsForStorage); - msgValue -= (storageFee + self.gasConsumption); - if (msg.forwardTonAmount > 0) { - let fwdFee: Int = ctx.readForwardFee(); - msgValue -= msg.forwardTonAmount + fwdFee; message(MessageParameters{ to: self.owner, value: msg.forwardTonAmount, @@ -86,11 +100,17 @@ contract JettonWalletNotcoin( } // 0xd53276db -- Cashback to the original Sender - if (msg.responseDestination != null && msgValue > 0) { + if (msg.responseDestination != null) { + let leaveOnBalance: Int = myBalance() - context().value + myStorageDue(); + nativeReserve( + max(leaveOnBalance, getStorageFee(JETTON_WALLET_CELLS, JETTON_WALLET_BITS, MIN_STORAGE_DURATION, false)), + 2 + ); + message(MessageParameters{ to: msg.responseDestination!!, - value: msgValue, - mode: SendIgnoreErrors, + value: 0, + mode: SendRemainingBalance | SendIgnoreErrors, bounce: false, body: JettonExcesses{ queryId: msg.queryId }.toCell(), }); @@ -103,9 +123,7 @@ contract JettonWalletNotcoin( self.balance -= msg.amount; throwUnless(706, self.balance >= 0); - let ctx = context(); - let fwdFee: Int = ctx.readForwardFee(); - throwUnless(707, ctx.value > (fwdFee + 2 * self.gasConsumption)); + checkAmountIsEnoughToBurn(context().value); message(MessageParameters{ to: self.master, diff --git a/src/benchmarks/notcoin/notcoin.spec.ts b/src/benchmarks/notcoin/notcoin.spec.ts index 583dd5dee..b13e3bda1 100644 --- a/src/benchmarks/notcoin/notcoin.spec.ts +++ b/src/benchmarks/notcoin/notcoin.spec.ts @@ -321,11 +321,6 @@ describe("NotCoin", () => { implementationName: "FunC", printMode: "full", }); - - printBenchmarkTable(results.slice(1), undefined, { - implementationName: "NotCoin", - printMode: "first-last", - }); }); it("transfer", async () => { diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index 556e7b93e..2482430ee 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -15,9 +15,9 @@ "pr": "https://github.com/tact-lang/tact/pull/2229", "size": { "minter cells": "30", - "minter bits": "16852", + "minter bits": "16580", "wallet cells": "15", - "wallet bits": "8524" + "wallet bits": "8268" } } ] diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index 1ea66f476..7414e5d2d 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -13,8 +13,8 @@ "label": "1.6.2", "pr": "https://github.com/tact-lang/tact/pull/2229", "gas": { - "transfer": "15482", - "burn": "11859", + "transfer": "15822", + "burn": "11862", "discovery": "6102" } } From 13ce7ae9d9a016a4cd879a177e394626a6d9563d Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 12:39:29 +0300 Subject: [PATCH 06/16] remove not from default jetton --- src/benchmarks/jetton/jetton.spec.ts | 91 -------------------------- src/benchmarks/jetton/results_gas.json | 9 --- 2 files changed, 100 deletions(-) diff --git a/src/benchmarks/jetton/jetton.spec.ts b/src/benchmarks/jetton/jetton.spec.ts index 4533348a6..4d9571630 100644 --- a/src/benchmarks/jetton/jetton.spec.ts +++ b/src/benchmarks/jetton/jetton.spec.ts @@ -61,28 +61,6 @@ const loadFunCJettonsBoc = () => { return { bocMinter, bocWallet }; }; -const loadNotcoinJettonsBoc = () => { - const bocMinter = readFileSync( - posixNormalize( - resolve( - __dirname, - "../contracts/func/output/jetton-minter-not.boc", - ), - ), - ); - - const bocWallet = readFileSync( - posixNormalize( - resolve( - __dirname, - "../contracts/func/output/jetton-wallet-not.boc", - ), - ), - ); - - return { bocMinter, bocWallet }; -}; - const deployFuncJettonMinter = async ( via: SandboxContract, ) => { @@ -113,40 +91,6 @@ const deployFuncJettonMinter = async ( }; }; -const deployNotcoinJettonMinter = async ( - via: SandboxContract, -) => { - const jettonData = loadNotcoinJettonsBoc(); - const minterCell = Cell.fromBoc(jettonData.bocMinter)[0]!; - const walletCell = Cell.fromBoc(jettonData.bocWallet)[0]!; - - const stateInitMinter = beginCell() - .storeCoins(0) - .storeAddress(via.address) - .storeAddress(null) - .storeRef(walletCell) - .storeRef(beginCell().storeUint(1, 1).endCell()) // as salt - .endCell(); - - const init = { code: minterCell, data: stateInitMinter }; - - const minterAddress = contractAddress(0, init); - - return { - minterNotcoinAddress: minterAddress, - result: await via.send({ - to: minterAddress, - value: toNano("0.1"), - init, - body: beginCell() - .storeUint(0xd372158c, 32) - .storeUint(0, 64) - .endCell(), - sendMode: SendMode.PAY_GAS_SEPARATELY, - }), - }; -}; - const sendDiscoveryRaw = async ( minterAddress: Address, via: SandboxContract, @@ -295,7 +239,6 @@ describe("Jetton", () => { let blockchain: Blockchain; let jettonMinter: SandboxContract; let jettonMinterFuncAddress: Address; - let jettonMinterNotcoinAddress: Address; let deployer: SandboxContract; let step: Step; @@ -310,7 +253,6 @@ describe("Jetton", () => { const expectedResult = results.at(-1)!; const funcResult = results.at(0)!; - const notcoinResult = results.at(1)!; beforeAll(async () => { blockchain = await Blockchain.create(); @@ -335,21 +277,6 @@ describe("Jetton", () => { jettonMinterFuncAddress = minterAddress; - // deploy notcoin - const { - result: deployNotcoinJettonMinterResult, - minterNotcoinAddress, - } = await deployNotcoinJettonMinter(deployer); - - expect(deployNotcoinJettonMinterResult.transactions).toHaveTransaction({ - from: deployer.address, - to: minterNotcoinAddress, - success: true, - deploy: true, - }); - - jettonMinterNotcoinAddress = minterNotcoinAddress; - defaultContent = beginCell().endCell(); const msg: JettonUpdateContent = { $$type: "JettonUpdateContent", @@ -447,15 +374,9 @@ describe("Jetton", () => { const transferGasUsedFunC = await runMintTest(jettonMinterFuncAddress); - const transferGasUsedNotcoin = await runMintTest( - jettonMinterNotcoinAddress, - ); - expect(transferGasUsedTact).toEqual(expectedResult.gas["transfer"]); expect(transferGasUsedFunC).toEqual(funcResult.gas["transfer"]); - - expect(transferGasUsedNotcoin).toEqual(notcoinResult.gas["transfer"]); }); it("burn", async () => { @@ -492,15 +413,9 @@ describe("Jetton", () => { const burnGasUsedFunC = await runBurnTest(jettonMinterFuncAddress); - const burnGasUsedNotcoin = await runBurnTest( - jettonMinterNotcoinAddress, - ); - expect(burnGasUsedTact).toEqual(expectedResult.gas["burn"]); expect(burnGasUsedFunC).toEqual(funcResult.gas["burn"]); - - expect(burnGasUsedNotcoin).toEqual(notcoinResult.gas["burn"]); }); it("discovery", async () => { @@ -532,15 +447,9 @@ describe("Jetton", () => { jettonMinterFuncAddress, ); - const discoveryGasUsedNotcoin = await runDiscoveryTest( - jettonMinterNotcoinAddress, - ); - expect(discoveryGasUsedTact).toEqual(expectedResult.gas["discovery"]); expect(discoveryGasUsedFunC).toEqual(funcResult.gas["discovery"]); - - expect(discoveryGasUsedNotcoin).toEqual(notcoinResult.gas["discovery"]); }); it("minter cells", async () => { diff --git a/src/benchmarks/jetton/results_gas.json b/src/benchmarks/jetton/results_gas.json index 6bd59e231..e502bcbce 100644 --- a/src/benchmarks/jetton/results_gas.json +++ b/src/benchmarks/jetton/results_gas.json @@ -9,15 +9,6 @@ }, "pr": "https://github.com/ton-blockchain/token-contract" }, - { - "label": "NotCoin", - "gas": { - "transfer": "16984", - "burn": "12732", - "discovery": "6545" - }, - "pr": "https://github.com/OpenBuilders/notcoin-contract" - }, { "label": "1.5.3", "gas": { From 096426cd7f25dc2ff3eaeae0bd83a0662187240e Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 12:49:52 +0300 Subject: [PATCH 07/16] func version compat --- .../contracts/jetton-minter-notcoin.tact | 17 +++++------------ src/benchmarks/notcoin/results_code_size.json | 4 ++-- src/benchmarks/notcoin/results_gas.json | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index 8f885e84c..2528b5416 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -1,5 +1,3 @@ -// https://github.com/ton-blockchain/TEPs/blob/master/text/0089-jetton-wallet-discovery.md - import "./messages"; import "./jetton-wallet-notcoin"; @@ -40,11 +38,6 @@ contract JettonMinterNotcoin( } receive(msg: ProvideWalletAddress) { - // we use message fwdFee for estimation of forward_payload costs - let ctx = context(); - let fwdFee = ctx.readForwardFee(); - throwUnless(75, ctx.value > fwdFee + ProvideAddressGasConsumption); - let ownerWorkchain: Int = parseStdAddress(msg.ownerAddress.asSlice()).workchain; let targetJettonWallet: BasechainAddress = @@ -60,11 +53,6 @@ contract JettonMinterNotcoin( }); } - receive(msg: JettonUpdateContent) { - throwUnless(73, sender() == self.owner); - self.jettonContent = msg.content; - } - receive(msg: Mint) { let ctx = context(); throwUnless(73, ctx.sender == self.owner); @@ -84,6 +72,11 @@ contract JettonMinterNotcoin( }); } + receive(msg: JettonUpdateContent) { + throwUnless(73, sender() == self.owner); + self.jettonContent = msg.content; + } + receive(msg: ChangeAdmin) { throwUnless(73, sender() == self.owner); self.nextOwner = msg.nextAdmin; diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index 2482430ee..ae8c60447 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -14,8 +14,8 @@ "label": "1.6.2", "pr": "https://github.com/tact-lang/tact/pull/2229", "size": { - "minter cells": "30", - "minter bits": "16580", + "minter cells": "31", + "minter bits": "16302", "wallet cells": "15", "wallet bits": "8268" } diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index 7414e5d2d..0eed9646b 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -15,7 +15,7 @@ "gas": { "transfer": "15822", "burn": "11862", - "discovery": "6102" + "discovery": "5785" } } ] From e3117ad9fda82e29b41f4254fbb59662b3bc2ff8 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 12:55:28 +0300 Subject: [PATCH 08/16] reduce minter storage --- src/benchmarks/contracts/jetton-minter-notcoin.tact | 1 - 1 file changed, 1 deletion(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index 2528b5416..3a7ec76b6 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -1,7 +1,6 @@ import "./messages"; import "./jetton-wallet-notcoin"; -const ProvideAddressGasConsumption: Int = ton("0.01"); const Workchain: Int = 0; struct JettonMasterState { From efe3a31071b6544c2b088c2b9f7842f5876b0e4d Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 12:56:43 +0300 Subject: [PATCH 09/16] cspell --- src/benchmarks/contracts/jetton-wallet-notcoin.tact | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index d652ddee3..bc805557b 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -12,8 +12,8 @@ const MIN_STORAGE_DURATION: Int = 5 * 365 * 24 * 3600; // 5 years const JETTON_WALLET_BITS: Int = 1033; const JETTON_WALLET_CELLS: Int = 3; -const JETTON_WALLET_INITSTATE_BITS: Int = 931; -const JETTON_WALLET_INITSTATE_CELLS: Int = 3; +const JETTON_WALLET_INIT_STATE_BITS: Int = 931; +const JETTON_WALLET_INIT_STATE_CELLS: Int = 3; const BURN_NOTIFICATION_BITS: Int = 754; const BURN_NOTIFICATION_CELLS: Int = 1; @@ -24,7 +24,7 @@ inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, f throwUnless( 705, msgValue > fwdCount * fwdFee + - getSimpleForwardFee(JETTON_WALLET_INITSTATE_CELLS, JETTON_WALLET_INITSTATE_BITS, false) + + getSimpleForwardFee(JETTON_WALLET_INIT_STATE_CELLS, JETTON_WALLET_INIT_STATE_BITS, false) + getComputeFee(SEND_TRANSFER_GAS_CONSUMPTION, false) + getComputeFee(RECEIVE_TRANSFER_GAS_CONSUMPTION, false) + getStorageFee(JETTON_WALLET_CELLS, JETTON_WALLET_BITS, MIN_STORAGE_DURATION, false) From 8ca36d949bd524648f1786f9d5d16c048bc378bd Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 13:08:10 +0300 Subject: [PATCH 10/16] move jetton common logic --- src/benchmarks/escrow/escrow.spec.ts | 2 +- src/benchmarks/jetton/jetton.spec.ts | 170 ++--------------------- src/benchmarks/notcoin/notcoin.spec.ts | 170 ++--------------------- src/benchmarks/update.build.ts | 2 +- src/benchmarks/{util.ts => utils/gas.ts} | 0 src/benchmarks/utils/jetton.ts | 163 ++++++++++++++++++++++ src/benchmarks/wallet/wallet.spec.ts | 2 +- src/test/gas-consumption/gas.spec.ts | 2 +- 8 files changed, 187 insertions(+), 324 deletions(-) rename src/benchmarks/{util.ts => utils/gas.ts} (100%) create mode 100644 src/benchmarks/utils/jetton.ts diff --git a/src/benchmarks/escrow/escrow.spec.ts b/src/benchmarks/escrow/escrow.spec.ts index 50cbac992..b26fad8d0 100644 --- a/src/benchmarks/escrow/escrow.spec.ts +++ b/src/benchmarks/escrow/escrow.spec.ts @@ -18,7 +18,7 @@ import { printBenchmarkTable, generateCodeSizeResults, getStateSizeForAccount, -} from "../util"; +} from "../utils/gas"; import benchmarkResults from "./results_gas.json"; import { readFileSync } from "fs"; import { posixNormalize } from "../../utils/filePath"; diff --git a/src/benchmarks/jetton/jetton.spec.ts b/src/benchmarks/jetton/jetton.spec.ts index 4d9571630..459582abf 100644 --- a/src/benchmarks/jetton/jetton.spec.ts +++ b/src/benchmarks/jetton/jetton.spec.ts @@ -2,7 +2,6 @@ import "@ton/test-utils"; import { Address, beginCell, - Builder, Cell, contractAddress, SendMode, @@ -11,22 +10,11 @@ import { import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; import { Blockchain } from "@ton/sandbox"; -import { - type Mint, - type ProvideWalletAddress, - JettonMinter, - storeJettonBurn, - storeJettonTransfer, - storeMint, -} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; +import { JettonMinter } from "../contracts/output/jetton-minter-discoverable_JettonMinter"; import "@ton/test-utils"; import benchmarkCodeSizeResults from "./results_code_size.json"; -import type { - JettonBurn, - JettonTransfer, - JettonUpdateContent, -} from "../contracts/output/jetton-minter-discoverable_JettonMinter"; +import type { JettonUpdateContent } from "../contracts/output/jetton-minter-discoverable_JettonMinter"; import { generateCodeSizeResults, @@ -34,13 +22,19 @@ import { getStateSizeForAccount, getUsedGas, printBenchmarkTable, -} from "../util"; +} from "../utils/gas"; import benchmarkResults from "./results_gas.json"; import { join, resolve } from "path"; import { readFileSync } from "fs"; -import { storeProvideWalletAddress } from "../contracts/output/escrow_Escrow"; import { posixNormalize } from "../../utils/filePath"; import { type Step, writeLog } from "../../test/utils/write-vm-log"; +import { + getJettonWalletRaw, + sendBurnRaw, + sendDiscoveryRaw, + sendMintRaw, + sendTransferRaw, +} from "../utils/jetton"; const loadFunCJettonsBoc = () => { const bocMinter = readFileSync( @@ -91,150 +85,6 @@ const deployFuncJettonMinter = async ( }; }; -const sendDiscoveryRaw = async ( - minterAddress: Address, - via: SandboxContract, - address: Address, - includeAddress: boolean, - value: bigint, -) => { - const msg: ProvideWalletAddress = { - $$type: "ProvideWalletAddress", - queryId: 0n, - ownerAddress: address, - includeAddress: includeAddress, - }; - - const msgCell = beginCell().store(storeProvideWalletAddress(msg)).endCell(); - - return await via.send({ - to: minterAddress, - value, - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const sendTransferRaw = async ( - jettonWalletAddress: Address, - via: SandboxContract, - value: bigint, - jetton_amount: bigint, - to: Address, - responseAddress: Address, - customPayload: Cell | null, - forward_ton_amount: bigint, - forwardPayload: Cell | null, -) => { - const parsedForwardPayload = - forwardPayload != null - ? forwardPayload.beginParse() - : new Builder().storeUint(0, 1).endCell().beginParse(); //Either bit equals 0 - - const msg: JettonTransfer = { - $$type: "JettonTransfer", - queryId: 0n, - amount: jetton_amount, - destination: to, - responseDestination: responseAddress, - customPayload: customPayload, - forwardTonAmount: forward_ton_amount, - forwardPayload: parsedForwardPayload, - }; - - const msgCell = beginCell().store(storeJettonTransfer(msg)).endCell(); - - return await via.send({ - to: jettonWalletAddress, - value, - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const sendMintRaw = async ( - jettonMinterAddress: Address, - via: SandboxContract, - to: Address, - jetton_amount: bigint, - forward_ton_amount: bigint, - total_ton_amount: bigint, -) => { - if (total_ton_amount <= forward_ton_amount) { - throw new Error( - "Total TON amount should be greater than the forward amount", - ); - } - - const msg: Mint = { - $$type: "Mint", - queryId: 0n, - receiver: to, - tonAmount: total_ton_amount, - mintMessage: { - $$type: "JettonTransferInternal", - queryId: 0n, - amount: jetton_amount, - responseDestination: jettonMinterAddress, - forwardTonAmount: forward_ton_amount, - sender: jettonMinterAddress, - forwardPayload: beginCell().storeUint(0, 1).endCell().beginParse(), - }, - }; - - const msgCell = beginCell().store(storeMint(msg)).endCell(); - - return await via.send({ - to: jettonMinterAddress, - value: total_ton_amount + toNano("0.015"), - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const sendBurnRaw = async ( - jettonWalletAddress: Address, - via: SandboxContract, - value: bigint, - jetton_amount: bigint, - responseAddress: Address, - customPayload: Cell | null, -) => { - const msg: JettonBurn = { - $$type: "JettonBurn", - queryId: 0n, - amount: jetton_amount, - responseDestination: responseAddress, - customPayload: customPayload, - }; - - const msgCell = beginCell().store(storeJettonBurn(msg)).endCell(); - - return await via.send({ - to: jettonWalletAddress, - value, - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const getJettonWalletRaw = async ( - minterAddress: Address, - blockchain: Blockchain, - walletAddress: Address, -) => { - const walletAddressResult = await blockchain - .provider(minterAddress) - .get(`get_wallet_address`, [ - { - type: "slice", - cell: beginCell().storeAddress(walletAddress).endCell(), - }, - ]); - - return walletAddressResult.stack.readAddress(); -}; - describe("Jetton", () => { let blockchain: Blockchain; let jettonMinter: SandboxContract; diff --git a/src/benchmarks/notcoin/notcoin.spec.ts b/src/benchmarks/notcoin/notcoin.spec.ts index b13e3bda1..8dc3737b0 100644 --- a/src/benchmarks/notcoin/notcoin.spec.ts +++ b/src/benchmarks/notcoin/notcoin.spec.ts @@ -2,7 +2,6 @@ import "@ton/test-utils"; import { Address, beginCell, - Builder, Cell, contractAddress, SendMode, @@ -11,22 +10,11 @@ import { import type { SandboxContract, TreasuryContract } from "@ton/sandbox"; import { Blockchain } from "@ton/sandbox"; -import { - type Mint, - type ProvideWalletAddress, - JettonMinterNotcoin, - storeJettonBurn, - storeJettonTransfer, - storeMint, -} from "../contracts/output/jetton-minter-notcoin_JettonMinterNotcoin"; +import { JettonMinterNotcoin } from "../contracts/output/jetton-minter-notcoin_JettonMinterNotcoin"; import "@ton/test-utils"; import benchmarkCodeSizeResults from "./results_code_size.json"; -import type { - JettonBurn, - JettonTransfer, - JettonUpdateContent, -} from "../contracts/output/jetton-minter-notcoin_JettonMinterNotcoin"; +import type { JettonUpdateContent } from "../contracts/output/jetton-minter-notcoin_JettonMinterNotcoin"; import { generateCodeSizeResults, @@ -34,13 +22,19 @@ import { getStateSizeForAccount, getUsedGas, printBenchmarkTable, -} from "../util"; +} from "../utils/gas"; import benchmarkResults from "./results_gas.json"; import { join, resolve } from "path"; import { readFileSync } from "fs"; -import { storeProvideWalletAddress } from "../contracts/output/escrow_Escrow"; import { posixNormalize } from "../../utils/filePath"; import { type Step, writeLog } from "../../test/utils/write-vm-log"; +import { + getJettonWalletRaw, + sendBurnRaw, + sendDiscoveryRaw, + sendMintRaw, + sendTransferRaw, +} from "../utils/jetton"; const loadNotcoinJettonsBoc = () => { const bocMinter = readFileSync( @@ -98,150 +92,6 @@ const deployNotcoinJettonMinter = async ( }; }; -const sendDiscoveryRaw = async ( - minterAddress: Address, - via: SandboxContract, - address: Address, - includeAddress: boolean, - value: bigint, -) => { - const msg: ProvideWalletAddress = { - $$type: "ProvideWalletAddress", - queryId: 0n, - ownerAddress: address, - includeAddress: includeAddress, - }; - - const msgCell = beginCell().store(storeProvideWalletAddress(msg)).endCell(); - - return await via.send({ - to: minterAddress, - value, - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const sendTransferRaw = async ( - jettonWalletAddress: Address, - via: SandboxContract, - value: bigint, - jetton_amount: bigint, - to: Address, - responseAddress: Address, - customPayload: Cell | null, - forward_ton_amount: bigint, - forwardPayload: Cell | null, -) => { - const parsedForwardPayload = - forwardPayload != null - ? forwardPayload.beginParse() - : new Builder().storeUint(0, 1).endCell().beginParse(); //Either bit equals 0 - - const msg: JettonTransfer = { - $$type: "JettonTransfer", - queryId: 0n, - amount: jetton_amount, - destination: to, - responseDestination: responseAddress, - customPayload: customPayload, - forwardTonAmount: forward_ton_amount, - forwardPayload: parsedForwardPayload, - }; - - const msgCell = beginCell().store(storeJettonTransfer(msg)).endCell(); - - return await via.send({ - to: jettonWalletAddress, - value, - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const sendMintRaw = async ( - jettonMinterAddress: Address, - via: SandboxContract, - to: Address, - jetton_amount: bigint, - forward_ton_amount: bigint, - total_ton_amount: bigint, -) => { - if (total_ton_amount <= forward_ton_amount) { - throw new Error( - "Total TON amount should be greater than the forward amount", - ); - } - - const msg: Mint = { - $$type: "Mint", - queryId: 0n, - receiver: to, - tonAmount: total_ton_amount, - mintMessage: { - $$type: "JettonTransferInternal", - queryId: 0n, - amount: jetton_amount, - responseDestination: jettonMinterAddress, - forwardTonAmount: forward_ton_amount, - sender: jettonMinterAddress, - forwardPayload: beginCell().storeUint(0, 1).endCell().beginParse(), - }, - }; - - const msgCell = beginCell().store(storeMint(msg)).endCell(); - - return await via.send({ - to: jettonMinterAddress, - value: total_ton_amount + toNano("0.015"), - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const sendBurnRaw = async ( - jettonWalletAddress: Address, - via: SandboxContract, - value: bigint, - jetton_amount: bigint, - responseAddress: Address, - customPayload: Cell | null, -) => { - const msg: JettonBurn = { - $$type: "JettonBurn", - queryId: 0n, - amount: jetton_amount, - responseDestination: responseAddress, - customPayload: customPayload, - }; - - const msgCell = beginCell().store(storeJettonBurn(msg)).endCell(); - - return await via.send({ - to: jettonWalletAddress, - value, - body: msgCell, - sendMode: SendMode.PAY_GAS_SEPARATELY, - }); -}; - -const getJettonWalletRaw = async ( - minterAddress: Address, - blockchain: Blockchain, - walletAddress: Address, -) => { - const walletAddressResult = await blockchain - .provider(minterAddress) - .get(`get_wallet_address`, [ - { - type: "slice", - cell: beginCell().storeAddress(walletAddress).endCell(), - }, - ]); - - return walletAddressResult.stack.readAddress(); -}; - describe("NotCoin", () => { let blockchain: Blockchain; let jettonMinter: SandboxContract; diff --git a/src/benchmarks/update.build.ts b/src/benchmarks/update.build.ts index fc5ff09cb..a997e16fb 100644 --- a/src/benchmarks/update.build.ts +++ b/src/benchmarks/update.build.ts @@ -8,7 +8,7 @@ import { printBenchmarkTable, type RawBenchmarkResult, type RawCodeSizeResult, -} from "./util"; +} from "./utils/gas"; import { createInterface } from "readline/promises"; import { globSync } from "../test/utils/all-in-folder.build"; diff --git a/src/benchmarks/util.ts b/src/benchmarks/utils/gas.ts similarity index 100% rename from src/benchmarks/util.ts rename to src/benchmarks/utils/gas.ts diff --git a/src/benchmarks/utils/jetton.ts b/src/benchmarks/utils/jetton.ts new file mode 100644 index 000000000..8407b7350 --- /dev/null +++ b/src/benchmarks/utils/jetton.ts @@ -0,0 +1,163 @@ +import type { Address, Cell } from "@ton/core"; +import { beginCell, Builder, SendMode, toNano } from "@ton/core"; +import type { + Blockchain, + SandboxContract, + TreasuryContract, +} from "@ton/sandbox"; +import type { + JettonBurn, + JettonTransfer, + Mint, + ProvideWalletAddress, +} from "../contracts/output/escrow_Escrow"; +import { + storeJettonBurn, + storeJettonTransfer, + storeMint, + storeProvideWalletAddress, +} from "../contracts/output/escrow_Escrow"; + +export const sendDiscoveryRaw = async ( + minterAddress: Address, + via: SandboxContract, + address: Address, + includeAddress: boolean, + value: bigint, +) => { + const msg: ProvideWalletAddress = { + $$type: "ProvideWalletAddress", + queryId: 0n, + ownerAddress: address, + includeAddress: includeAddress, + }; + + const msgCell = beginCell().store(storeProvideWalletAddress(msg)).endCell(); + + return await via.send({ + to: minterAddress, + value, + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +export const sendTransferRaw = async ( + jettonWalletAddress: Address, + via: SandboxContract, + value: bigint, + jetton_amount: bigint, + to: Address, + responseAddress: Address, + customPayload: Cell | null, + forward_ton_amount: bigint, + forwardPayload: Cell | null, +) => { + const parsedForwardPayload = + forwardPayload != null + ? forwardPayload.beginParse() + : new Builder().storeUint(0, 1).endCell().beginParse(); //Either bit equals 0 + + const msg: JettonTransfer = { + $$type: "JettonTransfer", + queryId: 0n, + amount: jetton_amount, + destination: to, + responseDestination: responseAddress, + customPayload: customPayload, + forwardTonAmount: forward_ton_amount, + forwardPayload: parsedForwardPayload, + }; + + const msgCell = beginCell().store(storeJettonTransfer(msg)).endCell(); + + return await via.send({ + to: jettonWalletAddress, + value, + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +export const sendMintRaw = async ( + jettonMinterAddress: Address, + via: SandboxContract, + to: Address, + jetton_amount: bigint, + forward_ton_amount: bigint, + total_ton_amount: bigint, +) => { + if (total_ton_amount <= forward_ton_amount) { + throw new Error( + "Total TON amount should be greater than the forward amount", + ); + } + + const msg: Mint = { + $$type: "Mint", + queryId: 0n, + receiver: to, + tonAmount: total_ton_amount, + mintMessage: { + $$type: "JettonTransferInternal", + queryId: 0n, + amount: jetton_amount, + responseDestination: jettonMinterAddress, + forwardTonAmount: forward_ton_amount, + sender: jettonMinterAddress, + forwardPayload: beginCell().storeUint(0, 1).endCell().beginParse(), + }, + }; + + const msgCell = beginCell().store(storeMint(msg)).endCell(); + + return await via.send({ + to: jettonMinterAddress, + value: total_ton_amount + toNano("0.015"), + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +export const sendBurnRaw = async ( + jettonWalletAddress: Address, + via: SandboxContract, + value: bigint, + jetton_amount: bigint, + responseAddress: Address, + customPayload: Cell | null, +) => { + const msg: JettonBurn = { + $$type: "JettonBurn", + queryId: 0n, + amount: jetton_amount, + responseDestination: responseAddress, + customPayload: customPayload, + }; + + const msgCell = beginCell().store(storeJettonBurn(msg)).endCell(); + + return await via.send({ + to: jettonWalletAddress, + value, + body: msgCell, + sendMode: SendMode.PAY_GAS_SEPARATELY, + }); +}; + +export const getJettonWalletRaw = async ( + minterAddress: Address, + blockchain: Blockchain, + walletAddress: Address, +) => { + const walletAddressResult = await blockchain + .provider(minterAddress) + .get(`get_wallet_address`, [ + { + type: "slice", + cell: beginCell().storeAddress(walletAddress).endCell(), + }, + ]); + + return walletAddressResult.stack.readAddress(); +}; diff --git a/src/benchmarks/wallet/wallet.spec.ts b/src/benchmarks/wallet/wallet.spec.ts index b4ea124f5..b371fa661 100644 --- a/src/benchmarks/wallet/wallet.spec.ts +++ b/src/benchmarks/wallet/wallet.spec.ts @@ -7,7 +7,7 @@ import { SendMode } from "@ton/core"; import { beginCell, Dictionary, toNano } from "@ton/core"; import "@ton/test-utils"; -import { getUsedGas, generateResults, printBenchmarkTable } from "../util"; +import { getUsedGas, generateResults, printBenchmarkTable } from "../utils/gas"; import benchmarkResults from "./results_gas.json"; import type { KeyPair } from "@ton/crypto"; import { getSecureRandomBytes, keyPairFromSeed, sign } from "@ton/crypto"; diff --git a/src/test/gas-consumption/gas.spec.ts b/src/test/gas-consumption/gas.spec.ts index 1b9efc31b..cacf90423 100644 --- a/src/test/gas-consumption/gas.spec.ts +++ b/src/test/gas-consumption/gas.spec.ts @@ -10,7 +10,7 @@ import type { } from "@ton/sandbox"; import { Blockchain } from "@ton/sandbox"; import "@ton/test-utils"; -import { getUsedGas } from "../../benchmarks/util"; +import { getUsedGas } from "../../benchmarks/utils/gas"; import type { Step } from "../utils/write-vm-log"; import { writeLog } from "../utils/write-vm-log"; import { join } from "path"; From 0f8dd2300818760775b0b58a01fb15cd2abf1b58 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Fri, 7 Mar 2025 13:09:51 +0300 Subject: [PATCH 11/16] add pr num --- src/benchmarks/notcoin/results_code_size.json | 2 +- src/benchmarks/notcoin/results_gas.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index ae8c60447..d62542569 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -8,7 +8,7 @@ "wallet cells": "16", "wallet bits": "7384" }, - "pr": null + "pr": "https://github.com/OpenBuilders/notcoin-contract" }, { "label": "1.6.2", diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index 0eed9646b..428e43a74 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -7,7 +7,7 @@ "burn": "12732", "discovery": "6545" }, - "pr": "https://github.com/ton-blockchain/token-contract" + "pr": "https://github.com/OpenBuilders/notcoin-contract" }, { "label": "1.6.2", From d1eb7c3768ac4c3784f36edc98fa71ce303a1d69 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Mon, 10 Mar 2025 16:38:09 +0300 Subject: [PATCH 12/16] fwd fee --- src/benchmarks/contracts/jetton-minter-notcoin.tact | 3 +-- src/benchmarks/contracts/jetton-wallet-notcoin.tact | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index 3a7ec76b6..49b2665ed 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -57,10 +57,9 @@ contract JettonMinterNotcoin( throwUnless(73, ctx.sender == self.owner); self.totalSupply += msg.mintMessage.amount; - let fwdFee = getOriginalFwdFee(ctx.readForwardFee(), false); // force workchain - checkAmountIsEnoughToTransfer(ctx.value, msg.mintMessage.forwardTonAmount, fwdFee); + checkAmountIsEnoughToTransfer(ctx.value, msg.mintMessage.forwardTonAmount, ctx.readForwardFee()); deploy(DeployParameters{ value: 0, diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index bc805557b..004cb539b 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -55,9 +55,7 @@ contract JettonWalletNotcoin( throwUnless(708, msg.forwardPayload.bits() >= 1); let ctx = context(); - - let fwdFee = getOriginalFwdFee(ctx.readForwardFee(), false); - checkAmountIsEnoughToTransfer(ctx.value, msg.forwardTonAmount, fwdFee); + checkAmountIsEnoughToTransfer(ctx.value, msg.forwardTonAmount, ctx.readForwardFee()); deploy(DeployParameters{ value: 0, From c63d511e14dbdaa796232b3e7945caa67e0ac5f6 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Mon, 10 Mar 2025 16:41:30 +0300 Subject: [PATCH 13/16] force basechain --- src/benchmarks/contracts/jetton-minter-notcoin.tact | 3 +-- src/benchmarks/contracts/jetton-wallet-notcoin.tact | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index 49b2665ed..c1a8fa08d 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -57,8 +57,7 @@ contract JettonMinterNotcoin( throwUnless(73, ctx.sender == self.owner); self.totalSupply += msg.mintMessage.amount; - // force workchain - + forceBasechain(msg.receiver); checkAmountIsEnoughToTransfer(ctx.value, msg.mintMessage.forwardTonAmount, ctx.readForwardFee()); deploy(DeployParameters{ diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index 004cb539b..3e523197c 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -19,7 +19,7 @@ const BURN_NOTIFICATION_BITS: Int = 754; const BURN_NOTIFICATION_CELLS: Int = 1; inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, fwdFee: Int) { - let fwdCount = 1 + forwardTonAmount & 1; + let fwdCount = 1 + sign(msg.forwardTonAmount); throwUnless( 705, @@ -47,7 +47,7 @@ contract JettonWalletNotcoin( master: Address, ) { receive(msg: JettonTransfer) { - throwUnless(333, parseStdAddress(msg.destination.asSlice()).workchain == 0); + forceBasechain(msg.destination); throwUnless(705, sender() == self.owner); self.balance -= msg.amount; From 01d3d21675558e1a5978d1be0e95a93ecf0ed5f9 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Mon, 10 Mar 2025 16:47:43 +0300 Subject: [PATCH 14/16] update --- src/benchmarks/contracts/jetton-wallet-notcoin.tact | 2 +- src/benchmarks/notcoin/results_code_size.json | 4 ++-- src/benchmarks/notcoin/results_gas.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index 3e523197c..9ffd4fd40 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -19,7 +19,7 @@ const BURN_NOTIFICATION_BITS: Int = 754; const BURN_NOTIFICATION_CELLS: Int = 1; inline fun checkAmountIsEnoughToTransfer(msgValue: Int, forwardTonAmount: Int, fwdFee: Int) { - let fwdCount = 1 + sign(msg.forwardTonAmount); + let fwdCount = 1 + sign(forwardTonAmount); throwUnless( 705, diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index d62542569..236000129 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -15,9 +15,9 @@ "pr": "https://github.com/tact-lang/tact/pull/2229", "size": { "minter cells": "31", - "minter bits": "16302", + "minter bits": "16278", "wallet cells": "15", - "wallet bits": "8268" + "wallet bits": "8220" } } ] diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index 428e43a74..bf866a3a7 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -13,7 +13,7 @@ "label": "1.6.2", "pr": "https://github.com/tact-lang/tact/pull/2229", "gas": { - "transfer": "15822", + "transfer": "15734", "burn": "11862", "discovery": "5785" } From b678a4d7bf88b31d68127dbe5d58c64f9e448a49 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Mon, 10 Mar 2025 18:04:23 +0300 Subject: [PATCH 15/16] review --- src/benchmarks/contracts/jetton-minter-notcoin.tact | 2 +- src/benchmarks/contracts/jetton-wallet-notcoin.tact | 2 -- src/benchmarks/jetton/results_code_size.json | 2 +- src/benchmarks/notcoin/results_code_size.json | 2 +- src/benchmarks/notcoin/results_gas.json | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/benchmarks/contracts/jetton-minter-notcoin.tact b/src/benchmarks/contracts/jetton-minter-notcoin.tact index c1a8fa08d..eb78fd7ad 100644 --- a/src/benchmarks/contracts/jetton-minter-notcoin.tact +++ b/src/benchmarks/contracts/jetton-minter-notcoin.tact @@ -133,4 +133,4 @@ inline fun getJettonBasechainWalletByOwner(jettonWalletOwner: Address): Basechai return contractBasechainAddress(getJettonWalletInit(jettonWalletOwner)); } -inline extends fun isNotNone(self: Address): Bool { return self.asSlice().preloadUint(2) != 0 } \ No newline at end of file +inline extends fun isNotNone(self: Address): Bool { return self.asSlice().preloadUint(2) != 0 } diff --git a/src/benchmarks/contracts/jetton-wallet-notcoin.tact b/src/benchmarks/contracts/jetton-wallet-notcoin.tact index 9ffd4fd40..33385199d 100644 --- a/src/benchmarks/contracts/jetton-wallet-notcoin.tact +++ b/src/benchmarks/contracts/jetton-wallet-notcoin.tact @@ -154,5 +154,3 @@ contract JettonWalletNotcoin( }; } } - - diff --git a/src/benchmarks/jetton/results_code_size.json b/src/benchmarks/jetton/results_code_size.json index be2c155bd..85166c267 100644 --- a/src/benchmarks/jetton/results_code_size.json +++ b/src/benchmarks/jetton/results_code_size.json @@ -62,7 +62,7 @@ }, { "label": "1.6.2 with forceBasechain", - "pr": "https://github.com/tact-lang/tact/pull/123", + "pr": "https://github.com/tact-lang/tact/pull/2330", "size": { "minter cells": "29", "minter bits": "14625", diff --git a/src/benchmarks/notcoin/results_code_size.json b/src/benchmarks/notcoin/results_code_size.json index 236000129..49d4b38cc 100644 --- a/src/benchmarks/notcoin/results_code_size.json +++ b/src/benchmarks/notcoin/results_code_size.json @@ -12,7 +12,7 @@ }, { "label": "1.6.2", - "pr": "https://github.com/tact-lang/tact/pull/2229", + "pr": "https://github.com/tact-lang/tact/pull/2329", "size": { "minter cells": "31", "minter bits": "16278", diff --git a/src/benchmarks/notcoin/results_gas.json b/src/benchmarks/notcoin/results_gas.json index bf866a3a7..61d42463c 100644 --- a/src/benchmarks/notcoin/results_gas.json +++ b/src/benchmarks/notcoin/results_gas.json @@ -11,7 +11,7 @@ }, { "label": "1.6.2", - "pr": "https://github.com/tact-lang/tact/pull/2229", + "pr": "https://github.com/tact-lang/tact/pull/2329", "gas": { "transfer": "15734", "burn": "11862", From 17c20fb2872adebbd854f00cb9b3c470963e2050 Mon Sep 17 00:00:00 2001 From: kaladin13 <335095@niuitmo.ru> Date: Mon, 10 Mar 2025 18:07:35 +0300 Subject: [PATCH 16/16] more review --- src/benchmarks/contracts/escrow.tact | 2 +- src/benchmarks/contracts/messages.tact | 2 +- src/benchmarks/contracts/wallet.tact | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/benchmarks/contracts/escrow.tact b/src/benchmarks/contracts/escrow.tact index e717ac349..3f0d8865a 100644 --- a/src/benchmarks/contracts/escrow.tact +++ b/src/benchmarks/contracts/escrow.tact @@ -198,4 +198,4 @@ contract Escrow ( get fun escrowInfo(): Escrow { return self; } -} \ No newline at end of file +} diff --git a/src/benchmarks/contracts/messages.tact b/src/benchmarks/contracts/messages.tact index 63c693f69..deb09d9a3 100644 --- a/src/benchmarks/contracts/messages.tact +++ b/src/benchmarks/contracts/messages.tact @@ -107,4 +107,4 @@ message(0xfb88e119) ClaimAdmin { message(0x7431f221) DropAdmin { queryId: Int as uint64; -} \ No newline at end of file +} diff --git a/src/benchmarks/contracts/wallet.tact b/src/benchmarks/contracts/wallet.tact index 6ce27411f..49df2f3dc 100644 --- a/src/benchmarks/contracts/wallet.tact +++ b/src/benchmarks/contracts/wallet.tact @@ -241,4 +241,4 @@ contract Wallet( get fun get_extensions(): map { return self.extensions; } -} \ No newline at end of file +}