From 9d2466afddf78b0bcf3c7b81f5541f582433189b Mon Sep 17 00:00:00 2001 From: Andrew Gutarev Date: Tue, 11 Nov 2025 19:15:21 +0500 Subject: [PATCH 1/4] aa --- standard/wallets/comparison/.gitignore | 11 + standard/wallets/comparison/.prettierrc | 8 + standard/wallets/comparison/README.md | 15 + standard/wallets/comparison/jest.config.js | 14 + standard/wallets/comparison/package.json | 28 + .../tests/WalletFeeComparison.spec.ts | 847 ++++ .../wallets/comparison/tests/imports/const.ts | 18 + .../tests/results/wallet-fee-comparison.md | 145 + .../comparison/tests/utils/feeExtraction.ts | 73 + .../comparison/tests/utils/gasUtils.ts | 329 ++ standard/wallets/comparison/transaction.cpp | 4253 +++++++++++++++++ standard/wallets/comparison/tsconfig.json | 29 + standard/wallets/comparison/utils.ts | 7 + .../comparison/wrappers/HighloadQueryId.ts | 81 + .../comparison/wrappers/HighloadWalletV3.ts | 216 + .../comparison/wrappers/MsgGenerator.ts | 134 + .../wrappers/PreprocessedWalletV2.ts | 177 + 17 files changed, 6385 insertions(+) create mode 100644 standard/wallets/comparison/.gitignore create mode 100644 standard/wallets/comparison/.prettierrc create mode 100644 standard/wallets/comparison/README.md create mode 100644 standard/wallets/comparison/jest.config.js create mode 100644 standard/wallets/comparison/package.json create mode 100644 standard/wallets/comparison/tests/WalletFeeComparison.spec.ts create mode 100644 standard/wallets/comparison/tests/imports/const.ts create mode 100644 standard/wallets/comparison/tests/results/wallet-fee-comparison.md create mode 100644 standard/wallets/comparison/tests/utils/feeExtraction.ts create mode 100644 standard/wallets/comparison/tests/utils/gasUtils.ts create mode 100644 standard/wallets/comparison/transaction.cpp create mode 100644 standard/wallets/comparison/tsconfig.json create mode 100644 standard/wallets/comparison/utils.ts create mode 100644 standard/wallets/comparison/wrappers/HighloadQueryId.ts create mode 100644 standard/wallets/comparison/wrappers/HighloadWalletV3.ts create mode 100644 standard/wallets/comparison/wrappers/MsgGenerator.ts create mode 100644 standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts diff --git a/standard/wallets/comparison/.gitignore b/standard/wallets/comparison/.gitignore new file mode 100644 index 0000000..3c35454 --- /dev/null +++ b/standard/wallets/comparison/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +dist/ +build/ +*.tsbuildinfo +.env +.env.local +coverage/ +*.log +.DS_Store +temp/ + diff --git a/standard/wallets/comparison/.prettierrc b/standard/wallets/comparison/.prettierrc new file mode 100644 index 0000000..fa7224f --- /dev/null +++ b/standard/wallets/comparison/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "always" +} \ No newline at end of file diff --git a/standard/wallets/comparison/README.md b/standard/wallets/comparison/README.md new file mode 100644 index 0000000..4554cf3 --- /dev/null +++ b/standard/wallets/comparison/README.md @@ -0,0 +1,15 @@ +# Wallet Fee Comparison + +This workspace provides a reproducible test harness for benchmarking transaction fees across several TON wallet implementations. The suite focuses on measuring gas usage, total fees, and per-message costs for different payload sizes and batch configurations. + +## Layout +- `tests/WalletFeeComparison.spec.ts` — main Jest suite that orchestrates the fee measurements and outputs markdown reports. +- `tests/utils` — helper utilities for fee extraction and TON gas calculations. +- `wrappers/` — contract wrappers required to deploy and interact with wallets inside the sandbox. +- `build/` — precompiled wallet artifacts referenced by the wrappers. + +## Getting Started +1. Install dependencies: `yarn install` +2. Run the benchmark suite: `yarn test` + +The tests spawn sandbox blockchains locally, so no external network access is required. Results are written to `tests/results/wallet-fee-comparison.md`. diff --git a/standard/wallets/comparison/jest.config.js b/standard/wallets/comparison/jest.config.js new file mode 100644 index 0000000..1a021bd --- /dev/null +++ b/standard/wallets/comparison/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + collectCoverage: false, + coverageDirectory: 'coverage', + coveragePathIgnorePatterns: ['/node_modules/', '/dist/'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, + testMatch: ['**/tests/**/*.spec.ts'], +}; + diff --git a/standard/wallets/comparison/package.json b/standard/wallets/comparison/package.json new file mode 100644 index 0000000..4002430 --- /dev/null +++ b/standard/wallets/comparison/package.json @@ -0,0 +1,28 @@ +{ + "name": "example", + "version": "0.0.1", + "description": "Automated fee comparison tests for multiple TON wallet implementations", + "scripts": { + "build": "tsc", + "test": "jest", + "deploy": "ts-node scripts/deploy.ts", + "lint": "prettier --check .", + "format": "prettier --write ." + }, + "devDependencies": { + "@ton/blueprint": "^0.40.0", + "@ton/core": "^0.62.0", + "@ton/crypto": "^3.2.0", + "@ton/sandbox": "^0.37.2", + "@ton/test-utils": "^0.12.0", + "@ton/ton": "^15.3.1", + "@types/jest": "^29.5.0", + "@types/node": "^20.2.5", + "jest": "^29.5.0", + "prettier": "^3.1.0", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.3.2" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} \ No newline at end of file diff --git a/standard/wallets/comparison/tests/WalletFeeComparison.spec.ts b/standard/wallets/comparison/tests/WalletFeeComparison.spec.ts new file mode 100644 index 0000000..257754f --- /dev/null +++ b/standard/wallets/comparison/tests/WalletFeeComparison.spec.ts @@ -0,0 +1,847 @@ +import { writeFileSync, mkdirSync } from 'fs'; +import path from 'path'; +import { Blockchain } from '@ton/sandbox'; +import { + Cell, + SendMode, + internal as internal_relaxed, + toNano, + MessageRelaxed, + OutActionSendMsg, + beginCell, + Address, + fromNano, +} from '@ton/core'; +import { + WalletContractV2R1, + WalletContractV2R2, + WalletContractV3R1, + WalletContractV3R2, + WalletContractV4, + WalletContractV5R1 +} from '@ton/ton'; +import { KeyPair, keyPairFromSeed, getSecureRandomBytes } from '@ton/crypto'; +import { randomAddress } from '@ton/test-utils'; +import { HighloadWalletV3Code, HighloadWalletV3 } from '../wrappers/HighloadWalletV3'; +import { HighloadQueryId } from '../wrappers/HighloadQueryId'; +import { Wallet as PreprocessedWalletV2 } from '../wrappers/PreprocessedWalletV2'; +import { SUBWALLET_ID, DEFAULT_TIMEOUT } from './imports/const'; +import { extractTransactionFees } from './utils/feeExtraction'; + +type MessageBodyResolver = (messageIndex: number) => Cell; + +type MessageBodyConfig = { + name: string; + resolveBody: MessageBodyResolver; +}; + +type TestRunConfig = { + messageCount: number; + bodyResolver: MessageBodyResolver; + bodyName: string; +}; + +type WalletKey = + | 'v2r1' + | 'v2r2' + | 'v3r1' + | 'v3r2' + | 'v4r2' + | 'v5r1' + | 'preprocessedV2' + | 'highloadV3'; + +type EnabledWallets = Record; +type WalletNames = Record; + +type Config = { + constants: { + messageValue: bigint; + deployValue: bigint; + }; + requestTimings: { + realSeconds: number; + theoreticalSeconds: number; + }; + messageCounts: number[]; + messageBodyVariants: MessageBodyConfig[]; + enabledWallets: EnabledWallets; + walletNames: WalletNames; + displayFields: { + requests: boolean; + totalGas: boolean; + gasPerMsg: boolean; + totalFee: boolean; + feePerMsg: boolean; + percentToBestGas: boolean; + percentToBestFee: boolean; + time: boolean; + theoreticalTime: boolean; + }; + testRuns: TestRunConfig[]; +}; + +const CONFIG: Config = (() => { + const messageCounts = [1, 4, 200, 1000]; + const messageBodyVariants: MessageBodyConfig[] = [ + { name: 'Empty', resolveBody: () => Cell.EMPTY }, + { name: 'Comment', resolveBody: commentBodyResolver }, + { name: 'Jetton', resolveBody: jettonBodyResolver }, + ]; + + // Wallet selection (true = enabled, false = disabled) + const enabledWallets = { + v2r1: true, + v2r2: true, + v3r1: true, + v3r2: true, + v4r2: true, + v5r1: true, + preprocessedV2: false, + highloadV3: true, + } satisfies EnabledWallets; + + // Wallet names for reporting + const walletNames = { + v2r1: 'Wallet V2R1', + v2r2: 'Wallet V2R2', + v3r1: 'Wallet V3R1', + v3r2: 'Wallet V3R2', + v4r2: 'Wallet V4R2', + v5r1: 'Wallet V5R1', + preprocessedV2: 'Preprocessed Wallet V2', + highloadV3: 'Highload Wallet V3', + } satisfies WalletNames; + + // Columns to include + const displayFields = { + requests: true, + totalGas: true, + gasPerMsg: true, + totalFee: true, + feePerMsg: true, + percentToBestGas: true, + percentToBestFee: true, + time: true, + theoreticalTime: true, + }; + + return { + constants: { + messageValue: toNano('0.01'), + deployValue: toNano('1000'), + }, + requestTimings: { + realSeconds: 13, + theoreticalSeconds: 4, + }, + messageCounts, + messageBodyVariants, + enabledWallets, + walletNames, + displayFields, + testRuns: buildTestRuns(messageBodyVariants, messageCounts), + }; +})(); + +const toCoins = (value: bigint): number => { + return Number(fromNano(value)); +}; + +const formatSeconds = (seconds: number): string => { + const totalSeconds = Math.round(seconds); + if (totalSeconds < 60) { + return `${totalSeconds}s`; + } + const minutes = Math.floor(totalSeconds / 60); + const secs = totalSeconds % 60; + return `${minutes}m ${secs}s`; +}; + +const extractGasUsed = (tx: any): bigint => { + if (tx.description.type !== 'generic') return 0n; + if (tx.description.computePhase.type !== 'vm') return 0n; + return tx.description.computePhase.gasUsed as bigint; +}; + +const createMessages = ( + startIndex: number, + count: number, + resolveBody: MessageBodyResolver, +): MessageRelaxed[] => + Array.from({ length: count }, (_, offset) => + internal_relaxed({ + to: randomAddress(), + value: CONFIG.constants.messageValue, + bounce: false, + body: resolveBody(startIndex + offset), + }), + ); + +function buildTestRuns(bodyVariants: MessageBodyConfig[], counts: number[]): TestRunConfig[] { + return bodyVariants.flatMap((variant) => + counts.map((messageCount) => ({ + messageCount, + bodyResolver: variant.resolveBody, + bodyName: variant.name, + })), + ); +} + +function commentBodyResolver(messageIndex: number): Cell { + return beginCell().storeUint(0, 32).storeStringTail(randomString(12, messageIndex)).endCell(); +} + +function jettonBodyResolver(messageIndex: number): Cell { + return beginCell() + .storeUint(0xf8a7ea5, 32) + .storeUint(messageIndex, 64) + .storeCoins(1) + .storeAddress(randomAddress()) + .storeAddress(randomAddress()) + .storeMaybeRef(null) + .storeCoins(0) + .storeMaybeRef(commentBodyResolver(messageIndex)) + .endCell(); +} + +function randomString(size: number, seed: number): string { + return generateSeededString(seed, size); +} + +function generateSeededString( + seed: number, + size: number, + characterSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', +): string { + const randomFunc = mulberry32(seed); + + let result = ''; + for (let i = 0; i < size; i++) { + const randomIndex = Math.floor(randomFunc() * characterSet.length); + result += characterSet.charAt(randomIndex); + } + return result; +} + +function mulberry32(seed: number): () => number { + return function () { + let t = (seed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +type WalletTestResult = { + walletName: string; + requests: number; + totalGas: bigint; + totalFee: bigint; + messageCount: number; + bodyName: string; +}; + +type BatchExecutionContext = { + blockchain: Blockchain; + wallet: any; + seqno: bigint; + batchCount: number; + bodyResolver: MessageBodyResolver; + startIndex: number; +}; + +type BatchExecutionResult = { + gas: bigint; + fee: bigint; + nextSeqno?: bigint; +}; + +type WalletMeasurementOptions = { + walletName: string; + messageCount: number; + bodyResolver: MessageBodyResolver; + bodyName: string; + batchSize: number; + createWallet: (blockchain: Blockchain) => Promise | any; + deploy: (blockchain: Blockchain, wallet: any) => Promise; + executeBatch: (context: BatchExecutionContext) => Promise; +}; + +async function measureWalletBatches(options: WalletMeasurementOptions): Promise { + const { walletName, messageCount, bodyResolver, bodyName, batchSize, createWallet, deploy, executeBatch } = options; + + const blockchain = await Blockchain.create(); + const wallet = await createWallet(blockchain); + await deploy(blockchain, wallet); + + const balanceBefore = (await blockchain.getContract(wallet.address)).balance; + + let totalGas = 0n; + let totalFee = 0n; + let totalGasVirtual = 0n; + let totalFeeVirtual = 0n; + let requests = 0; + let sentMessagesCount = 0; + + let seqno: bigint = BigInt(await wallet.getSeqno()); + let nextMessageIndex = 0; + let lastBatch: { batchCount: number; gas: bigint; fee: bigint } | null = null; + + for (let i = 0; i < messageCount; i += batchSize) { + const batchCount = Math.min(batchSize, messageCount - i); + + if (lastBatch && batchCount === lastBatch.batchCount) { + totalGasVirtual += lastBatch.gas; + totalFeeVirtual += lastBatch.fee; + requests++; + continue; + } + + const { gas, fee, nextSeqno } = await executeBatch({ + blockchain, + wallet, + seqno, + batchCount, + bodyResolver, + startIndex: nextMessageIndex, + }); + + totalGas += gas; + totalFee += fee; + sentMessagesCount += batchCount; + requests++; + + seqno = nextSeqno ?? seqno + 1n; + nextMessageIndex += batchCount; + lastBatch = { batchCount, gas, fee }; + } + + const balanceAfter = (await blockchain.getContract(wallet.address)).balance; + const balanceDiff = balanceBefore - balanceAfter; + const totalMessageValue = CONFIG.constants.messageValue * BigInt(sentMessagesCount); + + expect(balanceDiff).toBe(totalMessageValue + totalFee); + + return { + walletName, + requests, + totalGas: totalGas + totalGasVirtual, + totalFee: totalFee + totalFeeVirtual, + messageCount, + bodyName, + }; +} + +describe('Wallet Fee Comparison', () => { + let keyPair: KeyPair; + const allResults: WalletTestResult[][] = []; // Results collected for each run + + beforeAll(async () => { + keyPair = keyPairFromSeed(await getSecureRandomBytes(32)); + }); + + const deployWallet = async (blockchain: Blockchain, wallet: any) => { + const deployer = await blockchain.treasury('deployer'); + await deployer.send({ + value: CONFIG.constants.deployValue, + to: wallet.address, + init: wallet.init, + }); + }; + + async function measureStandardWallet( + walletName: string, + createWallet: (blockchain: Blockchain) => any, + batchSize: number, + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string + ) { + return measureWalletBatches({ + walletName, + messageCount, + bodyResolver, + bodyName, + batchSize, + createWallet, + deploy: deployWallet, + executeBatch: async ({ wallet, seqno, batchCount, bodyResolver, blockchain, startIndex }) => { + const messages = createMessages(startIndex, batchCount, bodyResolver); + + const transfer = await wallet.createTransfer({ + seqno: Number(seqno), + secretKey: keyPair.secretKey, + messages, + sendMode: SendMode.NONE, + }); + const result = await wallet.send(transfer); + + const externalTx = result.transactions.find((tx: any) => tx.inMessage?.info.type === 'external-in'); + if (!externalTx) throw new Error('No external-in transaction'); + + const gas = extractGasUsed(externalTx); + const txFees = extractTransactionFees(externalTx, blockchain); + const fee = txFees.import_fee + txFees.storage_fee + txFees.gas_fees; + + return { gas, fee, nextSeqno: seqno + 1n }; + }, + }); + } + + async function measurePreprocessedWalletV2( + walletName: string, + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string + ) { + return measureWalletBatches({ + walletName, + messageCount, + bodyResolver, + bodyName, + batchSize: 255, + createWallet: (blockchain) => + blockchain.openContract(PreprocessedWalletV2.createFromPublicKey(keyPair.publicKey)), + deploy: async (blockchain, wallet) => { + const deployer = await blockchain.treasury('deployer'); + await wallet.sendDeploy(deployer.getSender(), CONFIG.constants.deployValue); + }, + executeBatch: async ({ wallet, seqno, batchCount, bodyResolver, blockchain, startIndex }) => { + const transfers = Array.from({ length: batchCount }, (_, offset) => ({ + to: randomAddress(), + value: CONFIG.constants.messageValue, + bounce: false, + body: bodyResolver(startIndex + offset), + mode: SendMode.NONE, + })); + + const result = await wallet.sendTransfers(keyPair, transfers, Number(seqno)); + + const externalTx = result.transactions.find((tx: any) => tx.inMessage?.info.type === 'external-in'); + if (!externalTx) throw new Error('No external-in transaction'); + + const gas = extractGasUsed(externalTx); + const txFees = extractTransactionFees(externalTx, blockchain); + const fee = txFees.import_fee + txFees.storage_fee + txFees.gas_fees; + + return { gas, fee, nextSeqno: seqno + 1n }; + }, + }); + } + + async function measureHighloadV3( + walletName: string, + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string + ) { + const blockchain = await Blockchain.create(); + blockchain.now = 1000; + let queryId = new HighloadQueryId(); + + const wallet = blockchain.openContract( + HighloadWalletV3.createFromConfig( + { publicKey: keyPair.publicKey, subwalletId: SUBWALLET_ID, timeout: DEFAULT_TIMEOUT }, + HighloadWalletV3Code + ) + ); + + const deployer = await blockchain.treasury('deployer'); + await wallet.sendDeploy(deployer.getSender(), CONFIG.constants.deployValue); + + const balanceBefore = (await blockchain.getContract(wallet.address)).balance; + const totalMessageValue = CONFIG.constants.messageValue * BigInt(messageCount); + + let totalGas = 0n; + let totalFee = 0n; + let requests = 0; + const batchSize = 254; + + for (let i = 0; i < messageCount; i += batchSize) { + const batchCount = Math.min(batchSize, messageCount - i); + const actions: OutActionSendMsg[] = Array.from({ length: batchCount }, (_, offset) => ({ + type: 'sendMsg', + mode: SendMode.NONE, + outMsg: internal_relaxed({ + to: randomAddress(), + value: CONFIG.constants.messageValue, + bounce: false, + body: bodyResolver(i + offset), + }), + })); + + const result = await wallet.sendBatch( + keyPair.secretKey, + actions, + SUBWALLET_ID, + queryId, + DEFAULT_TIMEOUT, + blockchain.now + ); + queryId = queryId.getNext(); + + const externalTx = result.transactions.find((tx: any) => tx.inMessage?.info.type === 'external-in'); + if (!externalTx) throw new Error('No external-in transaction'); + + const externalFees = extractTransactionFees(externalTx, blockchain); + const externalFee = + externalFees.import_fee + externalFees.storage_fee + externalFees.gas_fees + externalFees.out_fwd_fees; + + const internalTx = result.transactions.find( + (tx: any) => + tx.inMessage?.info.type === 'internal' && + tx.inMessage?.info.src?.equals?.(wallet.address) && + tx.inMessage?.info.dest?.equals?.(wallet.address) + ); + if (!internalTx) throw new Error('No internal self-call transaction'); + + const internalFees = extractTransactionFees(internalTx, blockchain); + const internalFee = internalFees.storage_fee + internalFees.gas_fees; + + totalGas += extractGasUsed(externalTx) + extractGasUsed(internalTx); + totalFee += externalFee + internalFee; + requests++; + } + + const balanceAfter = (await blockchain.getContract(wallet.address)).balance; + const balanceDiff = balanceBefore - balanceAfter; + + // Verify balance calculation + expect(balanceDiff).toBe(totalMessageValue + totalFee); + + return { + walletName, + requests, + totalGas, + totalFee, + messageCount, + bodyName, + }; + } + + CONFIG.testRuns.forEach((testRun, runIndex) => { + describe(`Run ${runIndex + 1}: ${testRun.messageCount} messages, body: ${testRun.bodyName}`, () => { + const results: WalletTestResult[] = []; + + // Wallet V2R1 + if (CONFIG.enabledWallets.v2r1) { + it(`Measure ${CONFIG.walletNames.v2r1}`, async () => { + const result = await measureStandardWallet( + CONFIG.walletNames.v2r1, + (blockchain) => + blockchain.openContract( + WalletContractV2R1.create({ workchain: 0, publicKey: keyPair.publicKey }) + ), + 4, // V2R1 supports up to 4 messages per transaction + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Wallet V2R2 + if (CONFIG.enabledWallets.v2r2) { + it(`Measure ${CONFIG.walletNames.v2r2}`, async () => { + const result = await measureStandardWallet( + CONFIG.walletNames.v2r2, + (blockchain) => + blockchain.openContract( + WalletContractV2R2.create({ workchain: 0, publicKey: keyPair.publicKey }) + ), + 4, // V2R2 supports up to 4 messages per transaction + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Wallet V3R1 + if (CONFIG.enabledWallets.v3r1) { + it(`Measure ${CONFIG.walletNames.v3r1}`, async () => { + const result = await measureStandardWallet( + CONFIG.walletNames.v3r1, + (blockchain) => + blockchain.openContract( + WalletContractV3R1.create({ workchain: 0, publicKey: keyPair.publicKey }) + ), + 4, // V3R1 supports up to 4 messages per transaction + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Wallet V3R2 + if (CONFIG.enabledWallets.v3r2) { + it(`Measure ${CONFIG.walletNames.v3r2}`, async () => { + const result = await measureStandardWallet( + CONFIG.walletNames.v3r2, + (blockchain) => + blockchain.openContract( + WalletContractV3R2.create({ workchain: 0, publicKey: keyPair.publicKey }) + ), + 4, // V3R2 supports up to 4 messages per transaction + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Wallet V4R2 + if (CONFIG.enabledWallets.v4r2) { + it(`Measure ${CONFIG.walletNames.v4r2}`, async () => { + const result = await measureStandardWallet( + CONFIG.walletNames.v4r2, + (blockchain) => + blockchain.openContract( + WalletContractV4.create({ workchain: 0, publicKey: keyPair.publicKey }) + ), + 4, // V4 supports up to 4 messages per transaction + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Wallet V5R1 + if (CONFIG.enabledWallets.v5r1) { + it(`Measure ${CONFIG.walletNames.v5r1}`, async () => { + const result = await measureStandardWallet( + CONFIG.walletNames.v5r1, + (blockchain) => + blockchain.openContract( + WalletContractV5R1.create({ workchain: 0, publicKey: keyPair.publicKey }) + ), + 255, // V5 supports up to 255 messages per transaction + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Preprocessed Wallet V2 + if (CONFIG.enabledWallets.preprocessedV2) { + it(`Measure ${CONFIG.walletNames.preprocessedV2}`, async () => { + const result = await measurePreprocessedWalletV2( + CONFIG.walletNames.preprocessedV2, + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + // Highload Wallet V3 + if (CONFIG.enabledWallets.highloadV3) { + it(`Measure ${CONFIG.walletNames.highloadV3}`, async () => { + const result = await measureHighloadV3( + CONFIG.walletNames.highloadV3, + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName + ); + results.push(result); + }); + } + + afterAll(() => { + allResults.push(results); + }); + }); + }); + + afterAll(() => { + if (allResults.length === 0) return; + + const markdownLines: string[] = ['# Wallet Fee Comparison Results', '']; + const numberFormatter = new Intl.NumberFormat('en-US'); + const tonFormatter = new Intl.NumberFormat('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 9, + }); + allResults.forEach((results, runIndex) => { + if (results.length === 0) return; + + const testRun = CONFIG.testRuns[runIndex]; + markdownLines.push( + `## Run ${runIndex + 1}: ${testRun.messageCount} messages, Body: ${testRun.bodyName}`, + '' + ); + + const gasPerMsgValues = results.map((r) => r.totalGas / BigInt(r.messageCount)); + const feePerMsgValues = results.map((r) => r.totalFee / BigInt(r.messageCount)); + const minGasPerMsg = gasPerMsgValues.reduce((min, val) => (val < min ? val : min), gasPerMsgValues[0]); + const minFeePerMsg = feePerMsgValues.reduce((min, val) => (val < min ? val : min), feePerMsgValues[0]); + + const formatPercentDiffPlain = (value: bigint, baseline: bigint): string => { + if (baseline === 0n) { + return 'N/A'; + } + const diff = Number(((value - baseline) * 10000n) / baseline) / 100; + if (!Number.isFinite(diff)) { + return 'N/A'; + } + if (diff === 0) { + return '0.00%'; + } + const prefix = diff > 0 ? '+' : ''; + return `${prefix}${diff.toFixed(2)}%`; + }; + + const formatPercentDiffMarkdown = (value: bigint, baseline: bigint): string => { + if (baseline === 0n) { + return 'N/A'; + } + if (value === baseline) { + return '**Best**'; + } + return formatPercentDiffPlain(value, baseline); + }; + + const isHighloadResult = (result: WalletTestResult) => result.walletName === CONFIG.walletNames.highloadV3; + + // Simplified assumption: Highload Wallet V3 can handle multiple batches per block, but the exact limit depends on network settings and payload size. + const formatRealTime = (result: WalletTestResult): string => { + const seconds = isHighloadResult(result) + ? CONFIG.requestTimings.realSeconds + : result.requests * CONFIG.requestTimings.realSeconds; + return formatSeconds(seconds); + }; + + const formatTheoreticalTime = (result: WalletTestResult): string => { + const seconds = isHighloadResult(result) + ? CONFIG.requestTimings.theoreticalSeconds + : result.requests * CONFIG.requestTimings.theoreticalSeconds; + return formatSeconds(seconds); + }; + + const columns: { + header: string; + markdownAccessor: (result: WalletTestResult, index: number) => string; + consoleAccessor: (result: WalletTestResult, index: number) => string | number; + }[] = [ + { + header: 'Wallet Version', + markdownAccessor: (result, idx) => { + const gasPerMsg = gasPerMsgValues[idx]; + const feePerMsg = feePerMsgValues[idx]; + const isGasBest = gasPerMsg === minGasPerMsg; + const isFeeBest = feePerMsg === minFeePerMsg; + const isBest = isGasBest || isFeeBest; + return isBest ? `**${result.walletName}** ${isFeeBest ? '✅' : ''}` : result.walletName; + }, + consoleAccessor: (result) => result.walletName, + }, + ]; + + if (CONFIG.displayFields.requests) { + columns.push({ + header: 'Requests', + markdownAccessor: (result) => numberFormatter.format(result.requests), + consoleAccessor: (result) => result.requests, + }); + } + if (CONFIG.displayFields.totalGas) { + columns.push({ + header: 'Total Gas', + markdownAccessor: (result) => numberFormatter.format(Number(result.totalGas)), + consoleAccessor: (result) => Number(result.totalGas), + }); + } + if (CONFIG.displayFields.gasPerMsg) { + columns.push({ + header: 'Gas per Msg', + markdownAccessor: (_result, idx) => numberFormatter.format(Number(gasPerMsgValues[idx])), + consoleAccessor: (_result, idx) => Number(gasPerMsgValues[idx]), + }); + } + if (CONFIG.displayFields.totalFee) { + columns.push({ + header: 'Total Fee (TON)', + markdownAccessor: (result) => tonFormatter.format(toCoins(result.totalFee)), + consoleAccessor: (result) => toCoins(result.totalFee), + }); + } + if (CONFIG.displayFields.feePerMsg) { + columns.push({ + header: 'Fee per Msg (TON)', + markdownAccessor: (_result, idx) => tonFormatter.format(toCoins(feePerMsgValues[idx])), + consoleAccessor: (_result, idx) => toCoins(feePerMsgValues[idx]), + }); + } + if (CONFIG.displayFields.percentToBestGas) { + columns.push({ + header: 'Gas delta (%)', + markdownAccessor: (_result, idx) => formatPercentDiffMarkdown(gasPerMsgValues[idx], minGasPerMsg), + consoleAccessor: (_result, idx) => formatPercentDiffPlain(gasPerMsgValues[idx], minGasPerMsg), + }); + } + if (CONFIG.displayFields.percentToBestFee) { + columns.push({ + header: 'Fee delta (%)', + markdownAccessor: (_result, idx) => formatPercentDiffMarkdown(feePerMsgValues[idx], minFeePerMsg), + consoleAccessor: (_result, idx) => formatPercentDiffPlain(feePerMsgValues[idx], minFeePerMsg), + }); + } + if (CONFIG.displayFields.time) { + columns.push({ + header: 'Real Time (sec)', + markdownAccessor: (result) => formatRealTime(result), + consoleAccessor: (result) => formatRealTime(result), + }); + } + if (CONFIG.displayFields.theoreticalTime) { + columns.push({ + header: 'Theoretical Time (sec)', + markdownAccessor: (result) => formatTheoreticalTime(result), + consoleAccessor: (result) => formatTheoreticalTime(result), + }); + } + + const headerRow = `| ${columns.map((column) => column.header).join(' | ')} |`; + const separatorRow = `| ${columns.map(() => '---').join(' | ')} |`; + + markdownLines.push(headerRow, separatorRow); + + results.forEach((result, idx) => { + const rowCells = columns.map((column) => column.markdownAccessor(result, idx)); + markdownLines.push(`| ${rowCells.join(' | ')} |`); + }); + + const consoleRows = results.map((result, idx) => { + const row: Record = {}; + columns.forEach((column) => { + row[column.header] = column.consoleAccessor(result, idx); + }); + return row; + }); + + console.log(`Run ${runIndex + 1}: ${testRun.messageCount} messages, Body: ${testRun.bodyName}`); + console.table(consoleRows); + + markdownLines.push(''); + }); + + const outputDir = path.resolve(__dirname, 'results'); + const outputFile = path.join(outputDir, 'wallet-fee-comparison.md'); + mkdirSync(outputDir, { recursive: true }); + writeFileSync(outputFile, markdownLines.join('\n'), { encoding: 'utf-8' }); + console.log(`Markdown report saved to ${outputFile}`); + }); +}); + + diff --git a/standard/wallets/comparison/tests/imports/const.ts b/standard/wallets/comparison/tests/imports/const.ts new file mode 100644 index 0000000..95c2d77 --- /dev/null +++ b/standard/wallets/comparison/tests/imports/const.ts @@ -0,0 +1,18 @@ +export const SUBWALLET_ID = 239; + +export const DEFAULT_TIMEOUT = 128; + +export enum OP { + InternalTransfer = 0xae42e5a4 +} +export abstract class Errors { + static invalid_signature = 33; + static invalid_subwallet = 34; + static invalid_creation_time = 35; + static already_executed = 36; +} + +export const maxKeyCount = (1 << 13); //That is max key count not max key value +export const maxShift = maxKeyCount - 1; +export const maxQueryCount = maxKeyCount * 1023; // Therefore value count +export const maxQueryId = (maxShift << 10) + 1022; \ No newline at end of file diff --git a/standard/wallets/comparison/tests/results/wallet-fee-comparison.md b/standard/wallets/comparison/tests/results/wallet-fee-comparison.md new file mode 100644 index 0000000..9be55f3 --- /dev/null +++ b/standard/wallets/comparison/tests/results/wallet-fee-comparison.md @@ -0,0 +1,145 @@ +# Wallet Fee Comparison Results + +## Run 1: 1 messages, Body: Empty + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Wallet V2R1** ✅ | 1 | 2,769 | 2,769 | 0.0017108 | 0.0017108 | **Best** | **Best** | 13s | 4s | +| Wallet V2R2 | 1 | 2,846 | 2,846 | 0.0017416 | 0.0017416 | +2.78% | +1.80% | 13s | 4s | +| Wallet V3R1 | 1 | 2,917 | 2,917 | 0.00177 | 0.00177 | +5.34% | +3.46% | 13s | 4s | +| Wallet V3R2 | 1 | 2,994 | 2,994 | 0.0018008 | 0.0018008 | +8.12% | +5.26% | 13s | 4s | +| Wallet V4R2 | 1 | 3,308 | 3,308 | 0.0019264 | 0.0019264 | +19.46% | +12.60% | 13s | 4s | +| Wallet V5R1 | 1 | 4,939 | 4,939 | 0.0026748 | 0.0026748 | +78.36% | +56.34% | 13s | 4s | +| Highload Wallet V3 | 1 | 7,956 | 7,956 | 0.0049124 | 0.0049124 | +187.32% | +187.14% | 13s | 4s | + +## Run 2: 4 messages, Body: Empty + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Wallet V2R1** ✅ | 1 | 4,695 | 1,173 | 0.0030908 | 0.0007727 | **Best** | **Best** | 13s | 4s | +| Wallet V2R2 | 1 | 4,772 | 1,193 | 0.0031216 | 0.0007804 | +1.70% | +0.99% | 13s | 4s | +| Wallet V3R1 | 1 | 4,843 | 1,210 | 0.00315 | 0.0007875 | +3.15% | +1.91% | 13s | 4s | +| Wallet V3R2 | 1 | 4,920 | 1,230 | 0.0031808 | 0.0007952 | +4.85% | +2.91% | 13s | 4s | +| Wallet V4R2 | 1 | 5,234 | 1,308 | 0.0033064 | 0.0008266 | +11.50% | +6.97% | 13s | 4s | +| Wallet V5R1 | 1 | 7,090 | 1,772 | 0.0043128 | 0.0010782 | +51.06% | +39.53% | 13s | 4s | +| Highload Wallet V3 | 1 | 7,956 | 1,989 | 0.0064676 | 0.0016169 | +69.56% | +109.25% | 13s | 4s | + +## Run 3: 200 messages, Body: Empty + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Wallet V2R1 | 50 | 234,750 | 1,173 | 0.15454 | 0.0007727 | +2907.69% | +42.99% | 10m 50s | 3m 20s | +| Wallet V2R2 | 50 | 238,600 | 1,193 | 0.15608 | 0.0007804 | +2958.97% | +44.41% | 10m 50s | 3m 20s | +| Wallet V3R1 | 50 | 242,150 | 1,210 | 0.1575 | 0.0007875 | +3002.56% | +45.73% | 10m 50s | 3m 20s | +| Wallet V3R2 | 50 | 246,000 | 1,230 | 0.15904 | 0.0007952 | +3053.84% | +47.15% | 10m 50s | 3m 20s | +| Wallet V4R2 | 50 | 261,700 | 1,308 | 0.16532 | 0.0008266 | +3253.84% | +52.96% | 10m 50s | 3m 20s | +| Wallet V5R1 | 1 | 147,622 | 738 | 0.1113288 | 0.000556644 | +1792.30% | +3.01% | 13s | 4s | +| **Highload Wallet V3** ✅ | 1 | 7,956 | 39 | 0.108074 | 0.00054037 | **Best** | **Best** | 13s | 4s | + +## Run 4: 1000 messages, Body: Empty + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Wallet V2R1 | 250 | 1,173,750 | 1,173 | 0.7727 | 0.0007727 | +3683.87% | +44.18% | 54m 10s | 16m 40s | +| Wallet V2R2 | 250 | 1,193,000 | 1,193 | 0.7804 | 0.0007804 | +3748.38% | +45.61% | 54m 10s | 16m 40s | +| Wallet V3R1 | 250 | 1,210,750 | 1,210 | 0.7875 | 0.0007875 | +3803.22% | +46.94% | 54m 10s | 16m 40s | +| Wallet V3R2 | 250 | 1,230,000 | 1,230 | 0.7952 | 0.0007952 | +3867.74% | +48.37% | 54m 10s | 16m 40s | +| Wallet V4R2 | 250 | 1,308,500 | 1,308 | 0.8266 | 0.0008266 | +4119.35% | +54.23% | 54m 10s | 16m 40s | +| Wallet V5R1 | 4 | 733,888 | 733 | 0.554515201 | 0.000554515 | +2264.51% | +3.46% | 52s | 16s | +| **Highload Wallet V3** ✅ | 4 | 31,689 | 31 | 0.535922 | 0.000535922 | **Best** | **Best** | 13s | 4s | + +## Run 5: 1 messages, Body: Comment + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Wallet V2R1** ✅ | 1 | 2,769 | 2,769 | 0.001762 | 0.001762 | **Best** | **Best** | 13s | 4s | +| Wallet V2R2 | 1 | 2,846 | 2,846 | 0.0017928 | 0.0017928 | +2.78% | +1.74% | 13s | 4s | +| Wallet V3R1 | 1 | 2,917 | 2,917 | 0.0018212 | 0.0018212 | +5.34% | +3.35% | 13s | 4s | +| Wallet V3R2 | 1 | 2,994 | 2,994 | 0.001852 | 0.001852 | +8.12% | +5.10% | 13s | 4s | +| Wallet V4R2 | 1 | 3,308 | 3,308 | 0.0019776 | 0.0019776 | +19.46% | +12.23% | 13s | 4s | +| Wallet V5R1 | 1 | 4,939 | 4,939 | 0.002726 | 0.002726 | +78.36% | +54.71% | 13s | 4s | +| Highload Wallet V3 | 1 | 7,956 | 7,956 | 0.0050148 | 0.0050148 | +187.32% | +184.60% | 13s | 4s | + +## Run 6: 4 messages, Body: Comment + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Wallet V2R1** ✅ | 1 | 4,695 | 1,173 | 0.0032956 | 0.0008239 | **Best** | **Best** | 13s | 4s | +| Wallet V2R2 | 1 | 4,772 | 1,193 | 0.0033264 | 0.0008316 | +1.70% | +0.93% | 13s | 4s | +| Wallet V3R1 | 1 | 4,843 | 1,210 | 0.0033548 | 0.0008387 | +3.15% | +1.79% | 13s | 4s | +| Wallet V3R2 | 1 | 4,920 | 1,230 | 0.0033856 | 0.0008464 | +4.85% | +2.73% | 13s | 4s | +| Wallet V4R2 | 1 | 5,234 | 1,308 | 0.0035112 | 0.0008778 | +11.50% | +6.54% | 13s | 4s | +| Wallet V5R1 | 1 | 7,090 | 1,772 | 0.0045176 | 0.0011294 | +51.06% | +37.07% | 13s | 4s | +| Highload Wallet V3 | 1 | 7,956 | 1,989 | 0.0068772 | 0.0017193 | +69.56% | +108.67% | 13s | 4s | + +## Run 7: 200 messages, Body: Comment + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Wallet V2R1 | 50 | 234,750 | 1,173 | 0.16478 | 0.0008239 | +2907.69% | +35.54% | 10m 50s | 3m 20s | +| Wallet V2R2 | 50 | 238,600 | 1,193 | 0.16632 | 0.0008316 | +2958.97% | +36.81% | 10m 50s | 3m 20s | +| Wallet V3R1 | 50 | 242,150 | 1,210 | 0.16774 | 0.0008387 | +3002.56% | +37.97% | 10m 50s | 3m 20s | +| Wallet V3R2 | 50 | 246,000 | 1,230 | 0.16928 | 0.0008464 | +3053.84% | +39.24% | 10m 50s | 3m 20s | +| Wallet V4R2 | 50 | 261,700 | 1,308 | 0.17556 | 0.0008778 | +3253.84% | +44.41% | 10m 50s | 3m 20s | +| **Wallet V5R1** ✅ | 1 | 147,622 | 738 | 0.1215688 | 0.000607844 | +1792.30% | **Best** | 13s | 4s | +| **Highload Wallet V3** | 1 | 7,956 | 39 | 0.128554 | 0.00064277 | **Best** | +5.74% | 13s | 4s | + +## Run 8: 1000 messages, Body: Comment + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Wallet V2R1 | 250 | 1,173,750 | 1,173 | 0.8239 | 0.0008239 | +3683.87% | +36.02% | 54m 10s | 16m 40s | +| Wallet V2R2 | 250 | 1,193,000 | 1,193 | 0.8316 | 0.0008316 | +3748.38% | +37.29% | 54m 10s | 16m 40s | +| Wallet V3R1 | 250 | 1,210,750 | 1,210 | 0.8387 | 0.0008387 | +3803.22% | +38.46% | 54m 10s | 16m 40s | +| Wallet V3R2 | 250 | 1,230,000 | 1,230 | 0.8464 | 0.0008464 | +3867.74% | +39.73% | 54m 10s | 16m 40s | +| Wallet V4R2 | 250 | 1,308,500 | 1,308 | 0.8778 | 0.0008778 | +4119.35% | +44.91% | 54m 10s | 16m 40s | +| **Wallet V5R1** ✅ | 4 | 733,888 | 733 | 0.6057152 | 0.000605715 | +2264.51% | **Best** | 52s | 16s | +| **Highload Wallet V3** | 4 | 31,689 | 31 | 0.638322 | 0.000638322 | **Best** | +5.38% | 13s | 4s | + +## Run 9: 1 messages, Body: Jetton + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Wallet V2R1** ✅ | 1 | 2,769 | 2,769 | 0.0021012 | 0.0021012 | **Best** | **Best** | 13s | 4s | +| Wallet V2R2 | 1 | 2,846 | 2,846 | 0.002132 | 0.002132 | +2.78% | +1.46% | 13s | 4s | +| Wallet V3R1 | 1 | 2,917 | 2,917 | 0.0021604 | 0.0021604 | +5.34% | +2.81% | 13s | 4s | +| Wallet V3R2 | 1 | 2,994 | 2,994 | 0.002191201 | 0.002191201 | +8.12% | +4.28% | 13s | 4s | +| Wallet V4R2 | 1 | 3,308 | 3,308 | 0.0023168 | 0.0023168 | +19.46% | +10.26% | 13s | 4s | +| Wallet V5R1 | 1 | 4,939 | 4,939 | 0.0030652 | 0.0030652 | +78.36% | +45.87% | 13s | 4s | +| Highload Wallet V3 | 1 | 7,956 | 7,956 | 0.0056932 | 0.0056932 | +187.32% | +170.94% | 13s | 4s | + +## Run 10: 4 messages, Body: Jetton + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| **Wallet V2R1** ✅ | 1 | 4,695 | 1,173 | 0.0046524 | 0.0011631 | **Best** | **Best** | 13s | 4s | +| Wallet V2R2 | 1 | 4,772 | 1,193 | 0.0046832 | 0.0011708 | +1.70% | +0.66% | 13s | 4s | +| Wallet V3R1 | 1 | 4,843 | 1,210 | 0.0047116 | 0.0011779 | +3.15% | +1.27% | 13s | 4s | +| Wallet V3R2 | 1 | 4,920 | 1,230 | 0.004742401 | 0.0011856 | +4.85% | +1.93% | 13s | 4s | +| Wallet V4R2 | 1 | 5,234 | 1,308 | 0.004868 | 0.001217 | +11.50% | +4.63% | 13s | 4s | +| Wallet V5R1 | 1 | 7,090 | 1,772 | 0.0058744 | 0.0014686 | +51.06% | +26.26% | 13s | 4s | +| Highload Wallet V3 | 1 | 7,956 | 1,989 | 0.0095908 | 0.0023977 | +69.56% | +106.14% | 13s | 4s | + +## Run 11: 200 messages, Body: Jetton + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Wallet V2R1 | 50 | 234,750 | 1,173 | 0.23262 | 0.0011631 | +2907.69% | +22.81% | 10m 50s | 3m 20s | +| Wallet V2R2 | 50 | 238,600 | 1,193 | 0.23416 | 0.0011708 | +2958.97% | +23.62% | 10m 50s | 3m 20s | +| Wallet V3R1 | 50 | 242,150 | 1,210 | 0.23558 | 0.0011779 | +3002.56% | +24.37% | 10m 50s | 3m 20s | +| Wallet V3R2 | 50 | 246,000 | 1,230 | 0.23712 | 0.0011856 | +3053.84% | +25.18% | 10m 50s | 3m 20s | +| Wallet V4R2 | 50 | 261,700 | 1,308 | 0.2434 | 0.001217 | +3253.84% | +28.50% | 10m 50s | 3m 20s | +| **Wallet V5R1** ✅ | 1 | 147,622 | 738 | 0.1894088 | 0.000947044 | +1792.30% | **Best** | 13s | 4s | +| **Highload Wallet V3** | 1 | 7,956 | 39 | 0.264234 | 0.00132117 | **Best** | +39.50% | 13s | 4s | + +## Run 12: 1000 messages, Body: Jetton + +| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Wallet V2R1 | 250 | 1,173,750 | 1,173 | 1.1631 | 0.0011631 | +3683.87% | +23.09% | 54m 10s | 16m 40s | +| Wallet V2R2 | 250 | 1,193,000 | 1,193 | 1.1708 | 0.0011708 | +3748.38% | +23.90% | 54m 10s | 16m 40s | +| Wallet V3R1 | 250 | 1,210,750 | 1,210 | 1.1779 | 0.0011779 | +3803.22% | +24.65% | 54m 10s | 16m 40s | +| Wallet V3R2 | 250 | 1,230,000 | 1,230 | 1.18560025 | 0.0011856 | +3867.74% | +25.47% | 54m 10s | 16m 40s | +| Wallet V4R2 | 250 | 1,308,500 | 1,308 | 1.217 | 0.001217 | +4119.35% | +28.79% | 54m 10s | 16m 40s | +| **Wallet V5R1** ✅ | 4 | 733,888 | 733 | 0.944915201 | 0.000944915 | +2264.51% | **Best** | 52s | 16s | +| **Highload Wallet V3** | 4 | 31,689 | 31 | 1.316722 | 0.001316722 | **Best** | +39.34% | 13s | 4s | diff --git a/standard/wallets/comparison/tests/utils/feeExtraction.ts b/standard/wallets/comparison/tests/utils/feeExtraction.ts new file mode 100644 index 0000000..29923d4 --- /dev/null +++ b/standard/wallets/comparison/tests/utils/feeExtraction.ts @@ -0,0 +1,73 @@ +import { Blockchain } from '@ton/sandbox'; +import { beginCell, storeMessage } from '@ton/core'; +import { computeCellForwardFees, getMsgPrices } from './gasUtils'; + +/** + * Transaction fee components extracted from a transaction. + * + * Note: According to TON documentation, msg_fwd_fees already includes the action fee. + * For internal messages: msg_fwd_fees = action_fee + fwd_fee + * where action_fee ≈ msg_fwd_fees * first_frac / 2^16 + * + * Reference: https://docs.ton.org/develop/howto/fees-low-level#forward-fee + */ +export type TransactionFees = { + storage_fee: bigint; // Storage fees collected during storage phase + gas_fees: bigint; // Computation fees (gas) from compute phase + action_fees: bigint; // Action phase fees for sending messages + out_fwd_fees: bigint; // Total forward fees for outbound messages (includes action_fees) + import_fee: bigint; // Import fee for external-in messages (0 for internal) + in_fwd_fee: bigint; // Forward fee for inbound internal messages (0 for external) +}; + +/** + * Extracts fee components from a transaction. + * + * @param tx - Transaction object to analyze + * @param blockchain - Blockchain instance for config access (required for import_fee calculation) + * @returns TransactionFees object with detailed fee breakdown + */ +export function extractTransactionFees(tx: any, blockchain: Blockchain): TransactionFees { + const fees: TransactionFees = { + storage_fee: 0n, + gas_fees: 0n, + action_fees: 0n, + out_fwd_fees: 0n, + import_fee: 0n, + in_fwd_fee: 0n, + }; + + if (tx.description.type !== 'generic') { + return fees; + } + + // Storage fee + fees.storage_fee = (tx.description.storagePhase?.storageFeesCollected ?? 0n) as bigint; + + // Compute phase: gas fees + if (tx.description.computePhase.type === 'vm') { + fees.gas_fees = tx.description.computePhase.gasFees as bigint; + } + + // Action phase: fees for sending messages, setting code, etc. + fees.action_fees = (tx.description.actionPhase?.totalActionFees ?? 0n) as bigint; + + // Action phase: total forward fees for outbound messages + // Note: totalFwdFees includes action_fees (sender's share of msg_fwd_fees) + fees.out_fwd_fees = (tx.description.actionPhase?.totalFwdFees ?? 0n) as bigint; + + // Inbound message fees (depends on message type) + if (tx.inMessage?.info.type === 'external-in') { + // External messages: import fee + const msgPrices = getMsgPrices(blockchain.config, 0); + + const extMsgCell = beginCell().store(storeMessage(tx.inMessage)).endCell(); + fees.import_fee = computeCellForwardFees(msgPrices, extMsgCell); + } else if (tx.inMessage?.info.type === 'internal') { + // Internal messages: forward fee paid by sender + fees.in_fwd_fee = tx.inMessage.info.forwardFee as bigint; + } + + return fees; +} + diff --git a/standard/wallets/comparison/tests/utils/gasUtils.ts b/standard/wallets/comparison/tests/utils/gasUtils.ts new file mode 100644 index 0000000..599ecc9 --- /dev/null +++ b/standard/wallets/comparison/tests/utils/gasUtils.ts @@ -0,0 +1,329 @@ +import { Cell, Slice, toNano, beginCell, Address, Dictionary, Message, DictionaryValue, Transaction } from '@ton/core'; + +export type GasPrices = { + flat_gas_limit: bigint, + flat_gas_price: bigint, + gas_price: bigint; +}; +export type StoragePrices = { + utime_sice: number, + bit_price_ps: bigint, + cell_price_ps: bigint, + mc_bit_price_ps: bigint, + mc_cell_price_ps: bigint +}; + + +export type MsgPrices = ReturnType; +export type FullFees = ReturnType; + +export class StorageStats { + bits: bigint; + cells: bigint; + + constructor(bits?: number | bigint, cells?: number | bigint) { + this.bits = bits !== undefined ? BigInt(bits) : 0n; + this.cells = cells !== undefined ? BigInt(cells) : 0n; + } + add(...stats: StorageStats[]) { + let cells = this.cells, bits = this.bits; + for (let stat of stats) { + bits += stat.bits; + cells += stat.cells; + } + return new StorageStats(bits, cells); + } + sub(...stats: StorageStats[]) { + let cells = this.cells, bits = this.bits; + for (let stat of stats) { + bits -= stat.bits; + cells -= stat.cells; + } + return new StorageStats(bits, cells); + } + addBits(bits: number | bigint) { + return new StorageStats(this.bits + BigInt(bits), this.cells); + } + subBits(bits: number | bigint) { + return new StorageStats(this.bits - BigInt(bits), this.cells); + } + addCells(cells: number | bigint) { + return new StorageStats(this.bits, this.cells + BigInt(cells)); + } + subCells(cells: number | bigint) { + return new StorageStats(this.bits, this.cells - BigInt(cells)); + } + + toString(): string { + return JSON.stringify({ + bits: this.bits.toString(), + cells: this.cells.toString() + }); + } +} + +export function computedGeneric(transaction: T) { + if (transaction.description.type !== "generic") + throw new Error("Expected generic transactionaction"); + if (transaction.description.computePhase.type !== "vm") + throw new Error("Compute phase expected") + return transaction.description.computePhase; +} + +export function storageGeneric(transaction: T) { + if (transaction.description.type !== "generic") + throw new Error("Expected generic transactionaction"); + const storagePhase = transaction.description.storagePhase; + if (storagePhase === null || storagePhase === undefined) + throw new Error("Storage phase expected") + return storagePhase; +} +export function getFwdStats(transaction: T) { + if (transaction.description.type !== "generic") + throw new Error("Expected generic transaction"); + if (transaction.description.actionPhase == undefined) + throw new Error("Action phase expected"); + const actionMsgSize = transaction.description.actionPhase.totalMessageSize; + return new StorageStats(actionMsgSize.bits, actionMsgSize.cells); +} + +function shr16ceil(src: bigint) { + let rem = src % BigInt(65536); + let res = src / 65536n; // >> BigInt(16); + if (rem != BigInt(0)) { + res += BigInt(1); + } + return res; +} + +export function collectCellStats(cell: Cell, visited: Array, skipRoot: boolean = false): StorageStats { + let bits = skipRoot ? 0n : BigInt(cell.bits.length); + let cells = skipRoot ? 0n : 1n; + let hash = cell.hash().toString(); + if (visited.includes(hash)) { + // We should not account for current cell data if visited + return new StorageStats(); + } + else { + visited.push(hash); + } + for (let ref of cell.refs) { + let r = collectCellStats(ref, visited); + cells += r.cells; + bits += r.bits; + } + return new StorageStats(bits, cells); +} + +export function getGasPrices(configRaw: Cell, workchain: 0 | -1): GasPrices { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const ds = config.get(21 + workchain)!.beginParse(); + if (ds.loadUint(8) !== 0xd1) { + throw new Error("Invalid flat gas prices tag!"); + } + + const flat_gas_limit = ds.loadUintBig(64); + const flat_gas_price = ds.loadUintBig(64); + + if (ds.loadUint(8) !== 0xde) { + throw new Error("Invalid gas prices tag!"); + } + return { + flat_gas_limit, + flat_gas_price, + gas_price: ds.preloadUintBig(64) + }; +} + +export function setGasPrice(configRaw: Cell, prices: GasPrices, workchain: 0 | -1): Cell { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const idx = 21 + workchain; + const ds = config.get(idx)!; + const tail = ds.beginParse().skip(8 + 64 + 64 + 8 + 64); + + const newPrices = beginCell().storeUint(0xd1, 8) + .storeUint(prices.flat_gas_limit, 64) + .storeUint(prices.flat_gas_price, 64) + .storeUint(0xde, 8) + .storeUint(prices.gas_price, 64) + .storeSlice(tail) + .endCell(); + config.set(idx, newPrices); + + return beginCell().storeDictDirect(config).endCell(); +} + +export const storageValue: DictionaryValue = { + serialize: (src, builder) => { + builder.storeUint(0xcc, 8) + .storeUint(src.utime_sice, 32) + .storeUint(src.bit_price_ps, 64) + .storeUint(src.cell_price_ps, 64) + .storeUint(src.mc_bit_price_ps, 64) + .storeUint(src.mc_cell_price_ps, 64) + }, + parse: (src) => { + return { + utime_sice: src.skip(8).loadUint(32), + bit_price_ps: src.loadUintBig(64), + cell_price_ps: src.loadUintBig(64), + mc_bit_price_ps: src.loadUintBig(64), + mc_cell_price_ps: src.loadUintBig(64) + }; + } +}; + +export function getStoragePrices(configRaw: Cell) { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32), storageValue, config.get(18)!); + const values = storageData.values(); + + return values[values.length - 1]; +} +export function calcStorageFee(prices: StoragePrices, stats: StorageStats, duration: bigint) { + return shr16ceil((stats.bits * prices.bit_price_ps + stats.cells * prices.cell_price_ps) * duration) +} +export function setStoragePrices(configRaw: Cell, prices: StoragePrices) { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32), storageValue, config.get(18)!); + storageData.set(storageData.values().length - 1, prices); + config.set(18, beginCell().storeDictDirect(storageData).endCell()); + return beginCell().storeDictDirect(config).endCell(); +} + +export function computeGasFee(prices: GasPrices, gas: bigint): bigint { + if (gas <= prices.flat_gas_limit) { + return prices.flat_gas_price; + } + return prices.flat_gas_price + prices.gas_price * (gas - prices.flat_gas_limit) / 65536n +} + +export function computeDefaultForwardFee(msgPrices: MsgPrices) { + return msgPrices.lumpPrice - ((msgPrices.lumpPrice * msgPrices.firstFrac) >> BigInt(16)); +} + +export function computeCellForwardFees(msgPrices: MsgPrices, msg: Cell) { + let storageStats = collectCellStats(msg, [], true); + return computeFwdFees(msgPrices, storageStats.cells, storageStats.bits); +} +export function computeMessageForwardFees(msgPrices: MsgPrices, msg: Message) { + // let msg = loadMessageRelaxed(cell.beginParse()); + let storageStats = new StorageStats(); + + if (msg.info.type !== "internal") { + throw Error("Helper intended for internal messages"); + } + const defaultFwd = computeDefaultForwardFee(msgPrices); + // If message forward fee matches default than msg cell is flat + if (msg.info.forwardFee == defaultFwd) { + return { fees: { total: msgPrices.lumpPrice, res: msgPrices.lumpPrice - defaultFwd, remaining: defaultFwd }, stats: storageStats }; + } + let visited: Array = []; + // Init + if (msg.init) { + let addBits = 5n; // Minimal additional bits + let refCount = 0; + if (msg.init.splitDepth) { + addBits += 5n; + } + if (msg.init.libraries) { + refCount++; + storageStats = storageStats.add(collectCellStats(beginCell().storeDictDirect(msg.init.libraries).endCell(), visited, true)); + } + if (msg.init.code) { + refCount++; + storageStats = storageStats.add(collectCellStats(msg.init.code, visited)) + } + if (msg.init.data) { + refCount++; + storageStats = storageStats.add(collectCellStats(msg.init.data, visited)); + } + if (refCount >= 2) { //https://github.com/ton-blockchain/ton/blob/51baec48a02e5ba0106b0565410d2c2fd4665157/crypto/block/transaction.cpp#L2079 + storageStats.cells++; + storageStats.bits += addBits; + } + } + const lumpBits = BigInt(msg.body.bits.length); + const bodyStats = collectCellStats(msg.body, visited, true); + storageStats = storageStats.add(bodyStats); + + // NOTE: Extra currencies are ignored for now + let fees = computeFwdFeesVerbose(msgPrices, BigInt(storageStats.cells), BigInt(storageStats.bits)); + // Meeh + if (fees.remaining < msg.info.forwardFee) { + // console.log(`Remaining ${fees.remaining} < ${msg.info.forwardFee} lump bits:${lumpBits}`); + storageStats = storageStats.addCells(1).addBits(lumpBits); + fees = computeFwdFeesVerbose(msgPrices, storageStats.cells, storageStats.bits); + } + if (fees.remaining != msg.info.forwardFee) { + console.log("Result fees:", fees); + console.log(msg); + console.log(fees.remaining); + throw (new Error("Something went wrong in fee calcuation!")); + } + return { fees, stats: storageStats }; +} + +export const configParseMsgPrices = (sc: Slice) => { + + let magic = sc.loadUint(8); + + if (magic != 0xea) { + throw Error("Invalid message prices magic number!"); + } + return { + lumpPrice: sc.loadUintBig(64), + bitPrice: sc.loadUintBig(64), + cellPrice: sc.loadUintBig(64), + ihrPriceFactor: sc.loadUintBig(32), + firstFrac: sc.loadUintBig(16), + nextFrac: sc.loadUintBig(16) + }; +} + +export const setMsgPrices = (configRaw: Cell, prices: MsgPrices, workchain: 0 | -1) => { + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const priceCell = beginCell().storeUint(0xea, 8) + .storeUint(prices.lumpPrice, 64) + .storeUint(prices.bitPrice, 64) + .storeUint(prices.cellPrice, 64) + .storeUint(prices.ihrPriceFactor, 32) + .storeUint(prices.firstFrac, 16) + .storeUint(prices.nextFrac, 16) + .endCell(); + config.set(25 + workchain, priceCell); + + return beginCell().storeDictDirect(config).endCell(); +} + +export const getMsgPrices = (configRaw: Cell, workchain: 0 | -1) => { + + const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const prices = config.get(25 + workchain); + + if (prices === undefined) { + throw Error("No prices defined in config"); + } + + return configParseMsgPrices(prices.beginParse()); +} + +export function computeFwdFees(msgPrices: MsgPrices, cells: bigint, bits: bigint) { + return msgPrices.lumpPrice + (shr16ceil((msgPrices.bitPrice * bits) + + (msgPrices.cellPrice * cells)) + ); +} + +export function computeFwdFeesVerbose(msgPrices: MsgPrices, cells: bigint | number, bits: bigint | number) { + const fees = computeFwdFees(msgPrices, BigInt(cells), BigInt(bits)); + + const res = (fees * msgPrices.firstFrac) >> 16n; + return { + total: fees, + res, + remaining: fees - res + } +} \ No newline at end of file diff --git a/standard/wallets/comparison/transaction.cpp b/standard/wallets/comparison/transaction.cpp new file mode 100644 index 0000000..e8e06e4 --- /dev/null +++ b/standard/wallets/comparison/transaction.cpp @@ -0,0 +1,4253 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "block/transaction.h" +#include "block/block.h" +#include "block/block-parse.h" +#include "block/block-auto.h" +#include "crypto/openssl/rand.hpp" +#include "td/utils/bits.h" +#include "td/utils/uint128.h" +#include "ton/ton-shard.h" +#include "vm/vm.h" +#include "td/utils/Timer.h" + +namespace { +/** + * Logger that stores the tail of log messages. + * + * @param max_size The size of the buffer. Default is 256. + */ +class StringLoggerTail : public td::LogInterface { + public: + explicit StringLoggerTail(size_t max_size = 256) : buf(max_size, '\0') {} + + /** + * Appends a slice of data to the buffer. + * + * @param slice The slice of data to be appended. + */ + void append(td::CSlice slice) override { + if (slice.size() > buf.size()) { + slice.remove_prefix(slice.size() - buf.size()); + } + while (!slice.empty()) { + size_t s = std::min(buf.size() - pos, slice.size()); + std::copy(slice.begin(), slice.begin() + s, buf.begin() + pos); + pos += s; + if (pos == buf.size()) { + pos = 0; + truncated = true; + } + slice.remove_prefix(s); + } + } + + /** + * Retrieves the tail of the log. + * + * @returns The log as std::string. + */ + std::string get_log() const { + if (truncated) { + std::string res = buf; + std::rotate(res.begin(), res.begin() + pos, res.end()); + return res; + } else { + return buf.substr(0, pos); + } + } + + private: + std::string buf; + size_t pos = 0; + bool truncated = false; +}; +} + +namespace block { +using td::Ref; + +/** + * Looks up a library among public libraries. + * + * @param key A constant bit pointer representing the key of the library to lookup. + * + * @returns A reference to the library cell if found, null otherwise. + */ +Ref ComputePhaseConfig::lookup_library(td::ConstBitPtr key) const { + return libraries ? vm::lookup_library_in(key, libraries->get_root_cell()) : Ref{}; +} + +/* + * + * ACCOUNTS + * + */ + +/** + * Sets the address of the account. + * + * @param wc The workchain ID of the account. + * @param new_addr The new address of the account. + * + * @returns True if the address was successfully set, false otherwise. + */ +bool Account::set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr) { + workchain = wc; + addr = new_addr; + return true; +} + +/** + * Sets the length of anycast prefix length in the account address. + * + * @param new_length The new rewrite length. + * + * @returns True if the length was successfully set, False otherwise. + */ +bool Account::set_addr_rewrite_length(int new_length) { + if (new_length < 0 || new_length > 30) { + return false; // invalid value + } + if (addr_rewrite_length_set) { + return addr_rewrite_length == new_length; + } else { + addr_rewrite_length = (unsigned char)new_length; + addr_rewrite_length_set = true; + return true; + } +} + +/** + * Checks if the given addr rewrite length is valid for the Account. + * + * @param length The addr rewrite length to be checked. + * + * @returns True if the addr rewrite length is valid, False otherwise. + */ +bool Account::check_addr_rewrite_length(int length) const { + return addr_rewrite_length_set ? (length == addr_rewrite_length) : (length >= 0 && length <= 30); +} + +/** + * Parses anycast data of the account address. + * + * Initializes addr_rewrite. + * + * @param cs The cell slice containing partially-parsed account address. + * + * @returns True if parsing was successful, false otherwise. + */ +bool Account::parse_maybe_anycast(vm::CellSlice& cs) { + int t = (int)cs.fetch_ulong(1); + if (t < 0) { + return false; + } else if (!t) { + return set_addr_rewrite_length(0); + } + int depth; + return cs.fetch_uint_leq(30, depth) // anycast_info$_ depth:(#<= 30) + && depth // { depth >= 1 } + && cs.fetch_bits_to(addr_rewrite.bits(), depth) // rewrite_pfx:(bits depth) + && set_addr_rewrite_length(depth); +} + +/** + * Stores the anycast information to a serialized account address. + * + * @param cb The vm::CellBuilder object to store the information in. + * + * @returns True if the anycast information was successfully stored, false otherwise. + */ +bool Account::store_maybe_anycast(vm::CellBuilder& cb) const { + if (!addr_rewrite_length_set || !addr_rewrite_length) { + return cb.store_bool_bool(false); + } + return cb.store_bool_bool(true) // just$1 + && cb.store_uint_leq(30, addr_rewrite_length) // depth:(#<= 30) + && cb.store_bits_bool(addr_rewrite.cbits(), addr_rewrite_length); // rewrite_pfx:(bits depth) +} + +/** + * Unpacks the address from a given CellSlice. + * + * @param addr_cs The CellSlice containing the address. + * + * @returns True if the address was successfully unpacked, False otherwise. + */ +bool Account::unpack_address(vm::CellSlice& addr_cs) { + int addr_tag = block::gen::t_MsgAddressInt.get_tag(addr_cs); + int new_wc = ton::workchainInvalid; + switch (addr_tag) { + case block::gen::MsgAddressInt::addr_std: + if (!(addr_cs.advance(2) && parse_maybe_anycast(addr_cs) && addr_cs.fetch_int_to(8, new_wc) && + addr_cs.fetch_bits_to(addr_orig.bits(), 256) && addr_cs.empty_ext())) { + return false; + } + break; + case block::gen::MsgAddressInt::addr_var: + // cannot appear in masterchain / basechain + return false; + default: + return false; + } + addr_cs.clear(); + if (new_wc == ton::workchainInvalid) { + return false; + } + if (workchain == ton::workchainInvalid) { + workchain = new_wc; + addr = addr_orig; + addr.bits().copy_from(addr_rewrite.cbits(), addr_rewrite_length); + } else if (addr_rewrite_length) { + ton::StdSmcAddress new_addr = addr_orig; + new_addr.bits().copy_from(addr_rewrite.cbits(), addr_rewrite_length); + if (new_addr != addr) { + LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex() + << " : account header contains different address " << new_addr.to_hex() << " (with splitting depth " + << (int)addr_rewrite_length << ")"; + return false; + } + } else if (addr != addr_orig) { + LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex() + << " : account header contains different address " << addr_orig.to_hex(); + return false; + } + if (workchain != new_wc) { + LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex() + << " : account header contains different workchain " << new_wc; + return false; + } + addr_rewrite = addr.bits(); // initialize all 32 bits of addr_rewrite + if (!addr_rewrite_length) { + my_addr_exact = my_addr; + } + return true; +} + +/** + * Unpacks storage information from a CellSlice. + * + * Storage information is serialized using StorageInfo TLB-scheme. + * + * @param cs The CellSlice containing the storage information. + * + * @returns True if the unpacking is successful, false otherwise. + */ +bool Account::unpack_storage_info(vm::CellSlice& cs) { + block::gen::StorageInfo::Record info; + block::gen::StorageUsed::Record used; + if (!tlb::unpack_exact(cs, info) || !tlb::csr_unpack(info.used, used)) { + return false; + } + last_paid = info.last_paid; + if (info.storage_extra.write().fetch_long(3) == 1) { + info.storage_extra->prefetch_bits_to(storage_dict_hash.value_force()); + } else { + storage_dict_hash = {}; + } + orig_storage_dict_hash = storage_dict_hash; + if (info.due_payment->prefetch_ulong(1) == 1) { + vm::CellSlice& cs2 = info.due_payment.write(); + cs2.advance(1); + due_payment = block::tlb::t_Grams.as_integer_skip(cs2); + if (due_payment.is_null() || !cs2.empty_ext()) { + return false; + } + } else { + due_payment = td::zero_refint(); + } + unsigned long long u = 0; + u |= storage_used.cells = block::tlb::t_VarUInteger_7.as_uint(*used.cells); + u |= storage_used.bits = block::tlb::t_VarUInteger_7.as_uint(*used.bits); + LOG(DEBUG) << "last_paid=" << last_paid << "; cells=" << storage_used.cells << " bits=" << storage_used.bits; + return (u != std::numeric_limits::max()); +} + +/** + * Unpacks the state of an Account from a CellSlice. + * + * State is serialized using StateInit TLB-scheme. + * Initializes fixed_prefix_length (from account state - StateInit) + * + * @param cs The CellSlice containing the serialized state. + * + * @returns True if the state was successfully unpacked, False otherwise. + */ +bool Account::unpack_state(vm::CellSlice& cs) { + block::gen::StateInit::Record state; + if (!tlb::unpack_exact(cs, state)) { + return false; + } + fixed_prefix_length = 0; + if (state.fixed_prefix_length->size() == 6) { + fixed_prefix_length = (int)state.fixed_prefix_length->prefetch_ulong(6) - 32; + } + if (state.special->size() > 1) { + int z = (int)state.special->prefetch_ulong(3); + if (z < 0) { + return false; + } + tick = z & 2; + tock = z & 1; + LOG(DEBUG) << "tick=" << tick << ", tock=" << tock; + } + code = orig_code = state.code->prefetch_ref(); + data = orig_data = state.data->prefetch_ref(); + library = orig_library = state.library->prefetch_ref(); + return true; +} + +/** + * Computes the address of the account. + * + * @param force If set to true, the address will be recomputed even if it already exists. + * + * @returns True if the address was successfully computed, false otherwise. + */ +bool Account::compute_my_addr(bool force) { + if (!force && my_addr.not_null() && my_addr_exact.not_null()) { + return true; + } + if (workchain == ton::workchainInvalid) { + my_addr.clear(); + return false; + } + vm::CellBuilder cb; + Ref cell, cell2; + if (workchain >= -128 && workchain < 127) { + if (!(cb.store_long_bool(2, 2) // addr_std$10 + && store_maybe_anycast(cb) // anycast:(Maybe Anycast) + && cb.store_long_rchk_bool(workchain, 8) // workchain_id:int8 + && cb.store_bits_bool(addr_orig) // addr:bits256 + && cb.finalize_to(cell) && cb.store_long_bool(4, 3) // addr_std$10 anycast:(Maybe Anycast) + && cb.store_long_rchk_bool(workchain, 8) // workchain_id:int8 + && cb.store_bits_bool(addr) // addr:bits256 + && cb.finalize_to(cell2))) { + return false; + } + } else { + if (!(cb.store_long_bool(3, 2) // addr_var$11 + && store_maybe_anycast(cb) // anycast:(Maybe Anycast) + && cb.store_long_bool(256, 9) // addr_len:(## 9) + && cb.store_long_rchk_bool(workchain, 32) // workchain_id:int32 + && cb.store_bits_bool(addr_orig) // addr:(bits addr_len) + && cb.finalize_to(cell) && cb.store_long_bool(6, 3) // addr_var$11 anycast:(Maybe Anycast) + && cb.store_long_bool(256, 9) // addr_len:(## 9) + && cb.store_long_rchk_bool(workchain, 32) // workchain_id:int32 + && cb.store_bits_bool(addr) // addr:(bits addr_len) + && cb.finalize_to(cell2))) { + return false; + } + } + my_addr = load_cell_slice_ref(std::move(cell)); + my_addr_exact = load_cell_slice_ref(std::move(cell2)); + return true; +} + +/** + * Computes the address of the Account. + * + * Legacy (used only if global_version < 10). + * + * @param tmp_addr A reference to the CellSlice for the result. + * @param fixed_prefix_length The fixed prefix length for the address. + * @param orig_addr_rewrite Address prefix of length fixed_prefix_length. + * + * @returns True if the address was successfully computed, false otherwise. + */ +bool Account::recompute_tmp_addr(Ref& tmp_addr, int fixed_prefix_length, + td::ConstBitPtr orig_addr_rewrite) const { + if (!fixed_prefix_length && my_addr_exact.not_null()) { + tmp_addr = my_addr_exact; + return true; + } + if (fixed_prefix_length == addr_rewrite_length && my_addr.not_null()) { + tmp_addr = my_addr; + return true; + } + if (fixed_prefix_length < 0 || fixed_prefix_length > 30) { + return false; + } + vm::CellBuilder cb; + bool std = (workchain >= -128 && workchain < 128); + if (!cb.store_long_bool(std ? 2 : 3, 2)) { // addr_std$10 or addr_var$11 + return false; + } + if (!fixed_prefix_length) { + if (!cb.store_bool_bool(false)) { // anycast:(Maybe Anycast) + return false; + } + } else if (!(cb.store_bool_bool(true) // just$1 + && cb.store_long_bool(fixed_prefix_length, 5) // depth:(#<= 30) + && cb.store_bits_bool(addr.bits(), fixed_prefix_length))) { // rewrite_pfx:(bits depth) + return false; + } + if (std) { + if (!cb.store_long_rchk_bool(workchain, 8)) { // workchain:int8 + return false; + } + } else if (!(cb.store_long_bool(256, 9) // addr_len:(## 9) + && cb.store_long_bool(workchain, 32))) { // workchain:int32 + return false; + } + Ref cell; + return cb.store_bits_bool(orig_addr_rewrite, fixed_prefix_length) // address:(bits addr_len) or bits256 + && cb.store_bits_bool(addr.bits() + fixed_prefix_length, 256 - fixed_prefix_length) && cb.finalize_to(cell) && + (tmp_addr = vm::load_cell_slice_ref(std::move(cell))).not_null(); +} + +/** + * Sets address rewriting info for a newly-activated account. + * + * @param rewrite_length The fixed prefix length for the account address. + * @param orig_addr_rewrite Address prefix of length fixed_prefix_length. + * + * @returns True if the rewriting info was successfully set, false otherwise. + */ +bool Account::init_rewrite_addr(int rewrite_length, td::ConstBitPtr orig_addr_rewrite) { + if (addr_rewrite_length_set || !set_addr_rewrite_length(rewrite_length)) { + return false; + } + addr_orig = addr; + addr_rewrite = addr.bits(); + addr_orig.bits().copy_from(orig_addr_rewrite, rewrite_length); + return compute_my_addr(true); +} + +/** + * Unpacks the account information from the provided CellSlice. + * + * Used to unpack previously existing accounts. + * + * @param shard_account The ShardAccount to unpack. + * @param now The current Unix time. + * @param special Flag indicating if the account is special. + * + * @returns True if the unpacking is successful, false otherwise. + */ +bool Account::unpack(Ref shard_account, ton::UnixTime now, bool special) { + LOG(DEBUG) << "unpacking " << (special ? "special " : "") << "account " << addr.to_hex(); + if (shard_account.is_null()) { + LOG(ERROR) << "account " << addr.to_hex() << " does not have a valid ShardAccount to unpack"; + return false; + } + if (verbosity > 2) { + FLOG(INFO) { + shard_account->print_rec(sb, 2); + block::gen::t_ShardAccount.print(sb, shard_account); + }; + } + block::gen::ShardAccount::Record acc_info; + if (!(block::tlb::t_ShardAccount.validate_csr(shard_account) && tlb::unpack_exact(shard_account.write(), acc_info))) { + LOG(ERROR) << "account " << addr.to_hex() << " state is invalid"; + return false; + } + last_trans_lt_ = acc_info.last_trans_lt; + last_trans_hash_ = acc_info.last_trans_hash; + now_ = now; + auto account = std::move(acc_info.account); + total_state = orig_total_state = account; + auto acc_cs = load_cell_slice(std::move(account)); + if (block::gen::t_Account.get_tag(acc_cs) == block::gen::Account::account_none) { + is_special = special; + return acc_cs.size_ext() == 1 && init_new(now); + } + block::gen::Account::Record_account acc; + block::gen::AccountStorage::Record storage; + if (!(tlb::unpack_exact(acc_cs, acc) && (my_addr = acc.addr).not_null() && unpack_address(acc.addr.write()) && + compute_my_addr() && unpack_storage_info(acc.storage_stat.write()) && + tlb::csr_unpack(this->storage = std::move(acc.storage), storage) && + std::max(storage.last_trans_lt, 1ULL) > acc_info.last_trans_lt && balance.unpack(std::move(storage.balance)))) { + return false; + } + is_special = special; + last_trans_end_lt_ = storage.last_trans_lt; + switch (block::gen::t_AccountState.get_tag(*storage.state)) { + case block::gen::AccountState::account_uninit: + status = orig_status = acc_uninit; + state_hash = addr; + forget_addr_rewrite_length(); + break; + case block::gen::AccountState::account_frozen: + status = orig_status = acc_frozen; + if (!storage.state->have(2 + 256)) { + return false; + } + state_hash = storage.state->data_bits() + 2; + break; + case block::gen::AccountState::account_active: + status = orig_status = acc_active; + if (storage.state.write().fetch_ulong(1) != 1) { + return false; + } + inner_state = storage.state; + if (!unpack_state(storage.state.write())) { + return false; + } + state_hash.clear(); + break; + default: + return false; + } + LOG(DEBUG) << "end of Account.unpack() for " << workchain << ":" << addr.to_hex() + << " (balance = " << balance.to_str() << " ; last_trans_lt = " << last_trans_lt_ << ".." + << last_trans_end_lt_ << ")"; + return true; +} + +/** + * Initializes a new Account object. + * + * @param now The current Unix time. + * + * @returns True if the initialization is successful, false otherwise. + */ +bool Account::init_new(ton::UnixTime now) { + // only workchain and addr are initialized at this point + if (workchain == ton::workchainInvalid) { + return false; + } + addr_orig = addr; + addr_rewrite = addr.cbits(); + last_trans_lt_ = last_trans_end_lt_ = 0; + last_trans_hash_.set_zero(); + now_ = now; + last_paid = 0; + storage_used = {}; + orig_storage_dict_hash = storage_dict_hash = {}; + due_payment = td::zero_refint(); + balance.set_zero(); + if (my_addr_exact.is_null()) { + vm::CellBuilder cb; + if (workchain >= -128 && workchain < 128) { + CHECK(cb.store_long_bool(4, 3) // addr_std$10 anycast:(Maybe Anycast) + && cb.store_long_rchk_bool(workchain, 8) // workchain:int8 + && cb.store_bits_bool(addr)); // address:bits256 + } else { + CHECK(cb.store_long_bool(0xd00, 12) // addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + && cb.store_long_rchk_bool(workchain, 32) // workchain:int32 + && cb.store_bits_bool(addr)); // address:(bits addr_len) + } + my_addr_exact = load_cell_slice_ref(cb.finalize()); + } + if (my_addr.is_null()) { + my_addr = my_addr_exact; + } + if (total_state.is_null()) { + vm::CellBuilder cb; + CHECK(cb.store_long_bool(0, 1) // account_none$0 = Account + && cb.finalize_to(total_state)); + orig_total_state = total_state; + } + state_hash = addr_orig; + status = orig_status = acc_nonexist; + addr_rewrite_length_set = false; + return true; +} + +/** + * Removes extra currencies dict from AccountStorage. + * + * This is used for computing account storage stats. + * + * @param storage_cs AccountStorage as CellSlice. + * + * @returns AccountStorage without extra currencies as CellSlice. + */ +static td::Ref storage_without_extra_currencies(td::Ref storage_cs) { + block::gen::AccountStorage::Record rec; + if (!block::gen::csr_unpack(storage_cs, rec)) { + LOG(ERROR) << "failed to unpack AccountStorage"; + return {}; + } + if (rec.balance->size_refs() > 0) { + block::gen::CurrencyCollection::Record balance; + if (!block::gen::csr_unpack(rec.balance, balance)) { + LOG(ERROR) << "failed to unpack AccountStorage"; + return {}; + } + balance.other = vm::CellBuilder{}.store_zeroes(1).as_cellslice_ref(); + if (!block::gen::csr_pack(rec.balance, balance)) { + LOG(ERROR) << "failed to pack AccountStorage"; + return {}; + } + } + td::Ref result; + if (!block::gen::csr_pack(result, rec)) { + LOG(ERROR) << "failed to pack AccountStorage"; + return {}; + } + return result; +} + +/** + * Computes storage dict of the account from scratch. + * This requires storage_dict_hash to be set, as it guarantees that the stored storage_used was computed recently + * (in older versions it included extra currency balance, in newer versions it does not). + * + * @returns Root of the dictionary, or Error + */ +td::Result> Account::compute_account_storage_dict() const { + if (storage.is_null()) { + return td::Status::Error("cannot compute storage dict: empty storage"); + } + if (!storage_dict_hash) { + return td::Status::Error("cannot compute storage dict: storage_dict_hash is not set"); + } + AccountStorageStat stat; + auto storage_for_stat = storage_without_extra_currencies(storage); + if (storage_for_stat.is_null()) { + return td::Status::Error("cannot compute storage dict: invalid storage"); + } + TRY_STATUS(stat.replace_roots(storage_for_stat->prefetch_all_refs())); + // Root of AccountStorage is not counted in AccountStorageStat + td::uint64 expected_cells = stat.get_total_cells() + 1; + td::uint64 expected_bits = stat.get_total_bits() + storage->size(); + if (expected_cells != storage_used.cells || expected_bits != storage_used.bits) { + return td::Status::Error(PSTRING() << "invalid storage_used: computed cells=" << expected_cells + << " bits=" << expected_bits << ", found cells" << storage_used.cells + << " bits=" << storage_used.bits); + } + TRY_RESULT(root_hash, stat.get_dict_hash()); + if (storage_dict_hash.value() != root_hash) { + return td::Status::Error(PSTRING() << "invalid storage dict hash: computed " << root_hash.to_hex() << ", found " + << storage_dict_hash.value().to_hex()); + } + return stat.get_dict_root(); +} + +/** + * Initializes account_storage_stat of the account using the existing dict_root. + * This is not strictly necessary, as the storage stat is recomputed in Transaction. + * However, it can be used to optimize cell usage. + * This requires storage_dict_hash to be set, as it guarantees that the stored storage_used was computed recently + * (in older versions it included extra currency balance, in newer versions it does not). + * + * @param dict_root Root of the storage dictionary. + * + * @returns Status of the operation. + */ +td::Status Account::init_account_storage_stat(Ref dict_root) { + if (storage.is_null()) { + if (dict_root.not_null()) { + return td::Status::Error("storage is null, but dict_root is not null"); + } + account_storage_stat = {}; + return td::Status::OK(); + } + if (!storage_dict_hash) { + return td::Status::Error("cannot init storage dict: storage_dict_hash is not set"); + } + // Root of AccountStorage is not counted in AccountStorageStat + if (storage_used.cells < 1 || storage_used.bits < storage->size()) { + return td::Status::Error(PSTRING() << "storage_used is too small: cells=" << storage_used.cells + << " bits=" << storage_used.bits << " storage_root_bits=" << storage->size()); + } + AccountStorageStat new_stat(std::move(dict_root), storage->prefetch_all_refs(), storage_used.cells - 1, + storage_used.bits - storage->size()); + TRY_RESULT(root_hash, new_stat.get_dict_hash()); + if (storage_dict_hash.value() != root_hash) { + return td::Status::Error(PSTRING() << "invalid storage dict hash: computed " << root_hash.to_hex() << ", found " + << storage_dict_hash.value().to_hex()); + } + account_storage_stat = std::move(new_stat); + return td::Status::OK(); +} + +/** + * Resets the fixed prefix length of the account. + * + * @returns True if the fixed prefix length was successfully reset, false otherwise. + */ +bool Account::forget_addr_rewrite_length() { + addr_rewrite_length_set = false; + addr_rewrite_length = 0; + addr_orig = addr; + my_addr = my_addr_exact; + addr_rewrite = addr.bits(); + return true; +} + +/** + * Deactivates the account. + * + * @returns True if the account was successfully deactivated, false otherwise. + */ +bool Account::deactivate() { + if (status == acc_active) { + return false; + } + // forget special (tick/tock) info + tick = tock = false; + fixed_prefix_length = 0; + if (status == acc_nonexist || status == acc_uninit) { + // forget fixed prefix length and address rewriting info + forget_addr_rewrite_length(); + // forget specific state hash for deleted or uninitialized accounts (revert to addr) + state_hash = addr; + } + // forget code and data (only active accounts remember these) + code.clear(); + data.clear(); + library.clear(); + // if deleted, balance must be zero + if (status == acc_nonexist && !balance.is_zero()) { + return false; + } + return true; +} + +/** + * Checks if the account belongs to a specific shard. + * + * @param shard The shard to check against. + * + * @returns True if the account belongs to the shard, False otherwise. + */ +bool Account::belongs_to_shard(ton::ShardIdFull shard) const { + return workchain == shard.workchain && ton::shard_is_ancestor(shard.shard, addr); +} + +/** + * Adds the partial storage payment to the total sum. + * + * @param payment The total sum to be updated. + * @param delta The time delta for which the payment is calculated. + * @param prices The storage prices. + * @param storage_used Account storage statistics. + * @param is_mc A flag indicating whether the account is in the masterchain. + */ +void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, const block::StoragePrices& prices, + const StorageUsed& storage_used, bool is_mc) { + td::BigInt256 c{(long long)storage_used.cells}, b{(long long)storage_used.bits}; + if (is_mc) { + // storage.cells * prices.mc_cell_price + storage.bits * prices.mc_bit_price; + c.mul_short(prices.mc_cell_price); + b.mul_short(prices.mc_bit_price); + } else { + // storage.cells * prices.cell_price + storage.bits * prices.bit_price; + c.mul_short(prices.cell_price); + b.mul_short(prices.bit_price); + } + b += c; + b.mul_short(delta).normalize(); + CHECK(b.sgn() >= 0); + payment += b; +} + +/** + * Computes the storage fees based on the given parameters. + * + * @param now The current Unix time. + * @param pricing The vector of storage prices. + * @param storage_used Account storage statistics. + * @param last_paid The Unix time when the last payment was made. + * @param is_special A flag indicating if the account is special. + * @param is_masterchain A flag indicating if the account is in the masterchain. + * + * @returns The computed storage fees as RefInt256. + */ +td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector& pricing, + const StorageUsed& storage_used, ton::UnixTime last_paid, + bool is_special, bool is_masterchain) { + if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) { + return td::zero_refint(); + } + std::size_t n = pricing.size(), i = n; + while (i && pricing[i - 1].valid_since > last_paid) { + --i; + } + if (i) { + --i; + } + ton::UnixTime upto = std::max(last_paid, pricing[0].valid_since); + td::RefInt256 total{true, 0}; + for (; i < n && upto < now; i++) { + ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now); + if (upto < valid_until) { + assert(upto >= pricing[i].valid_since); + add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_used, is_masterchain); + } + upto = valid_until; + } + return td::rshift(total, 16, 1); // divide by 2^16 with ceil rounding to obtain nanograms +} + +/** + * Computes the storage fees for the account. + * + * @param now The current Unix time. + * @param pricing The vector of storage prices. + * + * @returns The computed storage fees as RefInt256. + */ +td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const { + return StoragePrices::compute_storage_fees(now, pricing, storage_used, last_paid, is_special, is_masterchain()); +} + +namespace transaction { +/** + * Constructs a new Transaction object. + * + * @param _account The Account object. + * @param ttype The type of the transaction (see transaction.cpp#309). + * @param req_start_lt The minimal logical time of the transaction. + * @param _now The current Unix time. + * @param _inmsg The input message that caused the transaction. + * + * @returns None + */ +Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, + Ref _inmsg) + : trans_type(ttype) + , is_first(_account.transactions.empty()) + , new_tick(_account.tick) + , new_tock(_account.tock) + , new_fixed_prefix_length(_account.fixed_prefix_length) + , now(_now) + , account(_account) + , my_addr(_account.my_addr) + , my_addr_exact(_account.my_addr_exact) + , balance(_account.balance) + , original_balance(_account.balance) + , due_payment(_account.due_payment) + , last_paid(_account.last_paid) + , new_code(_account.code) + , new_data(_account.data) + , new_library(_account.library) + , in_msg(std::move(_inmsg)) { + start_lt = std::max(req_start_lt, account.last_trans_end_lt_); + end_lt = start_lt + 1; + acc_status = (account.status == Account::acc_nonexist ? Account::acc_uninit : account.status); + if (acc_status == Account::acc_frozen) { + frozen_hash = account.state_hash; + } +} + +/** + * Unpacks the input message of a transaction. + * + * @param ihr_delivered A boolean indicating whether the message was delivered using IHR (Instant Hypercube Routing). + * @param cfg Action phase configuration. + * + * @returns A boolean indicating whether the unpacking was successful. + */ +bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* cfg) { + if (in_msg.is_null() || in_msg_type) { + return false; + } + if (verbosity > 2) { + FLOG(INFO) { + sb << "unpacking inbound message for a new transaction: "; + block::gen::t_Message_Any.print_ref(sb, in_msg); + load_cell_slice(in_msg).print_rec(sb); + }; + } + auto cs = vm::load_cell_slice(in_msg); + int tag = gen::t_CommonMsgInfo.get_tag(cs); + switch (tag) { + case gen::CommonMsgInfo::int_msg_info: { + if (!(tlb::unpack(cs, in_msg_info) && msg_balance_remaining.unpack(in_msg_info.value))) { + return false; + } + if (in_msg_info.ihr_disabled && ihr_delivered) { + return false; + } + bounce_enabled = in_msg_info.bounce; + in_msg_type = 1; + td::RefInt256 ihr_fee; + if (cfg->global_version >= 12) { + ihr_fee = td::zero_refint(); + td::RefInt256 extra_flags = tlb::t_Grams.as_integer(in_msg_info.extra_flags); + new_bounce_format = extra_flags->get_bit(0); + new_bounce_format_full_body = extra_flags->get_bit(1); + } else { + // Legacy: extra_flags was previously ihr_fee + ihr_fee = tlb::t_Grams.as_integer(in_msg_info.extra_flags); + } + if (ihr_delivered) { + in_fwd_fee = std::move(ihr_fee); + } else { + in_fwd_fee = td::zero_refint(); + msg_balance_remaining += std::move(ihr_fee); + } + if (in_msg_info.created_lt >= start_lt) { + start_lt = in_msg_info.created_lt + 1; + end_lt = start_lt + 1; + } + // ... + break; + } + case gen::CommonMsgInfo::ext_in_msg_info: { + gen::CommonMsgInfo::Record_ext_in_msg_info info; + if (!tlb::unpack(cs, info)) { + return false; + } + in_msg_info.ihr_disabled = in_msg_info.bounce = in_msg_info.bounced = false; + in_msg_info.src = info.src; + in_msg_info.dest = info.dest; + in_msg_info.created_at = in_msg_info.created_lt = 0; + if (cfg->disable_anycast) { + // Check that dest is addr_std without anycast + gen::MsgAddressInt::Record_addr_std rec; + if (!gen::csr_unpack(info.dest, rec)) { + LOG(DEBUG) << "destination address of the external message is not a valid addr_std"; + return false; + } + if (rec.anycast->size() > 1) { + LOG(DEBUG) << "destination address of the external message is an anycast address"; + return false; + } + } + in_msg_type = 2; + in_msg_extern = true; + // compute forwarding fees for this external message + vm::CellStorageStat sstat; // for message size + auto cell_info = sstat.compute_used_storage(cs).move_as_ok(); // message body + sstat.bits -= cs.size(); // bits in the root cells are free + sstat.cells--; // the root cell itself is not counted as a cell + LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; + if (sstat.bits > cfg->size_limits.max_msg_bits || sstat.cells > cfg->size_limits.max_msg_cells) { + LOG(DEBUG) << "inbound external message too large, invalid"; + return false; + } + if (cell_info.max_merkle_depth > max_allowed_merkle_depth) { + LOG(DEBUG) << "inbound external message has too big merkle depth, invalid"; + return false; + } + // fetch message pricing info + CHECK(cfg); + const MsgPrices& msg_prices = cfg->fetch_msg_prices(account.is_masterchain()); + // compute forwarding fees + auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, true); + LOG(DEBUG) << "computed fwd fees = " << fees_c.first << " + " << fees_c.second; + + if (account.is_special) { + LOG(DEBUG) << "computed fwd fees set to zero for special account"; + fees_c.first = fees_c.second = 0; + } + in_fwd_fee = td::make_refint(fees_c.first); + if (balance.grams < in_fwd_fee) { + LOG(DEBUG) << "cannot pay for importing this external message"; + return false; + } + // (tentatively) debit account for importing this external message + balance -= in_fwd_fee; + msg_balance_remaining.set_zero(); // external messages cannot carry value + // ... + break; + } + default: + return false; + } + // init:(Maybe (Either StateInit ^StateInit)) + switch ((int)cs.prefetch_ulong(2)) { + case 2: { // (just$1 (left$0 _:StateInit )) + Ref state_init; + vm::CellBuilder cb; + if (!(cs.advance(2) && block::gen::t_StateInit.fetch_to(cs, state_init) && + cb.append_cellslice_bool(std::move(state_init)) && cb.finalize_to(in_msg_state) && + block::gen::t_StateInitWithLibs.validate_ref(in_msg_state))) { + LOG(DEBUG) << "cannot parse StateInit in inbound message"; + return false; + } + break; + } + case 3: { // (just$1 (right$1 _:^StateInit )) + if (!(cs.advance(2) && cs.fetch_ref_to(in_msg_state) && + block::gen::t_StateInitWithLibs.validate_ref(in_msg_state))) { + LOG(DEBUG) << "cannot parse ^StateInit in inbound message"; + return false; + } + break; + } + default: // nothing$0 + if (!cs.advance(1)) { + LOG(DEBUG) << "invalid init field in an inbound message"; + return false; + } + } + // body:(Either X ^X) + switch ((int)cs.fetch_ulong(1)) { + case 0: // left$0 _:X + in_msg_body = Ref{true, cs}; + break; + case 1: // right$1 _:^X + if (cs.size_ext() != 0x10000) { + LOG(DEBUG) << "body of an inbound message is not represented by exactly one reference"; + return false; + } + in_msg_body = load_cell_slice_ref(cs.prefetch_ref()); + break; + default: + LOG(DEBUG) << "invalid body field in an inbound message"; + return false; + } + total_fees += in_fwd_fee; + if (account.workchain == ton::masterchainId && cfg->mc_blackhole_addr && + cfg->mc_blackhole_addr.value() == account.addr) { + blackhole_burned.grams = msg_balance_remaining.grams; + msg_balance_remaining.grams = td::zero_refint(); + LOG(DEBUG) << "Burning " << blackhole_burned.grams << " nanoton (blackhole address)"; + } + return true; +} + +/** + * Prepares the storage phase of a transaction. + * + * @param cfg The configuration for the storage phase. + * @param force_collect Flag indicating whether to collect fees for frozen accounts. + * @param adjust_msg_value Flag indicating whether to adjust the message value if the account balance becomes less than the message balance. + * + * @returns True if the storage phase was successfully prepared, false otherwise. + */ +bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect, bool adjust_msg_value) { + if (now < account.last_paid) { + return false; + } + auto to_pay = account.compute_storage_fees(now, *(cfg.pricing)) + due_payment; + if (to_pay.not_null() && sgn(to_pay) < 0) { + return false; + } + auto res = std::make_unique(); + res->is_special = account.is_special; + last_paid = res->last_paid_updated = (res->is_special ? 0 : now); + if (to_pay.is_null() || sgn(to_pay) == 0) { + res->fees_collected = res->fees_due = td::zero_refint(); + } else if (to_pay <= balance.grams) { + res->fees_collected = to_pay; + res->fees_due = td::zero_refint(); + balance -= std::move(to_pay); + if (cfg.global_version >= 7) { + due_payment = td::zero_refint(); + } + } else if (acc_status == Account::acc_frozen && !force_collect && to_pay < cfg.delete_due_limit) { + // do not collect fee + res->last_paid_updated = (res->is_special ? 0 : account.last_paid); + res->fees_collected = res->fees_due = td::zero_refint(); + } else { + res->fees_collected = balance.grams; + res->fees_due = std::move(to_pay) - std::move(balance.grams); + balance.grams = td::zero_refint(); + if (!res->is_special) { + auto total_due = res->fees_due; + switch (acc_status) { + case Account::acc_uninit: + case Account::acc_frozen: + if (total_due > cfg.delete_due_limit && balance.extra.is_null()) { + // Keeping accounts with non-null extras is a temporary measure before implementing proper collection of + // extracurrencies from deleted accounts + res->deleted = true; + acc_status = Account::acc_deleted; + if (balance.extra.not_null()) { + // collect extra currencies as a fee + total_fees += block::CurrencyCollection{0, std::move(balance.extra)}; + balance.extra.clear(); + } + } + break; + case Account::acc_active: + if (total_due > cfg.freeze_due_limit) { + res->frozen = true; + was_frozen = true; + acc_status = Account::acc_frozen; + } + break; + } + if (cfg.enable_due_payment) { + due_payment = total_due; + } + } + } + if (adjust_msg_value && msg_balance_remaining.grams > balance.grams) { + msg_balance_remaining.grams = balance.grams; + } + total_fees += res->fees_collected; + storage_phase = std::move(res); + return true; +} + +/** + * Prepares the credit phase of a transaction. + * + * This function creates a CreditPhase object and performs the necessary calculations + * to determine the amount to be credited in the credit phase. It updates the due payment, + * credit, balance, and total fees accordingly. + * + * @returns True if the credit phase is prepared successfully, false otherwise. + */ +bool Transaction::prepare_credit_phase() { + credit_phase = std::make_unique(); + // Due payment is only collected in storage phase. + // For messages with bounce flag, contract always receives the amount specified in message + // auto collected = std::min(msg_balance_remaining.grams, due_payment); + // credit_phase->due_fees_collected = collected; + // due_payment -= collected; + // credit_phase->credit = msg_balance_remaining -= collected; + credit_phase->due_fees_collected = td::zero_refint(); + credit_phase->credit = msg_balance_remaining; + if (!msg_balance_remaining.is_valid()) { + LOG(ERROR) << "cannot compute the amount to be credited in the credit phase of transaction"; + return false; + } + // NB: msg_balance_remaining may be deducted from balance later during bounce phase + balance += msg_balance_remaining; + if (!balance.is_valid()) { + LOG(ERROR) << "cannot credit currency collection to account"; + return false; + } + // total_fees += std::move(collected); + return true; +} +} // namespace transaction + +/** + * Parses the gas limits and prices from a given cell. + * + * @param cell The cell containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. + * @param freeze_due_limit Reference to store the freeze due limit. + * @param delete_due_limit Reference to store the delete due limit. + * + * @returns True if the parsing is successful, false otherwise. + */ +bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit) { + return cell.not_null() && + parse_GasLimitsPrices(vm::load_cell_slice_ref(std::move(cell)), freeze_due_limit, delete_due_limit); +} + +/** + * Parses the gas limits and prices from a given cell slice. + * + * @param cs The cell slice containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. + * @param freeze_due_limit Reference to store the freeze due limit. + * @param delete_due_limit Reference to store the delete due limit. + * + * @returns True if the parsing is successful, false otherwise. + */ +bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit) { + if (cs.is_null()) { + return false; + } + block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; + if (tlb::csr_unpack(cs, flat)) { + return parse_GasLimitsPrices_internal(std::move(flat.other), freeze_due_limit, delete_due_limit, + flat.flat_gas_limit, flat.flat_gas_price); + } else { + return parse_GasLimitsPrices_internal(std::move(cs), freeze_due_limit, delete_due_limit); + } +} + +/** + * Parses the gas limits and prices from a gas limits and prices record. + * + * @param cs The cell slice containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. + * @param freeze_due_limit A reference to store the freeze due limit. + * @param delete_due_limit A reference to store the delete due limit. + * @param _flat_gas_limit The flat gas limit. + * @param _flat_gas_price The flat gas price. + * + * @returns True if the parsing is successful, false otherwise. + */ +bool ComputePhaseConfig::parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit, td::uint64 _flat_gas_limit, + td::uint64 _flat_gas_price) { + auto f = [&](const auto& r, td::uint64 spec_limit) { + gas_limit = r.gas_limit; + special_gas_limit = spec_limit; + gas_credit = r.gas_credit; + gas_price = r.gas_price; + freeze_due_limit = td::make_refint(r.freeze_due_limit); + delete_due_limit = td::make_refint(r.delete_due_limit); + }; + block::gen::GasLimitsPrices::Record_gas_prices_ext rec; + if (tlb::csr_unpack(cs, rec)) { + f(rec, rec.special_gas_limit); + } else { + block::gen::GasLimitsPrices::Record_gas_prices rec0; + if (tlb::csr_unpack(std::move(cs), rec0)) { + f(rec0, rec0.gas_limit); + } else { + return false; + } + } + flat_gas_limit = _flat_gas_limit; + flat_gas_price = _flat_gas_price; + compute_threshold(); + return true; +} + +/** + * Checks if an address is suspended according to the ConfigParam(44). + * + * @param wc The workchain ID. + * @param addr The account address address. + * + * @returns True if the address is suspended, False otherwise. + */ +bool ComputePhaseConfig::is_address_suspended(ton::WorkchainId wc, td::Bits256 addr) const { + if (!suspended_addresses) { + return false; + } + try { + vm::CellBuilder key; + key.store_long_bool(wc, 32); + key.store_bits_bool(addr); + return !suspended_addresses->lookup(key.data_bits(), 288).is_null(); + } catch (vm::VmError) { + return false; + } +} + +/** + * Computes the maximum gas fee based on the gas prices and limits. + * + * @param gas_price256 The gas price from config as RefInt256 + * @param gas_limit The gas limit from config + * @param flat_gas_limit The flat gas limit from config + * @param flat_gas_price The flat gas price from config + * + * @returns The maximum gas fee. + */ +static td::RefInt256 compute_max_gas_threshold(const td::RefInt256& gas_price256, td::uint64 gas_limit, + td::uint64 flat_gas_limit, td::uint64 flat_gas_price) { + if (gas_limit > flat_gas_limit) { + return td::rshift(gas_price256 * (gas_limit - flat_gas_limit), 16, 1) + td::make_bigint(flat_gas_price); + } else { + return td::make_refint(flat_gas_price); + } +} + +/** + * Computes the maximum for gas fee based on the gas prices and limits. + * + * Updates max_gas_threshold. + */ +void ComputePhaseConfig::compute_threshold() { + gas_price256 = td::make_refint(gas_price); + max_gas_threshold = compute_max_gas_threshold(gas_price256, gas_limit, flat_gas_limit, flat_gas_price); +} + +/** + * Computes the amount of gas that can be bought for a given amount of nanograms. + * + * @param nanograms The amount of nanograms to compute gas for. + * + * @returns The amount of gas. + */ +td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const { + if (nanograms.is_null() || sgn(nanograms) < 0) { + return 0; + } + if (nanograms >= max_gas_threshold) { + return gas_limit; + } + if (nanograms < flat_gas_price) { + return 0; + } + auto res = td::div((std::move(nanograms) - flat_gas_price) << 16, gas_price256); + return res->to_long() + flat_gas_limit; +} + +/** + * Computes the gas price. + * + * @param gas_used The amount of gas used. + * + * @returns The computed gas price. + */ +td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const { + return gas_used <= flat_gas_limit ? td::make_refint(flat_gas_price) + : td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price; +} + +namespace transaction { + +/** + * Checks if it is required to increase gas_limit (from GasLimitsPrices config) for the transaction + * + * In January 2024 a highload wallet of @wallet Telegram bot in mainnet was stuck because current gas limit (1M) is + * not enough to clean up old queries, thus locking funds inside. + * See comment in crypto/smartcont/highload-wallet-v2-code.fc for details on why this happened. + * Account address: EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu + * It was proposed to validators to increase gas limit for this account to 70M for a limited amount + * of time (until 2024-02-29). + * It is activated by setting global version to 5 in ConfigParam 8. + * This config change also activates new behavior for special accounts in masterchain. + * + * In August 2024 it was decided to unlock other old highload wallets that got into the same situation. + * See https://t.me/tondev_news/129 + * It is activated by setting global version to 9. + * + * @param cfg The compute phase configuration. + * @param now The Unix time of the transaction. + * @param account The account of the transaction. + * + * @returns Overridden gas limit or empty td::optional + */ +static td::optional override_gas_limit(const ComputePhaseConfig& cfg, ton::UnixTime now, + const Account& account) { + struct OverridenGasLimit { + td::uint64 new_limit; + int from_version; + ton::UnixTime until; + }; + static std::map, OverridenGasLimit> accounts = []() { + auto parse_addr = [](const char* s) -> std::pair { + auto r_addr = StdAddress::parse(td::Slice(s)); + r_addr.ensure(); + return {r_addr.ok().workchain, r_addr.ok().addr}; + }; + std::map, OverridenGasLimit> accounts; + + // Increase limit for EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu until 2024-02-29 00:00:00 UTC + accounts[parse_addr("0:FFBFD8F5AE5B2E1C7C3614885CB02145483DFAEE575F0DD08A72C366369211CD")] = { + .new_limit = 70'000'000, .from_version = 5, .until = 1709164800}; + + // Increase limit for multiple accounts (https://t.me/tondev_news/129) until 2025-03-01 00:00:00 UTC + accounts[parse_addr("UQBeSl-dumOHieZ3DJkNKVkjeso7wZ0VpzR4LCbLGTQ8xr57")] = { + .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; + accounts[parse_addr("EQC3VcQ-43klww9UfimR58TBjBzk7GPupXQ3CNuthoNp-uTR")] = { + .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; + accounts[parse_addr("EQBhwBb8jvokGvfreHRRoeVxI237PrOJgyrsAhLA-4rBC_H5")] = { + .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; + accounts[parse_addr("EQCkoRp4OE-SFUoMEnYfL3vF43T3AzNfW8jyTC4yzk8cJqMS")] = { + .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; + accounts[parse_addr("UQBN5ICras79U8FYEm71ws34n-ZNIQ0LRNpckOUsIV3OebnC")] = { + .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; + accounts[parse_addr("EQBDanbCeUqI4_v-xrnAN0_I2wRvEIaLg1Qg2ZN5c6Zl1KOh")] = { + .new_limit = 225'000'000, .from_version = 9, .until = 1740787200}; + return accounts; + }(); + auto it = accounts.find({account.workchain, account.addr}); + if (it == accounts.end() || cfg.global_version < it->second.from_version || now >= it->second.until) { + return {}; + } + return it->second.new_limit; +} + +/** + * Computes the amount of gas that can be bought for a given amount of nanograms. + * Usually equal to `cfg.gas_bought_for(nanograms)` + * However, it overrides gas_limit from config in special cases. + * + * @param cfg The compute phase configuration. + * @param nanograms The amount of nanograms to compute gas for. + * + * @returns The amount of gas. + */ +td::uint64 Transaction::gas_bought_for(const ComputePhaseConfig& cfg, td::RefInt256 nanograms) { + if (auto new_limit = override_gas_limit(cfg, now, account)) { + gas_limit_overridden = true; + // Same as ComputePhaseConfig::gas_bought for, but with other gas_limit and max_gas_threshold + auto gas_limit = new_limit.value(); + LOG(INFO) << "overridding gas limit for account " << account.workchain << ":" << account.addr.to_hex() << " to " + << gas_limit; + auto max_gas_threshold = + compute_max_gas_threshold(cfg.gas_price256, gas_limit, cfg.flat_gas_limit, cfg.flat_gas_price); + if (nanograms.is_null() || sgn(nanograms) < 0) { + return 0; + } + if (nanograms >= max_gas_threshold) { + return gas_limit; + } + if (nanograms < cfg.flat_gas_price) { + return 0; + } + auto res = td::div((std::move(nanograms) - cfg.flat_gas_price) << 16, cfg.gas_price256); + return res->to_long() + cfg.flat_gas_limit; + } + return cfg.gas_bought_for(nanograms); +} + +/** + * Computes the gas limits for a transaction. + * + * @param cp The ComputePhase object to store the computed gas limits. + * @param cfg The compute phase configuration. + * + * @returns True if the gas limits were successfully computed, false otherwise. + */ +bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) { + // Compute gas limits + if (account.is_special) { + cp.gas_max = cfg.special_gas_limit; + } else { + cp.gas_max = gas_bought_for(cfg, balance.grams); + } + if (trans_type != tr_ord || (account.is_special && cfg.special_gas_full)) { + // may use all gas that can be bought using remaining balance + cp.gas_limit = cp.gas_max; + } else { + // originally use only gas bought using remaining message balance + // if the message is "accepted" by the smart contract, the gas limit will be set to gas_max + cp.gas_limit = std::min(gas_bought_for(cfg, msg_balance_remaining.grams), cp.gas_max); + } + if (trans_type == tr_ord && !block::tlb::t_Message.is_internal(in_msg)) { + // external messages carry no balance, give them some credit to check whether they are accepted + cp.gas_credit = std::min(cfg.gas_credit, cp.gas_max); + } else { + cp.gas_credit = 0; + } + LOG(DEBUG) << "gas limits: max=" << cp.gas_max << ", limit=" << cp.gas_limit << ", credit=" << cp.gas_credit; + return true; +} + +/** + * Prepares a TVM stack for a transaction. + * + * @param cp The compute phase object. + * + * @returns A reference to the prepared virtual machine stack. + * Returns an empty reference if the transaction type is invalid. + */ +Ref Transaction::prepare_vm_stack(ComputePhase& cp) { + Ref stack_ref{true}; + td::RefInt256 acc_addr{true}; + CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256)); + vm::Stack& stack = stack_ref.write(); + switch (trans_type) { + case tr_tick: + case tr_tock: + stack.push_int(balance.grams); + stack.push_int(std::move(acc_addr)); + stack.push_bool(trans_type == tr_tock); + stack.push_smallint(-2); + return stack_ref; + case tr_ord: + stack.push_int(balance.grams); + stack.push_int(msg_balance_remaining.grams); + stack.push_cell(in_msg); + stack.push_cellslice(in_msg_body); + stack.push_bool(in_msg_extern); + return stack_ref; + default: + LOG(ERROR) << "cannot initialize stack for a transaction of type " << trans_type; + return {}; + } +} + +/** + * Prepares a random seed for a transaction. + * + * @param rand_seed The output random seed. + * @param cfg The configuration for the compute phase. + * + * @returns True if the random seed was successfully prepared, false otherwise. + */ +bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const { + // we might use SHA256(block_rand_seed . addr . trans_lt) + // instead, we use SHA256(block_rand_seed . addr) + // if the smart contract wants to randomize further, it can use RANDOMIZE instruction + td::BitArray<256 + 256> data; + data.bits().copy_from(cfg.block_rand_seed.cbits(), 256); + if (cfg.global_version >= 8) { + (data.bits() + 256).copy_from(account.addr.cbits(), 256); + } else { + (data.bits() + 256).copy_from(account.addr_rewrite.cbits(), 256); + } + rand_seed.clear(); + data.compute_sha256(rand_seed); + return true; +} + +/** + * Prepares the c7 tuple (virtual machine context) for a compute phase of a transaction. + * + * @param cfg The configuration for the compute phase. + * + * @returns A reference to a Tuple object. + * + * @throws CollatorError if the rand_seed cannot be computed for the transaction. + */ +Ref Transaction::prepare_vm_c7(const ComputePhaseConfig& cfg) const { + td::BitArray<256> rand_seed; + td::RefInt256 rand_seed_int{true}; + if (!(prepare_rand_seed(rand_seed, cfg) && rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false))) { + LOG(ERROR) << "cannot compute rand_seed for transaction"; + throw CollatorError{"cannot generate valid SmartContractInfo"}; + return {}; + } + std::vector tuple = { + td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea + td::zero_refint(), // actions:Integer + td::zero_refint(), // msgs_sent:Integer + td::make_refint(now), // unixtime:Integer + td::make_refint(account.block_lt), // block_lt:Integer + td::make_refint(start_lt), // trans_lt:Integer + std::move(rand_seed_int), // rand_seed:Integer + balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] + my_addr, // myself:MsgAddressInt + vm::StackEntry::maybe(cfg.global_config) // global_config:(Maybe Cell) ] = SmartContractInfo; + }; + if (cfg.global_version >= 4) { + tuple.push_back(vm::StackEntry::maybe(new_code)); // code:Cell + if (msg_balance_remaining.is_valid()) { + tuple.push_back(msg_balance_remaining.as_vm_tuple()); // in_msg_value:[Integer (Maybe Cell)] + } else { + tuple.push_back(block::CurrencyCollection::zero().as_vm_tuple()); + } + tuple.push_back(storage_phase->fees_collected); // storage_fees:Integer + + // See crypto/block/mc-config.cpp#2223 (get_prev_blocks_info) + // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId; + // [ last_mc_blocks:[BlockId...] + // prev_key_block:BlockId + // last_mc_blocks_100:[BlockId...] ] : PrevBlocksInfo + // The only context where PrevBlocksInfo (13 parameter of c7) is null is inside emulator + // where it need to be set via transaction_emulator_set_prev_blocks_info (see emulator/emulator-extern.cpp) + // Inside validator, collator and liteserver checking external message contexts + // prev_blocks_info is always not null, since get_prev_blocks_info() + // may only return tuple or raise Error (See crypto/block/mc-config.cpp#2223) + tuple.push_back(vm::StackEntry::maybe(cfg.prev_blocks_info)); + } + if (cfg.global_version >= 6) { + tuple.push_back(vm::StackEntry::maybe(cfg.unpacked_config_tuple)); // unpacked_config_tuple:[...] + tuple.push_back(due_payment.not_null() ? due_payment : td::zero_refint()); // due_payment:Integer + tuple.push_back(compute_phase->precompiled_gas_usage + ? vm::StackEntry(td::make_refint(compute_phase->precompiled_gas_usage.value())) + : vm::StackEntry()); // precompiled_gas_usage:Integer + } + if (cfg.global_version >= 11) { + // in_msg_params:[...] + tuple.push_back(prepare_in_msg_params_tuple(trans_type == tr_ord ? &in_msg_info : nullptr, in_msg_state, + msg_balance_remaining)); + } + auto tuple_ref = td::make_cnt_ref>(std::move(tuple)); + LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple_ref).to_string(); + return vm::make_tuple_ref(std::move(tuple_ref)); +} + +/** + * Prepares tuple with unpacked parameters of the inbound message (for the 17th element of c7). + * `info` is: + * - For internal messages - just int_msg_info of the message + * - For external messages - artificial int_msg_info based on ext_msg_info of the messages. + * - For tick-tock transactions and get methods - nullptr. + * + * @param info Pointer to the message info. + * @param state_init State init of the message (null if absent). + * @param msg_balance_remaining Remaining balance of the message (it's sometimes different from value in info). + * + * @returns Tuple with message parameters. + */ +Ref Transaction::prepare_in_msg_params_tuple(const gen::CommonMsgInfo::Record_int_msg_info* info, + const Ref& state_init, + const CurrencyCollection& msg_balance_remaining) { + std::vector in_msg_params(10); + if (info != nullptr) { + in_msg_params[0] = td::make_refint(info->bounce ? -1 : 0); // bounce + in_msg_params[1] = td::make_refint(info->bounced ? -1 : 0); // bounced + in_msg_params[2] = info->src; // src_addr + in_msg_params[3] = info->fwd_fee.is_null() ? td::zero_refint() : tlb::t_Grams.as_integer(info->fwd_fee); // fwd_fee + in_msg_params[4] = td::make_refint(info->created_lt); // created_lt + in_msg_params[5] = td::make_refint(info->created_at); // created_at + auto value = info->value; + in_msg_params[6] = + info->value.is_null() ? td::zero_refint() : tlb::t_Grams.as_integer_skip(value.write()); // original value + in_msg_params[7] = msg_balance_remaining.is_valid() ? msg_balance_remaining.grams : td::zero_refint(); // value + in_msg_params[8] = msg_balance_remaining.is_valid() ? vm::StackEntry::maybe(msg_balance_remaining.extra) + : vm::StackEntry{}; // value extra + in_msg_params[9] = vm::StackEntry::maybe(state_init); // state_init + } else { + in_msg_params[0] = td::zero_refint(); // bounce + in_msg_params[1] = td::zero_refint(); // bounced + static Ref addr_none = vm::CellBuilder{}.store_zeroes(2).as_cellslice_ref(); + in_msg_params[2] = addr_none; // src_addr + in_msg_params[3] = td::zero_refint(); // fed_fee + in_msg_params[4] = td::zero_refint(); // created_lt + in_msg_params[5] = td::zero_refint(); // created_at + in_msg_params[6] = td::zero_refint(); // original value + in_msg_params[7] = td::zero_refint(); // value + in_msg_params[8] = vm::StackEntry{}; // value extra + in_msg_params[9] = vm::StackEntry{}; // state_init + } + return td::make_cnt_ref>(std::move(in_msg_params)); +} + +/** + * Computes the number of output actions in a list. + * + * @param list c5 cell. + * + * @returns The number of output actions. + */ +int output_actions_count(Ref list) { + int i = -1; + do { + ++i; + bool special = true; + auto cs = load_cell_slice_special(std::move(list), special); + if (special) { + break; + } + list = cs.prefetch_ref(); + } while (list.not_null()); + return i; +} + +/** + * Unpacks the message StateInit. + * + * @param cfg The configuration for the compute phase. + * @param lib_only If true, only unpack libraries from the state. + * @param forbid_public_libs Don't allow public libraries in initstate. + * + * @returns True if the unpacking is successful, false otherwise. + */ +bool Transaction::unpack_msg_state(const ComputePhaseConfig& cfg, bool lib_only, bool forbid_public_libs) { + block::gen::StateInit::Record state; + if (in_msg_state.is_null() || !tlb::unpack_cell(in_msg_state, state)) { + LOG(ERROR) << "cannot unpack StateInit from an inbound message"; + return false; + } + if (lib_only) { + in_msg_library = state.library->prefetch_ref(); + return true; + } + if (state.fixed_prefix_length->size() == 6) { + new_fixed_prefix_length = (signed char)(state.fixed_prefix_length->prefetch_ulong(6) - 32); + } else { + new_fixed_prefix_length = 0; + } + if (!cfg.disable_anycast) { + new_addr_rewrite_length = new_fixed_prefix_length; + } + if (state.special->size() > 1) { + int z = (int)state.special->prefetch_ulong(3); + if (z < 0) { + return false; + } + new_tick = z & 2; + new_tock = z & 1; + LOG(DEBUG) << "tick=" << new_tick << ", tock=" << new_tock; + } + td::Ref old_code = new_code, old_data = new_data, old_library = new_library; + new_code = state.code->prefetch_ref(); + new_data = state.data->prefetch_ref(); + new_library = state.library->prefetch_ref(); + auto size_limits = cfg.size_limits; + if (forbid_public_libs) { + size_limits.max_acc_public_libraries = 0; + } + auto S = check_state_limits(size_limits, cfg.global_version, false); + if (S.is_error()) { + LOG(DEBUG) << "Cannot unpack msg state: " << S.move_as_error(); + new_code = old_code; + new_data = old_data; + new_library = old_library; + return false; + } + return true; +} + +/** + * Computes the set of libraries to be used during TVM execution. + * + * @param cfg The configuration for the compute phase. + * + * @returns A vector of hashmaps with libraries. + */ +std::vector> Transaction::compute_vm_libraries(const ComputePhaseConfig& cfg) { + std::vector> lib_set; + if (in_msg_library.not_null()) { + lib_set.push_back(in_msg_library); + } + if (new_library.not_null()) { + lib_set.push_back(new_library); + } + auto global_libs = cfg.get_lib_root(); + if (global_libs.not_null()) { + lib_set.push_back(std::move(global_libs)); + } + return lib_set; +} + +/** + * Checks if the input message StateInit hash corresponds to the account address. + * + * @param cfg The configuration for the compute phase. + * + * @returns True if the input message state hash is valid, False otherwise. + */ +bool Transaction::check_in_msg_state_hash(const ComputePhaseConfig& cfg) { + CHECK(in_msg_state.not_null()); + CHECK(new_fixed_prefix_length >= 0 && new_fixed_prefix_length < 32); + td::Bits256 in_state_hash = in_msg_state->get_hash().bits(); + int d = new_fixed_prefix_length; + if ((in_state_hash.bits() + d).compare(account.addr.bits() + d, 256 - d)) { + return false; + } + orig_addr_rewrite = in_state_hash.bits(); + orig_addr_rewrite_set = true; + if (cfg.disable_anycast) { + my_addr = my_addr_exact; + return true; + } else { + return account.recompute_tmp_addr(my_addr, d, orig_addr_rewrite.bits()); + } +} + +/** + * Runs the precompiled smart contract and prepares the compute phase. + * + * @param cfg The configuration for the compute phase. + * @param impl Implementation of the smart contract + * + * @returns True if the contract was successfully executed, false otherwise. + */ +bool Transaction::run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& impl) { + ComputePhase& cp = *compute_phase; + CHECK(cp.precompiled_gas_usage); + td::uint64 gas_usage = cp.precompiled_gas_usage.value(); + td::RealCpuTimer timer; + auto result = + impl.run(my_addr, now, start_lt, balance, new_data, *in_msg_body, in_msg, msg_balance_remaining, in_msg_extern, + compute_vm_libraries(cfg), cfg.global_version, cfg.max_vm_data_depth, new_code, + cfg.unpacked_config_tuple, due_payment.not_null() ? due_payment : td::zero_refint(), gas_usage); + time_tvm = timer.elapsed_both(); + cp.vm_init_state_hash = td::Bits256::zero(); + cp.exit_code = result.exit_code; + cp.out_of_gas = false; + cp.vm_final_state_hash = td::Bits256::zero(); + cp.vm_steps = 0; + cp.gas_used = gas_usage; + cp.accepted = result.accepted; + cp.success = (cp.accepted && result.committed); + LOG(INFO) << "Running precompiled smart contract " << impl.get_name() << ": exit_code=" << result.exit_code + << " accepted=" << result.accepted << " success=" << cp.success << " gas_used=" << gas_usage + << " time=" << time_tvm.real << "s cpu_time=" << time_tvm.cpu; + if (cp.accepted & use_msg_state) { + was_activated = true; + acc_status = Account::acc_active; + } + if (cfg.with_vm_log) { + cp.vm_log = PSTRING() << "Running precompiled smart contract " << impl.get_name() + << ": exit_code=" << result.exit_code << " accepted=" << result.accepted + << " success=" << cp.success << " gas_used=" << gas_usage << " time=" << time_tvm.real << "s"; + } + if (cp.success) { + cp.new_data = impl.get_c4(); + cp.actions = impl.get_c5(); + int out_act_num = output_actions_count(cp.actions); + if (verbosity > 2) { + FLOG(INFO) { + sb << "new smart contract data: "; + bool can_be_special = true; + load_cell_slice_special(cp.new_data, can_be_special).print_rec(sb); + sb << "output actions: "; + block::gen::OutList{out_act_num}.print_ref(sb, cp.actions); + }; + } + } + cp.mode = 0; + cp.exit_arg = 0; + if (!cp.success && result.exit_arg) { + auto value = td::narrow_cast_safe(result.exit_arg.value()); + if (value.is_ok()) { + cp.exit_arg = value.ok(); + } + } + if (cp.accepted) { + if (account.is_special) { + cp.gas_fees = td::zero_refint(); + } else { + cp.gas_fees = cfg.compute_gas_price(cp.gas_used); + total_fees += cp.gas_fees; + balance -= cp.gas_fees; + } + LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " + << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " + << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); + CHECK(td::sgn(balance.grams) >= 0); + } + return true; +} + +/** + * Prepares the compute phase of a transaction, which includes running TVM. + * + * @param cfg The configuration for the compute phase. + * + * @returns True if the compute phase was successfully prepared and executed, false otherwise. + */ +bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { + // TODO: add more skip verifications + sometimes use state from in_msg to re-activate + // ... + compute_phase = std::make_unique(); + ComputePhase& cp = *(compute_phase.get()); + if (cfg.global_version >= 9) { + original_balance = balance; + if (msg_balance_remaining.is_valid()) { + original_balance -= msg_balance_remaining; + } + } else { + original_balance -= total_fees; + } + if (td::sgn(balance.grams) <= 0) { + // no gas + cp.skip_reason = ComputePhase::sk_no_gas; + return true; + } + // Compute gas limits + if (!compute_gas_limits(cp, cfg)) { + compute_phase.reset(); + return false; + } + if (!cp.gas_limit && !cp.gas_credit) { + // no gas + cp.skip_reason = ComputePhase::sk_no_gas; + return true; + } + if (in_msg_state.not_null()) { + LOG(DEBUG) << "HASH(in_msg_state) = " << in_msg_state->get_hash().bits().to_hex(256) + << ", account_state_hash = " << account.state_hash.to_hex(); + } else { + LOG(DEBUG) << "in_msg_state is null"; + } + if (in_msg_state.not_null() && + (acc_status == Account::acc_uninit || + (acc_status == Account::acc_frozen && account.state_hash == in_msg_state->get_hash().bits()))) { + if (acc_status == Account::acc_uninit && cfg.is_address_suspended(account.workchain, account.addr)) { + LOG(DEBUG) << "address is suspended, skipping compute phase"; + cp.skip_reason = ComputePhase::sk_suspended; + return true; + } + use_msg_state = true; + bool forbid_public_libs = + acc_status == Account::acc_uninit && account.is_masterchain(); // Forbid for deploying, allow for unfreezing + if (!(unpack_msg_state(cfg, false, forbid_public_libs) && + account.check_addr_rewrite_length(new_fixed_prefix_length))) { + LOG(DEBUG) << "cannot unpack in_msg_state, or it has bad fixed_prefix_length; cannot init account state"; + cp.skip_reason = ComputePhase::sk_bad_state; + return true; + } + if (acc_status == Account::acc_uninit && !check_in_msg_state_hash(cfg)) { + LOG(DEBUG) << "in_msg_state hash mismatch, cannot init account state"; + cp.skip_reason = ComputePhase::sk_bad_state; + return true; + } + if (cfg.disable_anycast && acc_status == Account::acc_uninit && + new_fixed_prefix_length > cfg.size_limits.max_acc_fixed_prefix_length) { + LOG(DEBUG) << "cannot init account state: too big fixed prefix length (" << new_fixed_prefix_length << ", max " + << cfg.size_limits.max_acc_fixed_prefix_length << ")"; + cp.skip_reason = ComputePhase::sk_bad_state; + return true; + } + } else if (acc_status != Account::acc_active) { + // no state, cannot perform transactions + cp.skip_reason = in_msg_state.not_null() ? ComputePhase::sk_bad_state : ComputePhase::sk_no_state; + return true; + } else if (in_msg_state.not_null()) { + if (cfg.allow_external_unfreeze) { + if (in_msg_extern && account.addr != in_msg_state->get_hash().bits()) { + // only for external messages with non-zero initstate in active accounts + LOG(DEBUG) << "in_msg_state hash mismatch in external message"; + cp.skip_reason = ComputePhase::sk_bad_state; + return true; + } + } + unpack_msg_state(cfg, true); // use only libraries + } + if (!cfg.allow_external_unfreeze) { + if (in_msg_extern && in_msg_state.not_null() && account.addr != in_msg_state->get_hash().bits()) { + LOG(DEBUG) << "in_msg_state hash mismatch in external message"; + cp.skip_reason = ComputePhase::sk_bad_state; + return true; + } + } + if (cfg.disable_anycast) { + my_addr = my_addr_exact; + new_addr_rewrite_length = 0; + force_remove_anycast_address = true; + } + + td::optional precompiled; + if (new_code.not_null() && trans_type == tr_ord) { + precompiled = cfg.precompiled_contracts.get_contract(new_code->get_hash().bits()); + } + + vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit}; + if (precompiled) { + td::uint64 gas_usage = precompiled.value().gas_usage; + cp.precompiled_gas_usage = gas_usage; + if (gas_usage > cp.gas_limit) { + cp.skip_reason = ComputePhase::sk_no_gas; + return true; + } + auto impl = precompiled::get_implementation(new_code->get_hash().bits()); + if (impl != nullptr && !cfg.dont_run_precompiled_ && impl->required_version() <= cfg.global_version) { + return run_precompiled_contract(cfg, *impl); + } + + // Contract is marked as precompiled in global config, but implementation is not available + // In this case we run TVM and override gas_used + LOG(INFO) << "Unknown precompiled contract (code_hash=" << new_code->get_hash().to_hex() + << ", gas_usage=" << gas_usage << "), running VM"; + long long limit = account.is_special ? cfg.special_gas_limit : cfg.gas_limit; + gas = vm::GasLimits{limit, limit, gas.gas_credit ? limit : 0}; + } + + // initialize VM + Ref stack = prepare_vm_stack(cp); + if (stack.is_null()) { + compute_phase.reset(); + return false; + } + // OstreamLogger ostream_logger(error_stream); + // auto log = create_vm_log(error_stream ? &ostream_logger : nullptr); + LOG(DEBUG) << "creating VM"; + + std::unique_ptr logger; + auto vm_log = vm::VmLog(); + if (cfg.with_vm_log) { + size_t log_max_size = 256; + if (cfg.vm_log_verbosity > 4) { + log_max_size = 32 << 20; + } else if (cfg.vm_log_verbosity > 0) { + log_max_size = 1 << 20; + } + logger = std::make_unique(log_max_size); + vm_log.log_interface = logger.get(); + vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false); + if (cfg.vm_log_verbosity > 1) { + vm_log.log_mask |= vm::VmLog::ExecLocation; + if (cfg.vm_log_verbosity > 2) { + vm_log.log_mask |= vm::VmLog::GasRemaining; + if (cfg.vm_log_verbosity > 3) { + vm_log.log_mask |= vm::VmLog::DumpStack; + if (cfg.vm_log_verbosity > 4) { + vm_log.log_mask |= vm::VmLog::DumpStackVerbose; + vm_log.log_mask |= vm::VmLog::DumpC5; + } + } + } + } + } + vm::VmState vm{new_code, cfg.global_version, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)}; + vm.set_max_data_depth(cfg.max_vm_data_depth); + vm.set_c7(prepare_vm_c7(cfg)); // tuple with SmartContractInfo + vm.set_chksig_always_succeed(cfg.ignore_chksig); + vm.set_stop_on_accept_message(cfg.stop_on_accept_message); + // vm.incr_stack_trace(1); // enable stack dump after each step + + LOG(DEBUG) << "starting VM"; + cp.vm_init_state_hash = vm.get_state_hash(); + td::RealCpuTimer timer; + cp.exit_code = ~vm.run(); + time_tvm = timer.elapsed_both(); + LOG(DEBUG) << "VM terminated with exit code " << cp.exit_code; + cp.out_of_gas = (cp.exit_code == ~(int)vm::Excno::out_of_gas); + cp.vm_final_state_hash = vm.get_final_state_hash(cp.exit_code); + stack = vm.get_stack_ref(); + cp.vm_steps = (int)vm.get_steps_count(); + gas = vm.get_gas_limits(); + cp.gas_used = std::min(gas.gas_consumed(), gas.gas_limit); + cp.accepted = (gas.gas_credit == 0); + cp.success = (cp.accepted && vm.committed()); + if (cp.accepted & use_msg_state) { + was_activated = true; + acc_status = Account::acc_active; + } + if (precompiled) { + cp.gas_used = precompiled.value().gas_usage; + cp.vm_steps = 0; + cp.vm_init_state_hash = cp.vm_final_state_hash = td::Bits256::zero(); + if (cp.out_of_gas) { + LOG(ERROR) << "Precompiled smc got out_of_gas in TVM"; + return false; + } + } + LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas.gas_consumed() << ", max=" << gas.gas_max + << ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit; + LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success + << ", time=" << time_tvm.real << "s, cpu_time=" << time_tvm.cpu; + if (logger != nullptr) { + cp.vm_log = logger->get_log(); + } + if (cp.success) { + cp.new_data = vm.get_committed_state().c4; // c4 -> persistent data + cp.actions = vm.get_committed_state().c5; // c5 -> action list + int out_act_num = output_actions_count(cp.actions); + if (verbosity > 2) { + FLOG(INFO) { + sb << "new smart contract data: "; + bool can_be_special = true; + load_cell_slice_special(cp.new_data, can_be_special).print_rec(sb); + sb << "output actions: "; + block::gen::OutList{out_act_num}.print_ref(sb, cp.actions); + }; + } + } + cp.mode = 0; + cp.exit_arg = 0; + if (!cp.success && stack->depth() > 0) { + td::RefInt256 tos = stack->tos().as_int(); + if (tos.not_null() && tos->signed_fits_bits(32)) { + cp.exit_arg = (int)tos->to_long(); + } + } + if (cp.accepted) { + if (account.is_special) { + cp.gas_fees = td::zero_refint(); + } else { + cp.gas_fees = cfg.compute_gas_price(cp.gas_used); + total_fees += cp.gas_fees; + balance -= cp.gas_fees; + } + LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " + << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " + << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); + CHECK(td::sgn(balance.grams) >= 0); + } + cp.vm_loaded_cells = vm.extract_loaded_cells(); + return true; +} + +/** + * Prepares the action phase of a transaction. + * + * @param cfg The configuration for the action phase. + * + * @returns True if the action phase was prepared successfully, false otherwise. + */ +bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { + if (!compute_phase || !compute_phase->success) { + return false; + } + action_phase = std::make_unique(); + ActionPhase& ap = *(action_phase.get()); + ap.result_code = -1; + ap.result_arg = 0; + ap.tot_actions = ap.spec_actions = ap.skipped_actions = ap.msgs_created = 0; + Ref list = compute_phase->actions; + assert(list.not_null()); + ap.action_list_hash = list->get_hash().bits(); + ap.remaining_balance = balance; + ap.end_lt = end_lt; + ap.total_fwd_fees = td::zero_refint(); + ap.total_action_fees = td::zero_refint(); + ap.reserved_balance.set_zero(); + ap.action_fine = td::zero_refint(); + + td::Ref old_code = new_code, old_data = new_data, old_library = new_library; + // 1 - ok, 0 - limits exceeded, -1 - fatal error + auto enforce_state_limits = [&]() -> int { + if (account.is_special) { + return 1; + } + auto S = check_state_limits(cfg.size_limits, cfg.global_version); + if (S.is_error()) { + if (S.code() != AccountStorageStat::errorcode_limits_exceeded) { + LOG(ERROR) << "Account storage stat error: " << S.move_as_error(); + return -1; + } + // Rollback changes to state, fail action phase + LOG(INFO) << "Account state size exceeded limits: " << S.move_as_error(); + new_account_storage_stat = {}; + new_code = old_code; + new_data = old_data; + new_library = old_library; + ap.result_code = 50; + ap.state_exceeds_limits = true; + return 0; + } + return 1; + }; + + int n = 0; + while (true) { + ap.action_list.push_back(list); + bool special = true; + auto cs = load_cell_slice_special(std::move(list), special); + if (special) { + ap.result_code = 32; // action list invalid + ap.result_arg = n; + ap.action_list_invalid = true; + LOG(DEBUG) << "action list invalid: special cell"; + return true; + } + if (!cs.size_ext()) { + break; + } + if (!cs.have_refs()) { + ap.result_code = 32; // action list invalid + ap.result_arg = n; + ap.action_list_invalid = true; + LOG(DEBUG) << "action list invalid: entry found with data but no next reference"; + return true; + } + list = cs.prefetch_ref(); + n++; + if (n > cfg.max_actions) { + ap.result_code = 33; // too many actions + ap.result_arg = n; + ap.action_list_invalid = true; + LOG(DEBUG) << "action list too long: more than " << cfg.max_actions << " actions"; + return true; + } + } + + ap.tot_actions = n; + ap.spec_actions = ap.skipped_actions = 0; + for (int i = n - 1; i >= 0; --i) { + ap.result_arg = n - 1 - i; + if (!block::gen::t_OutListNode.validate_ref(ap.action_list[i])) { + if (cfg.message_skip_enabled) { + // try to read mode from action_send_msg even if out_msg scheme is violated + // action should at least contain 40 bits: 32bit tag and 8 bit mode + // if (mode & 2), that is ignore error mode, skip action even for invalid message + // if there is no (mode & 2) but (mode & 16) presents - enable bounce if possible + bool special = true; + auto cs = load_cell_slice_special(ap.action_list[i], special); + if (!special) { + if ((cs.size() >= 40) && ((int)cs.fetch_ulong(32) == 0x0ec3c86d)) { + int mode = (int)cs.fetch_ulong(8); + if (mode & 2) { + ap.skipped_actions++; + ap.action_list[i] = {}; + continue; + } else if ((mode & 16) && cfg.bounce_on_fail_enabled) { + ap.bounce = true; + } + } + } + } + ap.result_code = 34; // action #i invalid or unsupported + ap.action_list_invalid = true; + LOG(DEBUG) << "invalid action " << ap.result_arg << " found while preprocessing action list: error code " + << ap.result_code; + return true; + } + } + ap.valid = true; + for (int i = n - 1; i >= 0; --i) { + if(ap.action_list[i].is_null()) { + continue; + } + ap.result_arg = n - 1 - i; + vm::CellSlice cs = load_cell_slice(ap.action_list[i]); + CHECK(cs.fetch_ref().not_null()); + int tag = block::gen::t_OutAction.get_tag(cs); + CHECK(tag >= 0); + int err_code = 34; + ap.need_bounce_on_fail = false; + switch (tag) { + case block::gen::OutAction::action_set_code: + err_code = try_action_set_code(cs, ap, cfg); + break; + case block::gen::OutAction::action_send_msg: + err_code = try_action_send_msg(cs, ap, cfg); + if (err_code == -2) { + err_code = try_action_send_msg(cs, ap, cfg, 1); + if (err_code == -2) { + err_code = try_action_send_msg(cs, ap, cfg, 2); + } + } + break; + case block::gen::OutAction::action_reserve_currency: + err_code = try_action_reserve_currency(cs, ap, cfg); + break; + case block::gen::OutAction::action_change_library: + err_code = try_action_change_library(cs, ap, cfg); + break; + } + if (err_code) { + ap.result_code = (err_code == -1 ? 34 : err_code); + ap.end_lt = end_lt; + if (err_code == -1 || err_code == 34) { + ap.action_list_invalid = true; + } + if (err_code == 37 || err_code == 38) { + ap.no_funds = true; + } + LOG(DEBUG) << "invalid action " << ap.result_arg << " in action list: error code " << ap.result_code; + // This is required here because changes to libraries are applied even if action phase fails + if (enforce_state_limits() == -1) { + return false; + } + if (cfg.action_fine_enabled) { + ap.action_fine = std::min(ap.action_fine, balance.grams); + ap.total_action_fees = ap.action_fine; + balance.grams -= ap.action_fine; + total_fees += ap.action_fine; + } + if (ap.need_bounce_on_fail) { + ap.bounce = true; + } + return true; + } + } + + if (cfg.action_fine_enabled) { + ap.total_action_fees += ap.action_fine; + } + end_lt = ap.end_lt; + if (ap.new_code.not_null()) { + new_code = ap.new_code; + } + new_data = compute_phase->new_data; // tentative persistent data update applied + int res = enforce_state_limits(); + if (res == -1) { + return false; + } + if (res == 0) { + if (cfg.extra_currency_v2) { + end_lt = ap.end_lt = start_lt + 1; + if (cfg.action_fine_enabled) { + ap.action_fine = std::min(ap.action_fine, balance.grams); + ap.total_action_fees = ap.action_fine; + balance.grams -= ap.action_fine; + total_fees += ap.action_fine; + } + } + return true; + } + + ap.result_arg = 0; + ap.result_code = 0; + CHECK(ap.remaining_balance.grams->sgn() >= 0); + CHECK(ap.reserved_balance.grams->sgn() >= 0); + ap.remaining_balance += ap.reserved_balance; + CHECK(ap.remaining_balance.is_valid()); + if (ap.acc_delete_req) { + CHECK(cfg.extra_currency_v2 ? ap.remaining_balance.grams->sgn() == 0 : ap.remaining_balance.is_zero()); + ap.acc_status_change = ActionPhase::acst_deleted; + acc_status = (ap.remaining_balance.is_zero() ? Account::acc_deleted : Account::acc_uninit); + was_deleted = true; + } + ap.success = true; + out_msgs = std::move(ap.out_msgs); + total_fees += + ap.total_action_fees; // NB: forwarding fees are not accounted here (they are not collected by the validators in this transaction) + balance = ap.remaining_balance; + return true; +} + +/** + * Tries to set the code for an account. + * + * @param cs The CellSlice containing the action data serialized as action_set_code TLB-scheme. + * @param ap The action phase object. + * @param cfg The action phase configuration. + * + * @returns 0 if the code was successfully set, -1 otherwise. + */ +int Transaction::try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { + block::gen::OutAction::Record_action_set_code rec; + if (!tlb::unpack_exact(cs, rec)) { + return -1; + } + ap.new_code = std::move(rec.new_code); + ap.code_changed = true; + ap.spec_actions++; + return 0; +} + +/** + * Tries to change the library in the transaction. + * + * @param cs The cell slice containing the action data serialized as action_change_library TLB-scheme. + * @param ap The action phase object. + * @param cfg The action phase configuration. + * + * @returns 0 if the action was successfully performed, + * -1 if there was an error unpacking the data or the mode is invalid, + * 41 if the library reference is required but is null, + * 43 if the number of cells in the library exceeds the limit, + * 42 if there was a VM error during the operation. + */ +int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { + block::gen::OutAction::Record_action_change_library rec; + if (!tlb::unpack_exact(cs, rec)) { + return -1; + } + // mode: +0 = remove library, +1 = add private library, +2 = add public library, +16 - bounce on fail + if (rec.mode & 16) { + if (!cfg.bounce_on_fail_enabled) { + return -1; + } + ap.need_bounce_on_fail = true; + rec.mode &= ~16; + } + if (rec.mode > 2) { + return -1; + } + Ref lib_ref = rec.libref->prefetch_ref(); + ton::Bits256 hash; + if (lib_ref.not_null()) { + hash = lib_ref->get_hash().bits(); + } else { + CHECK(rec.libref.write().fetch_ulong(1) == 0 && rec.libref.write().fetch_bits_to(hash)); + } + try { + vm::Dictionary dict{new_library, 256}; + if (!rec.mode) { + // remove library + dict.lookup_delete(hash); + LOG(DEBUG) << "removed " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex(); + } else { + auto val = dict.lookup(hash); + if (val.not_null()) { + bool is_public = val->prefetch_ulong(1); + auto ref = val->prefetch_ref(); + if (hash == ref->get_hash().bits()) { + lib_ref = ref; + if (is_public == (rec.mode >> 1)) { + // library already in required state + ap.spec_actions++; + return 0; + } + } + } + if (lib_ref.is_null()) { + // library code not found + return 41; + } + vm::CellStorageStat sstat; + auto cell_info = sstat.compute_used_storage(lib_ref).move_as_ok(); + if (sstat.cells > cfg.size_limits.max_library_cells || cell_info.max_merkle_depth > max_allowed_merkle_depth) { + return 43; + } + vm::CellBuilder cb; + CHECK(cb.store_bool_bool(rec.mode >> 1) && cb.store_ref_bool(std::move(lib_ref))); + CHECK(dict.set_builder(hash, cb)); + LOG(DEBUG) << "added " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex(); + } + new_library = std::move(dict).extract_root_cell(); + } catch (vm::VmError&) { + return 42; + } + ap.spec_actions++; + return 0; +} +} // namespace transaction + +/** + * Computes the forward fees for a message based on the number of cells and bits. + * + * msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms + * ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms + * bits in the root cell of a message are not included in msg.bits (lump_price pays for them) + * + * @param cells The number of cells in the message. + * @param bits The number of bits in the message. + * + * @returns The computed forward fees for the message. + */ +td::uint64 MsgPrices::compute_fwd_fees(td::uint64 cells, td::uint64 bits) const { + return lump_price + td::uint128(bit_price) + .mult(bits) + .add(td::uint128(cell_price).mult(cells)) + .add(td::uint128(0xffffu)) + .shr(16) + .lo(); +} + +/** + * Computes the forward fees for a message based on the number of cells and bits. + * Return the result as td::RefInt256 + * + * msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms + * ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms + * bits in the root cell of a message are not included in msg.bits (lump_price pays for them) + * + * @param cells The number of cells in the message. + * @param bits The number of bits in the message. + * + * @returns The computed forward fees for the message as td::RefInt256j. + */ +td::RefInt256 MsgPrices::compute_fwd_fees256(td::uint64 cells, td::uint64 bits) const { + return td::make_refint(lump_price) + + td::rshift(td::make_refint(bit_price) * bits + td::make_refint(cell_price) * cells, 16, + 1); // divide by 2^16 with ceil rounding +} + +/** + * Computes the forward fees and IHR fees for a message with the given number of cells and bits. + * + * @param cells The number of cells. + * @param bits The number of bits. + * @param ihr_disabled Flag indicating whether IHR is disabled. + * + * @returns A pair of values representing the forward fees and IHR fees. + */ +std::pair MsgPrices::compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits, + bool ihr_disabled) const { + td::uint64 fwd = compute_fwd_fees(cells, bits); + if (ihr_disabled) { + return std::pair(fwd, 0); + } + return std::pair(fwd, td::uint128(fwd).mult(ihr_factor).shr(16).lo()); +} + +/** + * Computes the part of the fees that go to the total fees of the current block. + * + * @param total The amount of fees. + * + * @returns The the part of the fees that go to the total fees of the current block. + */ +td::RefInt256 MsgPrices::get_first_part(td::RefInt256 total) const { + return (std::move(total) * first_frac) >> 16; +} + +/** + * Computes the part of the fees that go to the total fees of the current block. + * + * @param total The amount of fees. + * + * @returns The the part of the fees that go to the total fees of the current block. + */ +td::uint64 MsgPrices::get_first_part(td::uint64 total) const { + return td::uint128(total).mult(first_frac).shr(16).lo(); +} + +/** + * Computes the part of the fees that go to the total fees of the transit block. + * + * @param total The amount of fees. + * + * @returns The the part of the fees that go to the total fees of the transit block. + */ +td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const { + return (std::move(total) * next_frac) >> 16; +} + +namespace transaction { +/** + * Checks if the source address is addr_none and replaces is with the account address. + * + * @param src_addr A reference to the source address of the message. + * + * @returns True if the source address is addr_none or is equal to the account address. + */ +bool Transaction::check_replace_src_addr(Ref& src_addr) const { + int t = (int)src_addr->prefetch_ulong(2); + if (!t && src_addr->size_ext() == 2) { + // addr_none$00 --> replace with the address of current smart contract + src_addr = my_addr; + return true; + } + if (t != 2) { + // invalid address (addr_extern and addr_var cannot be source addresses) + return false; + } + if (src_addr->contents_equal(*my_addr) || src_addr->contents_equal(*my_addr_exact)) { + // source address matches that of the current account + return true; + } + // only one valid case remaining: rewritten source address used, replace with the complete one + // (are we sure we want to allow this?) + return false; +} + +/** + * Checks the destination address of a message, rewrites it if it is an anycast address. + * + * @param dest_addr A reference to the destination address of the transaction. + * @param cfg The configuration for the action phase. + * @param is_mc A pointer to a boolean where it will be stored whether the destination is in the masterchain. + * @param allow_anycast Allow anycast the address. + * + * @returns True if the destination address is valid, false otherwise. + */ +bool Transaction::check_rewrite_dest_addr(Ref& dest_addr, const ActionPhaseConfig& cfg, + bool* is_mc, bool allow_anycast) const { + if (!dest_addr->prefetch_ulong(1)) { + // all external addresses allowed + if (is_mc) { + *is_mc = false; + } + return true; + } + bool repack = false; + int tag = block::gen::t_MsgAddressInt.get_tag(*dest_addr); + + block::gen::MsgAddressInt::Record_addr_var rec; + + if (tag == block::gen::MsgAddressInt::addr_var) { + if (!tlb::csr_unpack(dest_addr, rec)) { + // cannot unpack addr_var + LOG(DEBUG) << "cannot unpack addr_var in a destination address"; + return false; + } + if (rec.addr_len == 256 && rec.workchain_id >= -128 && rec.workchain_id < 128) { + LOG(DEBUG) << "destination address contains an addr_var to be repacked into addr_std"; + repack = true; + } + } else if (tag == block::gen::MsgAddressInt::addr_std) { + block::gen::MsgAddressInt::Record_addr_std recs; + if (!tlb::csr_unpack(dest_addr, recs)) { + // cannot unpack addr_std + LOG(DEBUG) << "cannot unpack addr_std in a destination address"; + return false; + } + rec.anycast = std::move(recs.anycast); + rec.addr_len = 256; + rec.workchain_id = recs.workchain_id; + rec.address = td::make_bitstring_ref(recs.address); + } else { + // unknown address format (not a MsgAddressInt) + LOG(DEBUG) << "destination address does not have a MsgAddressInt tag"; + return false; + } + if (rec.workchain_id != ton::masterchainId) { + // recover destination workchain info from configuration + auto it = cfg.workchains->find(rec.workchain_id); + if (it == cfg.workchains->end()) { + // undefined destination workchain + LOG(DEBUG) << "destination address contains unknown workchain_id " << rec.workchain_id; + return false; + } + if (!it->second->accept_msgs) { + // workchain does not accept new messages + LOG(DEBUG) << "destination address belongs to workchain " << rec.workchain_id << " not accepting new messages"; + return false; + } + if (!it->second->is_valid_addr_len(rec.addr_len)) { + // invalid address length for specified workchain + LOG(DEBUG) << "destination address has length " << rec.addr_len << " invalid for destination workchain " + << rec.workchain_id; + return false; + } + } + if (rec.anycast->size() > 1) { + if (!allow_anycast) { + return false; + } + // destination address is an anycast + vm::CellSlice cs{*rec.anycast}; + int d = (int)cs.fetch_ulong(6) - 32; + if (d <= 0 || d > 30) { + // invalid anycast prefix length + return false; + } + unsigned pfx = (unsigned)cs.fetch_ulong(d); + unsigned my_pfx = (unsigned)account.addr.cbits().get_uint(d); + if (pfx != my_pfx) { + // rewrite destination address + vm::CellBuilder cb; + CHECK(cb.store_long_bool(32 + d, 6) // just$1 depth:(#<= 30) + && cb.store_long_bool(my_pfx, d) // rewrite_pfx:(bits depth) + && (rec.anycast = load_cell_slice_ref(cb.finalize())).not_null()); + repack = true; + } + } + if (is_mc) { + *is_mc = (rec.workchain_id == ton::masterchainId); + } + if (!repack) { + return true; + } + if (rec.addr_len == 256 && rec.workchain_id >= -128 && rec.workchain_id < 128) { + // repack as an addr_std + vm::CellBuilder cb; + CHECK(cb.store_long_bool(2, 2) // addr_std$10 + && cb.append_cellslice_bool(std::move(rec.anycast)) // anycast:(Maybe Anycast) ... + && cb.store_long_bool(rec.workchain_id, 8) // workchain_id:int8 + && cb.append_bitstring(std::move(rec.address)) // address:bits256 + && (dest_addr = load_cell_slice_ref(cb.finalize())).not_null()); + } else { + // repack as an addr_var + CHECK(tlb::csr_pack(dest_addr, std::move(rec))); + } + CHECK(block::gen::t_MsgAddressInt.validate_csr(dest_addr)); + return true; +} + +/** + * Tries to send a message. + * + * @param cs0 The cell slice containing the action data serialized as action_send_msg TLB-scheme. + * @param ap The action phase. + * @param cfg The action phase configuration. + * @param redoing The index of the attempt, starting from 0. On later attempts tries to move message body and StateInit to separate cells. + * + * @returns 0 if the message is successfully sent or if the error may be ignored, error code otherwise. + * Returns -2 if the action should be attempted again. + */ +int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, const ActionPhaseConfig& cfg, + int redoing) { + block::gen::OutAction::Record_action_send_msg act_rec; + // mode: + // +128 = attach all remaining balance + // +64 = attach all remaining balance of the inbound message + // +32 = delete smart contract if balance becomes zero + // +1 = pay message fees + // +2 = skip if message cannot be sent + // +16 = bounce if action fails + vm::CellSlice cs{cs0}; + if (!tlb::unpack_exact(cs, act_rec)) { + return -1; + } + if ((act_rec.mode & 16) && cfg.bounce_on_fail_enabled) { + act_rec.mode &= ~16; + ap.need_bounce_on_fail = true; + } + if ((act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) { + return -1; + } + bool skip_invalid = (act_rec.mode & 2); + auto check_skip_invalid = [&](unsigned error_code) -> unsigned int { + if (skip_invalid) { + if (cfg.message_skip_enabled) { + ap.skipped_actions++; + } + return 0; + } + return error_code; + }; + // try to parse suggested message in act_rec.out_msg + td::RefInt256 fwd_fee, ihr_fee; + block::gen::MessageRelaxed::Record msg; + if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) { + return -1; + } + if (!block::tlb::validate_message_relaxed_libs(act_rec.out_msg)) { + LOG(DEBUG) << "outbound message has invalid libs in StateInit"; + return -1; + } + if (redoing >= 1) { + if (msg.init->size_refs() >= 2) { + LOG(DEBUG) << "moving the StateInit of a suggested outbound message into a separate cell"; + // init:(Maybe (Either StateInit ^StateInit)) + // transform (just (left z:StateInit)) into (just (right z:^StateInit)) + CHECK(msg.init.write().fetch_ulong(2) == 2); + vm::CellBuilder cb; + Ref cell; + CHECK(cb.append_cellslice_bool(std::move(msg.init)) // StateInit + && cb.finalize_to(cell) // -> ^StateInit + && cb.store_long_bool(3, 2) // (just (right ... )) + && cb.store_ref_bool(std::move(cell)) // z:^StateInit + && cb.finalize_to(cell)); + msg.init = vm::load_cell_slice_ref(cell); + } else { + redoing = 2; + } + } + if (redoing >= 2 && msg.body->size_ext() > 1 && msg.body->prefetch_ulong(1) == 0) { + LOG(DEBUG) << "moving the body of a suggested outbound message into a separate cell"; + // body:(Either X ^X) + // transform (left x:X) into (right x:^X) + CHECK(msg.body.write().fetch_ulong(1) == 0); + vm::CellBuilder cb; + Ref cell; + CHECK(cb.append_cellslice_bool(std::move(msg.body)) // X + && cb.finalize_to(cell) // -> ^X + && cb.store_long_bool(1, 1) // (right ... ) + && cb.store_ref_bool(std::move(cell)) // x:^X + && cb.finalize_to(cell)); + msg.body = vm::load_cell_slice_ref(cell); + } + + block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info; + bool ext_msg = msg.info->prefetch_ulong(1); + if (ext_msg) { + // ext_out_msg_info$11 constructor of CommonMsgInfoRelaxed + block::gen::CommonMsgInfoRelaxed::Record_ext_out_msg_info erec; + if (!tlb::csr_unpack(msg.info, erec)) { + return -1; + } + if (act_rec.mode & ~3) { + return -1; // invalid mode for an external message + } + info.src = std::move(erec.src); + info.dest = std::move(erec.dest); + // created_lt and created_at are ignored + info.ihr_disabled = true; + info.bounce = false; + info.bounced = false; + fwd_fee = ihr_fee = td::zero_refint(); + } else { + // int_msg_info$0 constructor + if (!tlb::csr_unpack(msg.info, info) || !block::tlb::t_CurrencyCollection.validate_csr(info.value)) { + return -1; + } + if (cfg.disable_custom_fess) { + fwd_fee = ihr_fee = td::zero_refint(); + } else { + fwd_fee = tlb::t_Grams.as_integer(info.fwd_fee); + ihr_fee = cfg.global_version >= 12 ? td::zero_refint() : tlb::t_Grams.as_integer(info.extra_flags); + } + if (cfg.disable_ihr_flag) { + info.ihr_disabled = true; + } + } + // set created_at and created_lt to correct values + info.created_at = now; + info.created_lt = ap.end_lt; + // always clear bounced flag + info.bounced = false; + // have to check source address + // it must be either our source address, or empty + if (!check_replace_src_addr(info.src)) { + LOG(DEBUG) << "invalid source address in a proposed outbound message"; + return 35; // invalid source address + } + bool to_mc = false; + if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc, !cfg.disable_anycast)) { + LOG(DEBUG) << "invalid destination address in a proposed outbound message"; + return check_skip_invalid(36); // invalid destination address + } + if (!ext_msg && cfg.extra_currency_v2) { + CurrencyCollection value; + if (!value.unpack(info.value)) { + LOG(DEBUG) << "invalid value:ExtraCurrencies in a proposed outbound message"; + return check_skip_invalid(37); // invalid value:CurrencyCollection + } + if (!CurrencyCollection::remove_zero_extra_currencies(value.extra, cfg.size_limits.max_msg_extra_currencies)) { + LOG(DEBUG) << "invalid value:ExtraCurrencies in a proposed outbound message: too many currencies (max " + << cfg.size_limits.max_msg_extra_currencies << ")"; + // Dict should be valid, since it was checked in t_OutListNode.validate_ref, so error here means limit exceeded + return check_skip_invalid(44); // invalid value:CurrencyCollection : too many extra currencies + } + info.value = value.pack(); + } + + // fetch message pricing info + const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain()); + // If action fails, account is required to pay fine_per_cell for every visited cell + // Number of visited cells is limited depending on available funds + unsigned max_cells = cfg.size_limits.max_msg_cells; + td::uint64 fine_per_cell = 0; + if (cfg.action_fine_enabled && !account.is_special) { + fine_per_cell = (msg_prices.cell_price >> 16) / 4; + td::RefInt256 funds = ap.remaining_balance.grams; + if (!ext_msg && !(act_rec.mode & 0x80) && !(act_rec.mode & 1)) { + if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) { + LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message"; + return check_skip_invalid(37); + } + block::CurrencyCollection value; + CHECK(value.unpack(info.value)); + CHECK(value.grams.not_null()); + td::RefInt256 new_funds = value.grams; + if (act_rec.mode & 0x40) { + if (msg_balance_remaining.is_valid()) { + new_funds += msg_balance_remaining.grams; + } + if (compute_phase) { + new_funds -= compute_phase->gas_fees; + } + new_funds -= ap.action_fine; + if (new_funds->sgn() < 0) { + LOG(DEBUG) + << "not enough value to transfer with the message: all of the inbound message value has been consumed"; + return check_skip_invalid(37); + } + } + funds = std::min(funds, new_funds); + } + if (funds->cmp(max_cells * fine_per_cell) < 0) { + max_cells = static_cast((funds / td::make_refint(fine_per_cell))->to_long()); + } + } + // compute size of message + vm::CellStorageStat sstat(max_cells); // for message size + // preliminary storage estimation of the resulting message + unsigned max_merkle_depth = 0; + auto add_used_storage = [&](const auto& x, unsigned skip_root_count) -> td::Status { + if (x.not_null()) { + TRY_RESULT(res, sstat.add_used_storage(x, true, skip_root_count)); + max_merkle_depth = std::max(max_merkle_depth, res.max_merkle_depth); + } + return td::Status::OK(); + }; + add_used_storage(msg.init, 3); // message init + add_used_storage(msg.body, 3); // message body (the root cell itself is not counted) + if (!ext_msg && !cfg.extra_currency_v2) { + add_used_storage(info.value->prefetch_ref(), 0); + } + auto collect_fine = [&] { + if (cfg.action_fine_enabled && !account.is_special) { + td::uint64 fine = fine_per_cell * std::min(max_cells, sstat.cells); + if (ap.remaining_balance.grams->cmp(fine) < 0) { + fine = ap.remaining_balance.grams->to_long(); + } + ap.action_fine += fine; + ap.remaining_balance.grams -= fine; + } + }; + if (sstat.cells > max_cells && max_cells < cfg.size_limits.max_msg_cells) { + LOG(DEBUG) << "not enough funds to process a message (max_cells=" << max_cells << ")"; + collect_fine(); + return check_skip_invalid(40); + } + if (sstat.bits > cfg.size_limits.max_msg_bits || sstat.cells > max_cells) { + LOG(DEBUG) << "message too large, invalid"; + collect_fine(); + return check_skip_invalid(40); + } + if (max_merkle_depth > max_allowed_merkle_depth) { + LOG(DEBUG) << "message has too big merkle depth, invalid"; + collect_fine(); + return check_skip_invalid(40); + } + LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; + + // compute forwarding fees + auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, info.ihr_disabled); + LOG(DEBUG) << "computed fwd fees = " << fees_c.first << " + " << fees_c.second; + + if (account.is_special) { + LOG(DEBUG) << "computed fwd fees set to zero for special account"; + fees_c.first = fees_c.second = 0; + } + + // set fees to computed values + if (fwd_fee->unsigned_fits_bits(63) && fwd_fee->to_long() < (long long)fees_c.first) { + fwd_fee = td::make_refint(fees_c.first); + } + if (fees_c.second && ihr_fee->unsigned_fits_bits(63) && ihr_fee->to_long() < (long long)fees_c.second) { + ihr_fee = td::make_refint(fees_c.second); + } + + Ref new_msg; + td::RefInt256 fees_collected, fees_total; + unsigned new_msg_bits; + + if (!ext_msg) { + // Process outbound internal message + // check value, check/compute ihr_fees, fwd_fees + // ... + if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) { + LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message"; + collect_fine(); + return check_skip_invalid(37); + } + if (info.ihr_disabled) { + // if IHR is disabled, IHR fees will be always zero + ihr_fee = td::zero_refint(); + } + // extract value to be carried by the message + block::CurrencyCollection req; + CHECK(req.unpack(info.value)); + CHECK(req.grams.not_null()); + + if (act_rec.mode & 0x80) { + // attach all remaining balance to this message + if (cfg.extra_currency_v2) { + req.grams = ap.remaining_balance.grams; + } else { + req = ap.remaining_balance; + } + act_rec.mode &= ~1; // pay fees from attached value + } else if (act_rec.mode & 0x40) { + // attach all remaining balance of the inbound message (in addition to the original value) + if (cfg.extra_currency_v2) { + req.grams += msg_balance_remaining.grams; + } else { + req += msg_balance_remaining; + } + if (!(act_rec.mode & 1)) { + req -= ap.action_fine; + if (compute_phase) { + req -= compute_phase->gas_fees; + } + if (!req.is_valid()) { + LOG(DEBUG) + << "not enough value to transfer with the message: all of the inbound message value has been consumed"; + collect_fine(); + return check_skip_invalid(37); + } + } + } + + // compute req_grams + fees + td::RefInt256 req_grams_brutto = req.grams; + fees_total = fwd_fee + ihr_fee; + if (act_rec.mode & 1) { + // we are going to pay the fees + req_grams_brutto += fees_total; + } else if (req.grams < fees_total) { + // receiver pays the fees (but cannot) + LOG(DEBUG) << "not enough value attached to the message to pay forwarding fees : have " << req.grams << ", need " + << fees_total; + collect_fine(); + return check_skip_invalid(37); // not enough grams + } else { + // decrease message value + req.grams -= fees_total; + } + + // check that we have at least the required value + if (ap.remaining_balance.grams < req_grams_brutto) { + LOG(DEBUG) << "not enough grams to transfer with the message : remaining balance is " + << ap.remaining_balance.to_str() << ", need " << req_grams_brutto << " (including forwarding fees)"; + collect_fine(); + return check_skip_invalid(37); // not enough grams + } + + if (cfg.extra_currency_v2 && !req.check_extra_currency_limit(cfg.size_limits.max_msg_extra_currencies)) { + LOG(DEBUG) << "too many extra currencies in the message : max " << cfg.size_limits.max_msg_extra_currencies; + return check_skip_invalid(44); // to many extra currencies + } + + Ref new_extra; + + if (!block::sub_extra_currency(ap.remaining_balance.extra, req.extra, new_extra)) { + LOG(DEBUG) << "not enough extra currency to send with the message: " + << block::CurrencyCollection{0, req.extra}.to_str() << " required, only " + << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " available"; + collect_fine(); + return check_skip_invalid(38); // not enough (extra) funds + } + if (ap.remaining_balance.extra.not_null() || req.extra.not_null()) { + LOG(DEBUG) << "subtracting extra currencies: " + << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " minus " + << block::CurrencyCollection{0, req.extra}.to_str() << " equals " + << block::CurrencyCollection{0, new_extra}.to_str(); + } + + auto fwd_fee_mine = msg_prices.get_first_part(fwd_fee); + auto fwd_fee_remain = fwd_fee - fwd_fee_mine; + + // re-pack message value + CHECK(req.pack_to(info.value)); + CHECK(block::tlb::t_Grams.pack_integer(info.fwd_fee, fwd_fee_remain)); + if (cfg.global_version < 12) { + CHECK(block::tlb::t_Grams.pack_integer(info.extra_flags, ihr_fee)); + } + + // serialize message + CHECK(tlb::csr_pack(msg.info, info)); + vm::CellBuilder cb; + if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) { + LOG(DEBUG) << "outbound message does not fit into a cell after rewriting"; + if (redoing == 2) { + collect_fine(); + return check_skip_invalid(39); + } + return -2; + } + + new_msg_bits = cb.size(); + new_msg = cb.finalize(); + + // clear msg_balance_remaining if it has been used + if (act_rec.mode & 0xc0) { + if (cfg.extra_currency_v2) { + msg_balance_remaining.grams = td::zero_refint(); + } else { + msg_balance_remaining.set_zero(); + } + } + + // update balance + ap.remaining_balance -= req_grams_brutto; + ap.remaining_balance.extra = std::move(new_extra); + CHECK(ap.remaining_balance.is_valid()); + CHECK(ap.remaining_balance.grams->sgn() >= 0); + fees_total = fwd_fee + ihr_fee; + fees_collected = fwd_fee_mine; + } else { + // external messages also have forwarding fees + if (ap.remaining_balance.grams < fwd_fee) { + LOG(DEBUG) << "not enough funds to pay for an outbound external message"; + collect_fine(); + return check_skip_invalid(37); // not enough grams + } + // repack message + // ext_out_msg_info$11 constructor of CommonMsgInfo + block::gen::CommonMsgInfo::Record_ext_out_msg_info erec; + erec.src = info.src; + erec.dest = info.dest; + erec.created_at = info.created_at; + erec.created_lt = info.created_lt; + CHECK(tlb::csr_pack(msg.info, erec)); + vm::CellBuilder cb; + if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) { + LOG(DEBUG) << "outbound message does not fit into a cell after rewriting"; + if (redoing == 2) { + collect_fine(); + return check_skip_invalid(39); + } + return -2; + } + + new_msg_bits = cb.size(); + new_msg = cb.finalize(); + + // update balance + ap.remaining_balance -= fwd_fee; + CHECK(ap.remaining_balance.is_valid()); + CHECK(td::sgn(ap.remaining_balance.grams) >= 0); + fees_collected = fees_total = fwd_fee; + } + + if (!block::tlb::t_Message.validate_ref(new_msg)) { + LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to hand-written check"; + collect_fine(); + return -1; + } + if (!block::gen::t_Message_Any.validate_ref(new_msg)) { + LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to automated check"; + FLOG(INFO) { + block::gen::t_Message_Any.print_ref(sb, new_msg); + vm::load_cell_slice(new_msg).print_rec(sb); + }; + collect_fine(); + return -1; + } + if (verbosity > 2) { + FLOG(INFO) { + sb << "converted outbound message: "; + block::gen::t_Message_Any.print_ref(sb, new_msg); + }; + } + + ap.msgs_created++; + ap.end_lt++; + + ap.out_msgs.push_back(std::move(new_msg)); + ap.total_action_fees += fees_collected; + ap.total_fwd_fees += fees_total; + + if ((act_rec.mode & 0xa0) == 0xa0) { + if (cfg.extra_currency_v2) { + CHECK(ap.remaining_balance.grams->sgn() == 0); + ap.acc_delete_req = ap.reserved_balance.grams->sgn() == 0; + } else { + CHECK(ap.remaining_balance.is_zero()); + ap.acc_delete_req = ap.reserved_balance.is_zero(); + } + } + + ap.tot_msg_bits += sstat.bits + new_msg_bits; + ap.tot_msg_cells += sstat.cells + 1; + + return 0; +} + +/** + * Tries to reserve a currency an action phase. + * + * @param cs The cell slice containing the action data serialized as action_reserve_currency TLB-scheme. + * @param ap The action phase. + * @param cfg The action phase configuration. + * + * @returns 0 if the currency is successfully reserved, error code otherwise. + */ +int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { + block::gen::OutAction::Record_action_reserve_currency rec; + if (!tlb::unpack_exact(cs, rec)) { + return -1; + } + if ((rec.mode & 16) && cfg.bounce_on_fail_enabled) { + rec.mode &= ~16; + ap.need_bounce_on_fail = true; + } + if (rec.mode & ~15) { + return -1; + } + int mode = rec.mode; + LOG(INFO) << "in try_action_reserve_currency(" << mode << ")"; + CurrencyCollection reserve, newc; + if (!reserve.validate_unpack(std::move(rec.currency))) { + LOG(DEBUG) << "cannot parse currency field in action_reserve_currency"; + return -1; + } + if (cfg.extra_currency_v2 && reserve.has_extra()) { + LOG(DEBUG) << "cannot reserve extra currencies"; + return -1; + } + LOG(DEBUG) << "action_reserve_currency: mode=" << mode << ", reserve=" << reserve.to_str() + << ", balance=" << ap.remaining_balance.to_str() << ", original balance=" << original_balance.to_str(); + if (mode & 4) { + if (mode & 8) { + if (cfg.extra_currency_v2) { + reserve.grams = original_balance.grams - reserve.grams; + } else { + reserve = original_balance - reserve; + } + } else { + if (cfg.extra_currency_v2) { + reserve.grams += original_balance.grams; + } else { + reserve += original_balance; + } + } + } else if (mode & 8) { + LOG(DEBUG) << "invalid reserve mode " << mode; + return -1; + } + if (!reserve.is_valid() || td::sgn(reserve.grams) < 0) { + LOG(DEBUG) << "cannot reserve a negative amount: " << reserve.to_str(); + return -1; + } + if (mode & 2) { + if (cfg.reserve_extra_enabled) { + if (!reserve.clamp(ap.remaining_balance)) { + LOG(DEBUG) << "failed to clamp reserve amount " << mode; + return -1; + } + } else { + reserve.grams = std::min(reserve.grams, ap.remaining_balance.grams); + } + } + if (reserve.grams > ap.remaining_balance.grams) { + LOG(DEBUG) << "cannot reserve " << reserve.grams << " nanograms : only " << ap.remaining_balance.grams + << " available"; + return 37; // not enough grams + } + if (!block::sub_extra_currency(ap.remaining_balance.extra, reserve.extra, newc.extra)) { + LOG(DEBUG) << "not enough extra currency to reserve: " << block::CurrencyCollection{0, reserve.extra}.to_str() + << " required, only " << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() + << " available"; + return 38; // not enough (extra) funds + } + newc.grams = ap.remaining_balance.grams - reserve.grams; + if (mode & 1) { + // leave only res_grams, reserve everything else + if (cfg.extra_currency_v2) { + std::swap(newc.grams, reserve.grams); + } else { + std::swap(newc, reserve); + } + } + // set remaining_balance to new_grams and new_extra + ap.remaining_balance = std::move(newc); + // increase reserved_balance by res_grams and res_extra + ap.reserved_balance += std::move(reserve); + CHECK(ap.reserved_balance.is_valid()); + CHECK(ap.remaining_balance.is_valid()); + LOG(INFO) << "changed remaining balance to " << ap.remaining_balance.to_str() << ", reserved balance to " + << ap.reserved_balance.to_str(); + ap.spec_actions++; + return 0; +} + +/** + * Calculates the number of public libraries in the dictionary. + * + * @param libraries The dictionary of account libraries. + * + * @returns The number of public libraries in the dictionary. + */ +static td::uint32 get_public_libraries_count(const td::Ref& libraries) { + td::uint32 count = 0; + vm::Dictionary dict{libraries, 256}; + dict.check_for_each([&](td::Ref value, td::ConstBitPtr key, int) { + if (block::is_public_library(key, std::move(value))) { + ++count; + } + return true; + }); + return count; +} + +/** + * Calculates the number of changes of public libraries in the dictionary. + * + * @param old_libraries The dictionary of account libraries before the transaction. + * @param new_libraries The dictionary of account libraries after the transaction. + * + * @returns The number of changed public libraries. + */ +static td::uint32 get_public_libraries_diff_count(const td::Ref& old_libraries, + const td::Ref& new_libraries) { + td::uint32 count = 0; + vm::Dictionary dict1{old_libraries, 256}; + vm::Dictionary dict2{new_libraries, 256}; + dict1.scan_diff(dict2, [&](td::ConstBitPtr key, int n, Ref val1, Ref val2) -> bool { + CHECK(n == 256); + bool is_public1 = val1.not_null() && block::is_public_library(key, val1); + bool is_public2 = val2.not_null() && block::is_public_library(key, val2); + if (is_public1 != is_public2) { + ++count; + } + return true; + }); + return count; +} + +/** + * Checks that the new account state fits in the limits. + * This function is not called for special accounts. + * + * @param size_limits The size limits configuration. + * @param global_version Global version (ConfigParam 8). + * @param is_account_stat Store storage stat in the Transaction's AccountStorageStat. + * + * @returns A `td::Status` indicating the result of the check. + * - If the state limits are within the allowed range, returns OK. + * - If the state limits exceed the maximum allowed range, returns an error with AccountStorageStat::errorcode_limits_exceeded code. + * - If an error occurred during storage stat calculation, returns other error. + */ +td::Status Transaction::check_state_limits(const SizeLimitsConfig& size_limits, int global_version, + bool is_account_stat) { + auto cell_equal = [](const td::Ref& a, const td::Ref& b) -> bool { + return a.is_null() || b.is_null() ? a.is_null() == b.is_null() : a->get_hash() == b->get_hash(); + }; + if (cell_equal(account.code, new_code) && cell_equal(account.data, new_data) && + cell_equal(account.library, new_library)) { + return td::Status::OK(); + } + AccountStorageStat storage_stat; + if (is_account_stat && account.account_storage_stat) { + storage_stat = AccountStorageStat{&account.account_storage_stat.value()}; + } + { + TD_PERF_COUNTER(transaction_storage_stat_a); + td::RealCpuTimer timer; + SCOPE_EXIT { + LOG_IF(INFO, timer.elapsed_real() > 0.1) << "Compute used storage (1) took " << timer.elapsed_real() << "s"; + if (is_account_stat) { + time_storage_stat += timer.elapsed_both(); + } + }; + if (is_account_stat && compute_phase) { + storage_stat.add_hint(compute_phase->vm_loaded_cells); + } + StorageStatCalculationContext context{is_account_stat}; + StorageStatCalculationContext::Guard guard{&context}; + if (is_account_stat) { + storage_stat_updates.push_back(new_code); + storage_stat_updates.push_back(new_data); + storage_stat_updates.push_back(new_library); + } + TRY_STATUS(storage_stat.replace_roots({new_code, new_data, new_library}, /* check_merkle_depth = */ true)); + } + + td::uint32 max_cells = account.is_masterchain() && global_version >= 12 ? size_limits.max_mc_acc_state_cells + : size_limits.max_acc_state_cells; + if (storage_stat.get_total_cells() > max_cells) { + return td::Status::Error(AccountStorageStat::errorcode_limits_exceeded, + PSTRING() << "account state is too big: cells=" << storage_stat.get_total_cells() + << " (max cells=" << max_cells << ")"); + } + if (account.is_masterchain() && !cell_equal(account.library, new_library)) { + auto libraries_count = get_public_libraries_count(new_library); + if (libraries_count > size_limits.max_acc_public_libraries) { + return td::Status::Error(AccountStorageStat::errorcode_limits_exceeded, + PSTRING() << "too many public libraries: " << libraries_count << " (max " + << size_limits.max_acc_public_libraries << ")"); + } + } + if (is_account_stat) { + // storage_stat will be reused in compute_state() + new_account_storage_stat.value_force() = std::move(storage_stat); + } + return td::Status::OK(); +} + +/** + * Prepares the bounce phase of a transaction. + * + * @param cfg The configuration for the action phase. + * + * @returns True if the bounce phase was successfully prepared, false otherwise. + */ +bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { + if (in_msg.is_null() || !bounce_enabled) { + return false; + } + bounce_phase = std::make_unique(); + BouncePhase& bp = *bounce_phase; + gen::Message::Record msg; + gen::CommonMsgInfo::Record_int_msg_info info; + auto cs = vm::load_cell_slice(in_msg); + if (!(tlb::unpack(cs, info) && gen::t_Maybe_Either_StateInit_Ref_StateInit.skip(cs) && cs.have(1) && + cs.have_refs((int)cs.prefetch_ulong(1)))) { + bounce_phase.reset(); + return false; + } + if (cs.fetch_ulong(1)) { + cs = vm::load_cell_slice(cs.prefetch_ref()); + } + + vm::CellBuilder body; + if (new_bounce_format) { + body.store_long(0xfffffffeU, 32); // new_bounce_body#fffffffe + if (new_bounce_format_full_body) { // original_body:^Cell + body.store_ref(vm::CellBuilder().append_cellslice(in_msg_body).finalize_novm()); + } else { + body.store_ref(vm::CellBuilder().store_bits(in_msg_body->as_bitslice()).finalize_novm()); + } + body.store_ref(vm::CellBuilder() + .append_cellslice(in_msg_info.value) // value:CurrencyCollection + .store_long(in_msg_info.created_lt, 64) // created_lt:uint64 + .store_long(in_msg_info.created_at, 32) // created_at:uint32 + .finalize_novm()); // original_info:^NewBounceOriginalInfo + if (compute_phase->skip_reason != ComputePhase::sk_none) { + body.store_long(0, 8); // bounced_by_phase:uint8 + body.store_long(-compute_phase->skip_reason, 32); // exit_code:int32 + } else if (!compute_phase->success) { + body.store_long(1, 8); // bounced_by_phase:uint8 + body.store_long(compute_phase->exit_code, 32); // exit_code:int32 + } else { + body.store_long(2, 8); // bounced_by_phase:uint8 + body.store_long(action_phase->result_code, 32); // exit_code:int32 + } + // compute_phase:(Maybe NewBounceComputePhaseInfo) + if (compute_phase->skip_reason != ComputePhase::sk_none) { + body.store_long(0, 1); + } else { + body.store_long(1, 1); + body.store_long(compute_phase->gas_used, 32); // gas_used:uint32 + body.store_long(compute_phase->vm_steps, 32); // vm_steps:uint32 + } + } else if (cfg.bounce_msg_body) { + int body_bits = std::min((int)cs.size(), cfg.bounce_msg_body); + body.store_long_bool(-1, 32); // 0xffffffff tag + body.append_bitslice(cs.prefetch_bits(body_bits)); // truncated message body + } + + info.ihr_disabled = true; + info.bounce = false; + info.bounced = true; + std::swap(info.src, info.dest); + bool to_mc = false; + if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc)) { + LOG(DEBUG) << "invalid destination address in a bounced message"; + bounce_phase.reset(); + return false; + } + // fetch message pricing info + const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain()); + // compute size of message + vm::CellStorageStat sstat; // for message size + // preliminary storage estimation of the resulting message + sstat.add_used_storage(info.value->prefetch_ref()); + sstat.add_used_storage(body.get_refs()); + bp.msg_bits = sstat.bits; + bp.msg_cells = sstat.cells; + // compute forwarding fees + bp.fwd_fees = msg_prices.compute_fwd_fees(sstat.cells, sstat.bits); + // check whether the message has enough funds + auto msg_balance = msg_balance_remaining; + if (compute_phase && compute_phase->gas_fees.not_null()) { + msg_balance.grams -= compute_phase->gas_fees; + } + if (action_phase && action_phase->action_fine.not_null()) { + msg_balance.grams -= action_phase->action_fine; + } + if ((msg_balance.grams < 0) || + (msg_balance.grams->signed_fits_bits(64) && msg_balance.grams->to_long() < (long long)bp.fwd_fees)) { + // not enough funds + bp.nofunds = true; + return true; + } + // debit msg_balance_remaining from account's (tentative) balance + balance -= msg_balance; + CHECK(balance.is_valid()); + // debit total forwarding fees from the message's balance, then split forwarding fees into our part and remaining part + msg_balance -= td::make_refint(bp.fwd_fees); + bp.fwd_fees_collected = msg_prices.get_first_part(bp.fwd_fees); + bp.fwd_fees -= bp.fwd_fees_collected; + total_fees += td::make_refint(bp.fwd_fees_collected); + // serialize outbound message + info.created_lt = start_lt + 1 + out_msgs.size(); + end_lt++; + info.created_at = now; + vm::CellBuilder cb; + CHECK(cb.store_long_bool(5, 4) // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool + && cb.append_cellslice_bool(info.src) // src:MsgAddressInt + && cb.append_cellslice_bool(info.dest) // dest:MsgAddressInt + && msg_balance.store(cb) // value:CurrencyCollection + && block::tlb::t_Grams.store_long(cb, 0) // extra_flags:(VarUInteger 16) + && block::tlb::t_Grams.store_long(cb, bp.fwd_fees) // fwd_fee:Grams + && cb.store_long_bool(info.created_lt, 64) // created_lt:uint64 + && cb.store_long_bool(info.created_at, 32) // created_at:uint32 + && cb.store_bool_bool(false)); // init:(Maybe ...) + if (cb.can_extend_by(1 + body.size(), body.size_refs())) { + // body:(Either X ^X) -> left X + CHECK(cb.store_bool_bool(false) && cb.append_builder_bool(body)); + } else { + // body:(Either X ^X) -> right ^X + CHECK(cb.store_bool_bool(true) && cb.store_builder_ref_bool(std::move(body))); + } + CHECK(cb.finalize_to(bp.out_msg)); + if (verbosity > 2) { + FLOG(INFO) { + sb << "generated bounced message: "; + block::gen::t_Message_Any.print_ref(sb, bp.out_msg); + }; + } + out_msgs.push_back(bp.out_msg); + bp.ok = true; + return true; +} +} // namespace transaction + +/* + * + * SERIALIZE PREPARED TRANSACTION + * + */ + +/** + * Stores the account status in a CellBuilder object. + * + * @param cb The CellBuilder object to store the account status in. + * @param acc_status The account status to store. + * + * @returns True if the account status was successfully stored, false otherwise. + */ +bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const { + int v; + switch (acc_status) { + case acc_nonexist: + case acc_deleted: + v = 3; // acc_state_nonexist$11 + break; + case acc_uninit: + v = 0; // acc_state_uninit$00 + break; + case acc_frozen: + v = 1; // acc_state_frozen$01 + break; + case acc_active: + v = 2; // acc_state_active$10 + break; + default: + return false; + } + return cb.store_long_bool(v, 2); +} + +namespace transaction { +/** + * Computes the new state of the account. + * + * @param cfg The configuration for the serialization phase. + * + * @returns True if the state computation is successful, false otherwise. + */ +bool Transaction::compute_state(const SerializeConfig& cfg) { + if (new_total_state.not_null()) { + return true; + } + if (acc_status == Account::acc_uninit && !was_activated && balance.is_zero()) { + LOG(DEBUG) << "account is uninitialized and has zero balance, deleting it back"; + acc_status = Account::acc_nonexist; + was_created = false; + } + if (acc_status == Account::acc_deleted && !balance.is_zero()) { + acc_status = Account::acc_uninit; + } + if (acc_status == Account::acc_nonexist || acc_status == Account::acc_deleted) { + CHECK(balance.is_zero()); + vm::CellBuilder cb; + CHECK(cb.store_long_bool(0, 1) // account_none$0 + && cb.finalize_to(new_total_state)); + return true; + } + vm::CellBuilder cb; + CHECK(cb.store_long_bool(end_lt, 64) // account_storage$_ last_trans_lt:uint64 + && balance.store(cb)); // balance:CurrencyCollection + int ticktock = new_tick * 2 + new_tock; + unsigned si_pos = 0; + int fixed_prefix_length = cfg.disable_anycast ? new_fixed_prefix_length : account.addr_rewrite_length; + if (acc_status == Account::acc_uninit) { + CHECK(cb.store_long_bool(0, 2)); // account_uninit$00 = AccountState + } else if (acc_status == Account::acc_frozen) { + if (was_frozen) { + vm::CellBuilder cb2; + CHECK(fixed_prefix_length ? cb2.store_long_bool(fixed_prefix_length + 32, 6) // _ ... = StateInit + : cb2.store_long_bool(0, 1)); // ... fixed_prefix_length:(Maybe (## 5)) + CHECK(ticktock ? cb2.store_long_bool(ticktock | 4, 3) : cb2.store_long_bool(0, 1)); // special:(Maybe TickTock) + CHECK(cb2.store_maybe_ref(new_code) && cb2.store_maybe_ref(new_data) && cb2.store_maybe_ref(new_library)); + // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) + auto frozen_state = cb2.finalize(); + frozen_hash = frozen_state->get_hash().bits(); + if (verbosity >= 3 * 1) { // !!!DEBUG!!! + FLOG(INFO) { + sb << "freezing state of smart contract: "; + block::gen::t_StateInit.print_ref(sb, frozen_state); + CHECK(block::gen::t_StateInit.validate_ref(frozen_state)); + CHECK(block::tlb::t_StateInit.validate_ref(frozen_state)); + sb << "with hash " << frozen_hash.to_hex(); + }; + } + } + new_code.clear(); + new_data.clear(); + new_library.clear(); + if (frozen_hash == account.addr_orig) { + // if frozen_hash equals account's "original" address (before rewriting), do not need storing hash + CHECK(cb.store_long_bool(0, 2)); // account_uninit$00 = AccountState + } else { + CHECK(cb.store_long_bool(1, 2) // account_frozen$01 + && cb.store_bits_bool(frozen_hash)); // state_hash:bits256 + } + } else { + CHECK(acc_status == Account::acc_active && !was_frozen && !was_deleted); + si_pos = cb.size_ext() + 1; + CHECK(fixed_prefix_length ? cb.store_long_bool(fixed_prefix_length + 96, 7) // account_active$1 _:StateInit + : cb.store_long_bool(2, 2)); // ... fixed_prefix_length:(Maybe (## 5)) + CHECK(ticktock ? cb.store_long_bool(ticktock | 4, 3) : cb.store_long_bool(0, 1)); // special:(Maybe TickTock) + CHECK(cb.store_maybe_ref(new_code) && cb.store_maybe_ref(new_data) && cb.store_maybe_ref(new_library)); + // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) + } + auto storage = cb.finalize(); + new_storage = td::Ref(true, vm::NoVm(), storage); + if (si_pos) { + auto cs_ref = load_cell_slice_ref(storage); + CHECK(cs_ref.unique_write().skip_ext(si_pos)); + new_inner_state = std::move(cs_ref); + } else { + new_inner_state.clear(); + } + + td::Ref old_storage_for_stat = account.storage; + td::Ref new_storage_for_stat = new_storage; + if (cfg.extra_currency_v2) { + new_storage_for_stat = storage_without_extra_currencies(new_storage); + if (new_storage_for_stat.is_null()) { + return false; + } + if (old_storage_for_stat.not_null()) { + old_storage_for_stat = storage_without_extra_currencies(old_storage_for_stat); + if (old_storage_for_stat.is_null()) { + return false; + } + } + } else if (cfg.store_storage_dict_hash) { + LOG(ERROR) << "unsupported store_storage_dict_hash=true, extra_currency_v2=false"; + return false; + } + + bool storage_refs_changed = false; + if (old_storage_for_stat.is_null() || new_storage_for_stat->size_refs() != old_storage_for_stat->size_refs()) { + storage_refs_changed = true; + } else { + for (unsigned i = 0; i < new_storage_for_stat->size_refs(); i++) { + if (new_storage_for_stat->prefetch_ref(i)->get_hash() != old_storage_for_stat->prefetch_ref(i)->get_hash()) { + storage_refs_changed = true; + break; + } + } + } + + bool store_storage_dict_hash = cfg.store_storage_dict_hash && !account.is_masterchain(); + if (storage_refs_changed || + (store_storage_dict_hash && !account.storage_dict_hash && account.storage_used.cells > 25)) { + TD_PERF_COUNTER(transaction_storage_stat_b); + td::Timer timer; + if (!new_account_storage_stat && account.account_storage_stat) { + new_account_storage_stat = AccountStorageStat(&account.account_storage_stat.value()); + if (compute_phase) { + new_account_storage_stat.value().add_hint(compute_phase->vm_loaded_cells); + } + } + AccountStorageStat& stats = new_account_storage_stat.value_force(); + // Don't check Merkle depth and size here - they were checked in check_state_limits + auto roots = new_storage_for_stat->prefetch_all_refs(); + storage_stat_updates.insert(storage_stat_updates.end(), roots.begin(), roots.end()); + { + td::RealCpuTimer timer; + StorageStatCalculationContext context{true}; + StorageStatCalculationContext::Guard guard{&context}; + td::Status S = stats.replace_roots(roots); + time_storage_stat += timer.elapsed_both(); + if (S.is_error()) { + LOG(ERROR) << "Cannot recompute storage stats for account " << account.addr.to_hex() << ": " << S.move_as_error(); + return false; + } + } + // Root of AccountStorage is not counted in AccountStorageStat + new_storage_used.cells = stats.get_total_cells() + 1; + new_storage_used.bits = stats.get_total_bits() + new_storage_for_stat->size(); + if (store_storage_dict_hash && new_storage_used.cells >= cfg.size_limits.acc_state_cells_for_storage_dict) { + auto r_hash = stats.get_dict_hash(); + if (r_hash.is_error()) { + LOG(ERROR) << "Cannot compute storage dict hash for account " << account.addr.to_hex() << ": " + << r_hash.move_as_error(); + return false; + } + new_storage_dict_hash = r_hash.move_as_ok(); + } + if (timer.elapsed() > 0.1) { + LOG(INFO) << "Compute used storage (2) took " << timer.elapsed() << "s"; + } + } else { + new_storage_used = account.storage_used; + new_storage_used.bits -= old_storage_for_stat->size(); + new_storage_used.bits += new_storage_for_stat->size(); + new_account_storage_stat = {}; + if (store_storage_dict_hash) { + new_storage_dict_hash = account.storage_dict_hash; + } + } + + CHECK(cb.store_long_bool(1, 1) // account$1 + && cb.append_cellslice_bool(cfg.disable_anycast ? my_addr : account.my_addr) // addr:MsgAddressInt + && block::store_UInt7(cb, new_storage_used.cells) // storage_used$_ cells:(VarUInteger 7) + && block::store_UInt7(cb, new_storage_used.bits) // bits:(VarUInteger 7) + && cb.store_long_bool(new_storage_dict_hash ? 1 : 0, 3) // extra:StorageExtraInfo + && (!new_storage_dict_hash || cb.store_bits_bool(new_storage_dict_hash.value())) // dict_hash:uint256 + && cb.store_long_bool(last_paid, 32)); // last_paid:uint32 + if (due_payment.not_null() && td::sgn(due_payment) != 0) { + CHECK(cb.store_long_bool(1, 1) && block::tlb::t_Grams.store_integer_ref(cb, due_payment)); + // due_payment:(Maybe Grams) + } else { + CHECK(cb.store_long_bool(0, 1)); + } + CHECK(cb.append_cellslice_bool(new_storage)); + new_total_state = cb.finalize(); + if (verbosity > 2) { + FLOG(INFO) { + sb << "new account state: "; + block::gen::t_Account.print_ref(sb, new_total_state); + }; + } + CHECK(block::tlb::t_Account.validate_ref(new_total_state)); + return true; +} + +/** + * Serializes the transaction object using Transaction TLB-scheme. + * + * Updates root. + * + * @param cfg The configuration for the serialization. + * + * @returns True if the serialization is successful, False otherwise. + */ +bool Transaction::serialize(const SerializeConfig& cfg) { + if (root.not_null()) { + return true; + } + if (!compute_state(cfg)) { + return false; + } + vm::Dictionary dict{15}; + for (unsigned i = 0; i < out_msgs.size(); i++) { + td::BitArray<15> key{i}; + if (!dict.set_ref(key, out_msgs[i], vm::Dictionary::SetMode::Add)) { + return false; + } + } + vm::CellBuilder cb, cb2; + if (!(cb.store_long_bool(7, 4) // transaction$0111 + && cb.store_bits_bool(account.addr) // account_addr:bits256 + && cb.store_long_bool(start_lt) // lt:uint64 + && cb.store_bits_bool(account.last_trans_hash_) // prev_trans_hash:bits256 + && cb.store_long_bool(account.last_trans_lt_, 64) // prev_trans_lt:uint64 + && cb.store_long_bool(account.now_, 32) // now:uint32 + && cb.store_ulong_rchk_bool(out_msgs.size(), 15) // outmsg_cnt:uint15 + && account.store_acc_status(cb) // orig_status:AccountStatus + && account.store_acc_status(cb, acc_status) // end_status:AccountStatus + && cb2.store_maybe_ref(in_msg) // ^[ in_msg:(Maybe ^(Message Any)) ... + && std::move(dict).append_dict_to_bool(cb2) // out_msgs:(HashmapE 15 ^(Message Any)) + && cb.store_ref_bool(cb2.finalize()) // ] + && total_fees.store(cb) // total_fees:CurrencyCollection + && cb2.store_long_bool(0x72, 8) // update_hashes#72 + && cb2.store_bits_bool(account.total_state->get_hash().bits(), 256) // old_hash:bits256 + && cb2.store_bits_bool(new_total_state->get_hash().bits(), 256) // new_hash:bits256 + && cb.store_ref_bool(cb2.finalize()))) { // state_update:^(HASH_UPDATE Account) + return false; + } + + switch (trans_type) { + case tr_tick: // fallthrough + case tr_tock: { + vm::CellBuilder cb3; + bool act = compute_phase->success; + bool act_ok = act && action_phase->success; + CHECK(cb2.store_long_bool(trans_type == tr_tick ? 2 : 3, 4) // trans_tick_tock$000 is_tock:Bool + && serialize_storage_phase(cb2) // storage:TrStoragePhase + && serialize_compute_phase(cb2) // compute_ph:TrComputePhase + && cb2.store_bool_bool(act) // action:(Maybe + && (!act || (serialize_action_phase(cb3) // ^TrActionPhase) + && cb2.store_ref_bool(cb3.finalize()))) && + cb2.store_bool_bool(!act_ok) // aborted:Bool + && cb2.store_bool_bool(was_deleted) // destroyed:Bool + && cb.store_ref_bool(cb2.finalize()) && cb.finalize_to(root)); + break; + } + case tr_ord: { + vm::CellBuilder cb3; + bool have_storage = (bool)storage_phase; + bool have_credit = (bool)credit_phase; + bool have_bounce = (bool)bounce_phase; + bool act = compute_phase->success; + bool act_ok = act && action_phase->success; + CHECK(cb2.store_long_bool(0, 4) // trans_ord$0000 + && cb2.store_long_bool(!bounce_enabled, 1) // credit_first:Bool + && cb2.store_bool_bool(have_storage) // storage_ph:(Maybe + && (!have_storage || serialize_storage_phase(cb2)) // TrStoragePhase) + && cb2.store_bool_bool(have_credit) // credit_ph:(Maybe + && (!have_credit || serialize_credit_phase(cb2)) // TrCreditPhase) + && serialize_compute_phase(cb2) // compute_ph:TrComputePhase + && cb2.store_bool_bool(act) // action:(Maybe + && (!act || (serialize_action_phase(cb3) && cb2.store_ref_bool(cb3.finalize()))) // ^TrActionPhase) + && cb2.store_bool_bool(!act_ok) // aborted:Bool + && cb2.store_bool_bool(have_bounce) // bounce:(Maybe + && (!have_bounce || serialize_bounce_phase(cb2)) // TrBouncePhase + && cb2.store_bool_bool(was_deleted) // destroyed:Bool + && cb.store_ref_bool(cb2.finalize()) && cb.finalize_to(root)); + break; + } + default: + return false; + } + if (verbosity >= 3 * 1) { + FLOG(INFO) { + sb << "new transaction: "; + block::gen::t_Transaction.print_ref(sb, root); + vm::load_cell_slice(root).print_rec(sb); + }; + } + + if (!block::gen::t_Transaction.validate_ref(4096, root)) { + LOG(ERROR) << "newly-generated transaction failed to pass automated validation:"; + FLOG(INFO) { + vm::load_cell_slice(root).print_rec(sb); + block::gen::t_Transaction.print_ref(sb, root); + }; + root.clear(); + return false; + } + if (!block::tlb::t_Transaction.validate_ref(4096, root)) { + LOG(ERROR) << "newly-generated transaction failed to pass hand-written validation:"; + FLOG(INFO) { + vm::load_cell_slice(root).print_rec(sb); + block::gen::t_Transaction.print_ref(sb, root); + }; + root.clear(); + return false; + } + + return true; +} + +/** + * Serializes the storage phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the serialization is successful, false otherwise. + */ +bool Transaction::serialize_storage_phase(vm::CellBuilder& cb) { + if (!storage_phase) { + return false; + } + StoragePhase& sp = *storage_phase; + bool ok; + // tr_phase_storage$_ storage_fees_collected:Grams + if (sp.fees_collected.not_null()) { + ok = block::tlb::t_Grams.store_integer_ref(cb, sp.fees_collected); + } else { + ok = block::tlb::t_Grams.null_value(cb); + } + // storage_fees_due:(Maybe Grams) + ok &= block::store_Maybe_Grams_nz(cb, sp.fees_due); + // status_change:AccStatusChange + if (sp.deleted || sp.frozen) { + ok &= cb.store_long_bool(sp.deleted ? 3 : 2, 2); // acst_frozen$10 acst_deleted$11 + } else { + ok &= cb.store_long_bool(0, 1); // acst_unchanged$0 = AccStatusChange + } + return ok; +} + +/** + * Serializes the credit phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the credit phase was successfully serialized, false otherwise. + */ +bool Transaction::serialize_credit_phase(vm::CellBuilder& cb) { + if (!credit_phase) { + return false; + } + CreditPhase& cp = *credit_phase; + // tr_phase_credit$_ due_fees_collected:(Maybe Grams) credit:CurrencyCollection + return block::store_Maybe_Grams_nz(cb, cp.due_fees_collected) && cp.credit.store(cb); +} + +/** + * Serializes the compute phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the serialization was successful, false otherwise. + */ +bool Transaction::serialize_compute_phase(vm::CellBuilder& cb) { + if (!compute_phase) { + return false; + } + ComputePhase& cp = *compute_phase; + switch (cp.skip_reason) { + // tr_compute_phase_skipped$0 reason:ComputeSkipReason; + case ComputePhase::sk_no_state: + return cb.store_long_bool(0, 3); // cskip_no_state$00 = ComputeSkipReason; + case ComputePhase::sk_bad_state: + return cb.store_long_bool(1, 3); // cskip_bad_state$01 = ComputeSkipReason; + case ComputePhase::sk_no_gas: + return cb.store_long_bool(2, 3); // cskip_no_gas$10 = ComputeSkipReason; + case ComputePhase::sk_suspended: + return cb.store_long_bool(0b0110, 4); // cskip_suspended$110 = ComputeSkipReason; + case ComputePhase::sk_none: + break; + default: + return false; + } + vm::CellBuilder cb2; + bool ok, credit = (cp.gas_credit != 0), exarg = (cp.exit_arg != 0); + ok = cb.store_long_bool(1, 1) // tr_phase_compute_vm$1 + && cb.store_long_bool(cp.success, 1) // success:Bool + && cb.store_long_bool(cp.msg_state_used, 1) // msg_state_used:Bool + && cb.store_long_bool(cp.account_activated, 1) // account_activated:Bool + && block::tlb::t_Grams.store_integer_ref(cb, cp.gas_fees) // gas_fees:Grams + && block::store_UInt7(cb2, cp.gas_used) // ^[ gas_used:(VarUInteger 7) + && block::store_UInt7(cb2, cp.gas_limit) // gas_limit:(VarUInteger 7) + && cb2.store_long_bool(credit, 1) // gas_credit:(Maybe (VarUInteger 3)) + && (!credit || block::tlb::t_VarUInteger_3.store_long(cb2, cp.gas_credit)) && + cb2.store_long_rchk_bool(cp.mode, 8) // mode:int8 + && cb2.store_long_bool(cp.exit_code, 32) // exit_code:int32 + && cb2.store_long_bool(exarg, 1) // exit_arg:(Maybe int32) + && (!exarg || cb2.store_long_bool(cp.exit_arg, 32)) && + cb2.store_ulong_rchk_bool(cp.vm_steps, 32) // vm_steps:uint32 + && cb2.store_bits_bool(cp.vm_init_state_hash) // vm_init_state_hash:bits256 + && cb2.store_bits_bool(cp.vm_final_state_hash) // vm_final_state_hash:bits256 + && cb.store_ref_bool(cb2.finalize()); // ] = TrComputePhase + return ok; +} + +/** + * Serializes the action phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the serialization is successful, false otherwise. + */ +bool Transaction::serialize_action_phase(vm::CellBuilder& cb) { + if (!action_phase) { + return false; + } + ActionPhase& ap = *action_phase; + bool ok, arg = (ap.result_arg != 0); + ok = cb.store_long_bool(ap.success, 1) // tr_phase_action$_ success:Bool + && cb.store_long_bool(ap.valid, 1) // valid:Bool + && cb.store_long_bool(ap.no_funds, 1) // no_funds:Bool + && cb.store_long_bool(ap.acc_status_change, (ap.acc_status_change >> 1) + 1) // status_change:AccStatusChange + && block::store_Maybe_Grams_nz(cb, ap.total_fwd_fees) // total_fwd_fees:(Maybe Grams) + && block::store_Maybe_Grams_nz(cb, ap.total_action_fees) // total_action_fees:(Maybe Grams) + && cb.store_long_bool(ap.result_code, 32) // result_code:int32 + && cb.store_long_bool(arg, 1) // result_arg:(Maybe + && (!arg || cb.store_long_bool(ap.result_arg, 32)) // uint32) + && cb.store_ulong_rchk_bool(ap.tot_actions, 16) // tot_actions:uint16 + && cb.store_ulong_rchk_bool(ap.spec_actions, 16) // spec_actions:uint16 + && cb.store_ulong_rchk_bool(ap.skipped_actions, 16) // skipped_actions:uint16 + && cb.store_ulong_rchk_bool(ap.msgs_created, 16) // msgs_created:uint16 + && cb.store_bits_bool(ap.action_list_hash) // action_list_hash:bits256 + && block::store_UInt7(cb, ap.tot_msg_cells, ap.tot_msg_bits); // tot_msg_size:StorageUsed + return ok; +} + +/** + * Serializes the bounce phase of a transaction. + * + * @param cb The CellBuilder to store the serialized data. + * + * @returns True if the bounce phase was successfully serialized, false otherwise. + */ +bool Transaction::serialize_bounce_phase(vm::CellBuilder& cb) { + if (!bounce_phase) { + return false; + } + BouncePhase& bp = *bounce_phase; + if (!(bp.ok ^ bp.nofunds)) { + return false; + } + if (bp.nofunds) { + return cb.store_long_bool(1, 2) // tr_phase_bounce_nofunds$01 + && block::store_UInt7(cb, bp.msg_cells, bp.msg_bits) // msg_size:StorageUsed + && block::tlb::t_Grams.store_long(cb, bp.fwd_fees); // req_fwd_fees:Grams + } else { + return cb.store_long_bool(1, 1) // tr_phase_bounce_ok$1 + && block::store_UInt7(cb, bp.msg_cells, bp.msg_bits) // msg_size:StorageUsed + && block::tlb::t_Grams.store_long(cb, bp.fwd_fees_collected) // msg_fees:Grams + && block::tlb::t_Grams.store_long(cb, bp.fwd_fees); // fwd_fees:Grams + } +} + +/** + * Estimates the block storage profile increment if the transaction is added to the block. + * + * @param store_stat The current storage statistics of the block. + * @param usage_tree The usage tree of the block. + * + * @returns The estimated block storage profile increment. + * Returns Error if the transaction is not serialized or if its new state is not computed. + */ +td::Result Transaction::estimate_block_storage_profile_incr( + const vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const { + if (root.is_null()) { + return td::Status::Error("Cannot estimate the size profile of a transaction before it is serialized"); + } + if (new_total_state.is_null()) { + return td::Status::Error("Cannot estimate the size profile of a transaction before its new state is computed"); + } + return store_stat.tentative_add_proof(new_total_state, usage_tree) + store_stat.tentative_add_cell(root); +} + +/** + * Updates the limits status of a block. + * + * @param blimst The block limit status object to update. + * @param with_size Flag indicating whether to update the size limits. + * + * @returns True if the limits were successfully updated, False otherwise. + */ +bool Transaction::update_limits(block::BlockLimitStatus& blimst, bool with_gas, bool with_size) const { + if (!(blimst.update_lt(end_lt) && blimst.update_gas(with_gas ? gas_used() : 0))) { + return false; + } + if (with_size) { + if (!(blimst.add_proof(new_total_state) && blimst.add_cell(root) && blimst.add_transaction() && + blimst.add_account(is_first))) { + return false; + } + if (account.is_masterchain()) { + if (was_frozen || was_deleted) { + blimst.public_library_diff += get_public_libraries_count(account.orig_library); + } else { + blimst.public_library_diff += get_public_libraries_diff_count(account.orig_library, new_library); + } + } + } + return true; +} + +/* + * + * COMMIT TRANSACTION + * + */ + +/** + * Commits a transaction for a given account. + * + * @param acc The account to commit the transaction for. + * + * @returns A reference to the root cell of the serialized transaction. + */ +Ref Transaction::commit(Account& acc) { + CHECK(account.last_trans_end_lt_ <= start_lt && start_lt < end_lt); + CHECK(root.not_null()); + CHECK(new_total_state.not_null()); + CHECK((const void*)&acc == (const void*)&account); + // export all fields modified by the Transaction into original account + // NB: this is the only method that modifies account + if (force_remove_anycast_address) { + CHECK(acc.forget_addr_rewrite_length()); + } else if (orig_addr_rewrite_set && new_addr_rewrite_length >= 0 && acc.status != Account::acc_active && + acc_status == Account::acc_active) { + LOG(DEBUG) << "setting address rewriting info for newly-activated account " << acc.addr.to_hex() + << " with addr_rewrite_length=" << new_addr_rewrite_length + << ", orig_addr_rewrite=" << orig_addr_rewrite.bits().to_hex(new_addr_rewrite_length); + CHECK(acc.init_rewrite_addr(new_addr_rewrite_length, orig_addr_rewrite.bits())); + } + acc.status = (acc_status == Account::acc_deleted ? Account::acc_nonexist : acc_status); + acc.last_trans_lt_ = start_lt; + acc.last_trans_end_lt_ = end_lt; + acc.last_trans_hash_ = root->get_hash().bits(); + acc.last_paid = last_paid; + acc.storage_used = new_storage_used; + if (new_account_storage_stat) { + if (acc.account_storage_stat) { + acc.account_storage_stat.value().apply_child_stat(std::move(new_account_storage_stat.value())); + } else { + acc.account_storage_stat = std::move(new_account_storage_stat); + } + } + acc.storage_dict_hash = new_storage_dict_hash; + acc.storage = new_storage; + acc.balance = std::move(balance); + acc.due_payment = std::move(due_payment); + acc.total_state = std::move(new_total_state); + acc.inner_state = std::move(new_inner_state); + if (was_frozen) { + acc.state_hash = frozen_hash; + } + acc.my_addr = std::move(my_addr); + // acc.my_addr_exact = std::move(my_addr_exact); + acc.code = std::move(new_code); + acc.data = std::move(new_data); + acc.library = std::move(new_library); + if (acc.status == Account::acc_active) { + acc.tick = new_tick; + acc.tock = new_tock; + acc.fixed_prefix_length = new_fixed_prefix_length; + } else { + CHECK(acc.deactivate()); + } + end_lt = 0; + acc.push_transaction(root, start_lt); + return root; +} + +/** + * Extracts the output message at the specified index from the transaction. + * + * @param i The index of the output message to extract. + * + * @returns A pair of the logical time and the extracted output message. + */ +LtCellRef Transaction::extract_out_msg(unsigned i) { + return {start_lt + i + 1, std::move(out_msgs.at(i))}; +} + +/** + * Extracts the output message at index i from the transaction. + * + * @param i The index of the output message to extract. + * + * @returns A triple of the logical time, the extracted output message and the transaction root. + */ +NewOutMsg Transaction::extract_out_msg_ext(unsigned i) { + return {start_lt + i + 1, std::move(out_msgs.at(i)), root, i}; +} + +/** + * Extracts the outgoing messages from the transaction and adds them to the given list. + * + * @param list The list to which the outgoing messages will be added. + */ +void Transaction::extract_out_msgs(std::vector& list) { + for (unsigned i = 0; i < out_msgs.size(); i++) { + list.emplace_back(start_lt + i + 1, std::move(out_msgs[i])); + } +} +} // namespace transaction + +/** + * Adds a transaction to the account's transaction list. + * + * @param trans_root The root of the transaction cell. + * @param trans_lt The logical time of the transaction. + */ +void Account::push_transaction(Ref trans_root, ton::LogicalTime trans_lt) { + transactions.emplace_back(trans_lt, std::move(trans_root)); +} + +/** + * Serializes an account block for the account using AccountBlock TLB-scheme. + * + * @param cb The CellBuilder used to store the serialized data. + * + * @returns True if the account block was successfully created, false otherwise. + */ +bool Account::create_account_block(vm::CellBuilder& cb) { + if (transactions.empty()) { + return false; + } + if (!(cb.store_long_bool(5, 4) // acc_trans#5 + && cb.store_bits_bool(addr))) { // account_addr:bits256 + return false; + } + vm::AugmentedDictionary dict{64, block::tlb::aug_AccountTransactions}; + for (auto& z : transactions) { + if (!dict.set_ref(td::BitArray<64>{(long long)z.first}, z.second, vm::Dictionary::SetMode::Add)) { + LOG(ERROR) << "error creating the list of transactions for account " << addr.to_hex() + << " : cannot add transaction with lt=" << z.first; + return false; + } + } + Ref dict_root = std::move(dict).extract_root_cell(); + // transactions:(HashmapAug 64 ^Transaction Grams) + if (dict_root.is_null() || !cb.append_cellslice_bool(vm::load_cell_slice(std::move(dict_root)))) { + return false; + } + vm::CellBuilder cb2; + return cb2.store_long_bool(0x72, 8) // update_hashes#72 + && cb2.store_bits_bool(orig_total_state->get_hash().bits(), 256) // old_hash:bits256 + && cb2.store_bits_bool(total_state->get_hash().bits(), 256) // new_hash:bits256 + && cb.store_ref_bool(cb2.finalize()); // state_update:^(HASH_UPDATE Account) +} + +/** + * Checks if the libraries stored in the account object have changed. + * + * @returns True if the libraries have changed, False otherwise. + */ +bool Account::libraries_changed() const { + bool s = orig_library.not_null(); + bool t = library.not_null(); + if (s & t) { + return orig_library->get_hash() != library->get_hash(); + } else { + return s != t; + } +} + +/** + * Fetches and initializes various configuration parameters from masterchain config for transaction processing. + * + * @param config The masterchain configuration. + * @param old_mparams Pointer to store a dictionary of mandatory parameters (ConfigParam 9). + * @param storage_prices Pointer to store the storage prices. + * @param storage_phase_cfg Pointer to store the storage phase configuration. + * @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`. + * @param compute_phase_cfg Pointer to store the compute phase configuration. + * @param action_phase_cfg Pointer to store the action phase configuration. + * @param serialize_cfg Pointer to store the serialize phase configuration. + * @param masterchain_create_fee Pointer to store the masterchain create fee. + * @param basechain_create_fee Pointer to store the basechain create fee. + * @param wc The workchain ID. + * @param now The current Unix time. + */ +td::Status FetchConfigParams::fetch_config_params( + const block::ConfigInfo& config, Ref* old_mparams, std::vector* storage_prices, + StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, + ActionPhaseConfig* action_phase_cfg, SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee, + td::RefInt256* basechain_create_fee, ton::WorkchainId wc, ton::UnixTime now) { + auto prev_blocks_info = config.get_prev_blocks_info(); + if (prev_blocks_info.is_error()) { + return prev_blocks_info.move_as_error_prefix( + td::Status::Error(-668, "cannot fetch prev blocks info from masterchain configuration: ")); + } + return fetch_config_params(config, prev_blocks_info.move_as_ok(), old_mparams, storage_prices, storage_phase_cfg, + rand_seed, compute_phase_cfg, action_phase_cfg, serialize_cfg, masterchain_create_fee, + basechain_create_fee, wc, now); +} + +/** + * Fetches and initializes various configuration parameters from masterchain config for transaction processing. + * + * @param config The masterchain configuration. + * @param prev_blocks_info The tuple with information about previous blocks. + * @param old_mparams Pointer to store a dictionary of mandatory parameters (ConfigParam 9). + * @param storage_prices Pointer to store the storage prices. + * @param storage_phase_cfg Pointer to store the storage phase configuration. + * @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`. + * @param compute_phase_cfg Pointer to store the compute phase configuration. + * @param action_phase_cfg Pointer to store the action phase configuration. + * @param serialize_cfg Pointer to store the serialize phase configuration. + * @param masterchain_create_fee Pointer to store the masterchain create fee. + * @param basechain_create_fee Pointer to store the basechain create fee. + * @param wc The workchain ID. + * @param now The current Unix time. + */ +td::Status FetchConfigParams::fetch_config_params( + const block::Config& config, td::Ref prev_blocks_info, Ref* old_mparams, + std::vector* storage_prices, StoragePhaseConfig* storage_phase_cfg, + td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg, + SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, + ton::WorkchainId wc, ton::UnixTime now) { + *old_mparams = config.get_config_param(9); + { + auto res = config.get_storage_prices(); + if (res.is_error()) { + return res.move_as_error(); + } + *storage_prices = res.move_as_ok(); + } + if (rand_seed->is_zero()) { + // generate rand seed + prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); + LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); + } + TRY_RESULT(size_limits, config.get_size_limits_config()); + { + // compute compute_phase_cfg / storage_phase_cfg + auto cell = config.get_config_param(wc == ton::masterchainId ? 20 : 21); + if (cell.is_null()) { + return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); + } + if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, + storage_phase_cfg->delete_due_limit)) { + return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); + } + TRY_RESULT_PREFIX(mc_gas_prices, config.get_gas_limits_prices(true), + "cannot unpack masterchain gas prices and limits: "); + compute_phase_cfg->mc_gas_prices = std::move(mc_gas_prices); + compute_phase_cfg->special_gas_full = config.get_global_version() >= 5; + storage_phase_cfg->enable_due_payment = config.get_global_version() >= 4; + storage_phase_cfg->global_version = config.get_global_version(); + compute_phase_cfg->block_rand_seed = *rand_seed; + compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; + compute_phase_cfg->global_config = config.get_root_cell(); + compute_phase_cfg->global_version = config.get_global_version(); + if (compute_phase_cfg->global_version >= 4) { + compute_phase_cfg->prev_blocks_info = std::move(prev_blocks_info); + } + if (compute_phase_cfg->global_version >= 6) { + compute_phase_cfg->unpacked_config_tuple = config.get_unpacked_config_tuple(now); + } + compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now); + compute_phase_cfg->size_limits = size_limits; + compute_phase_cfg->precompiled_contracts = config.get_precompiled_contracts_config(); + compute_phase_cfg->allow_external_unfreeze = compute_phase_cfg->global_version >= 8; + compute_phase_cfg->disable_anycast = config.get_global_version() >= 10; + } + { + // compute action_phase_cfg + block::gen::MsgForwardPrices::Record rec; + auto cell = config.get_config_param(24); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_mc = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + cell = config.get_config_param(25); + if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { + return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); + } + action_phase_cfg->fwd_std = + block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, + (unsigned)rec.first_frac, (unsigned)rec.next_frac}; + action_phase_cfg->workchains = &config.get_workchain_list(); + action_phase_cfg->bounce_msg_body = (config.has_capability(ton::capBounceMsgBody) ? 256 : 0); + action_phase_cfg->size_limits = size_limits; + action_phase_cfg->action_fine_enabled = config.get_global_version() >= 4; + action_phase_cfg->bounce_on_fail_enabled = config.get_global_version() >= 4; + action_phase_cfg->message_skip_enabled = config.get_global_version() >= 8; + action_phase_cfg->disable_custom_fess = config.get_global_version() >= 8; + action_phase_cfg->reserve_extra_enabled = config.get_global_version() >= 9; + action_phase_cfg->mc_blackhole_addr = config.get_burning_config().blackhole_addr; + action_phase_cfg->extra_currency_v2 = config.get_global_version() >= 10; + action_phase_cfg->disable_anycast = config.get_global_version() >= 10; + action_phase_cfg->disable_ihr_flag = config.get_global_version() >= 11; + action_phase_cfg->global_version = config.get_global_version(); + } + { + serialize_cfg->extra_currency_v2 = config.get_global_version() >= 10; + serialize_cfg->disable_anycast = config.get_global_version() >= 10; + serialize_cfg->store_storage_dict_hash = config.get_global_version() >= 11; + serialize_cfg->size_limits = size_limits; + } + { + // fetch block_grams_created + auto cell = config.get_config_param(14); + if (cell.is_null()) { + *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); + } else { + block::gen::BlockCreateFees::Record create_fees; + if (!(tlb::unpack_cell(cell, create_fees) && + block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && + block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { + return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); + } + } + } + return td::Status::OK(); +} + +} // namespace block \ No newline at end of file diff --git a/standard/wallets/comparison/tsconfig.json b/standard/wallets/comparison/tsconfig.json new file mode 100644 index 0000000..c6f6c22 --- /dev/null +++ b/standard/wallets/comparison/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "ES2020" + ], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node" + }, + "include": [ + "wrappers/**/*", + "tests/**/*", + "scripts/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/standard/wallets/comparison/utils.ts b/standard/wallets/comparison/utils.ts new file mode 100644 index 0000000..bfaca13 --- /dev/null +++ b/standard/wallets/comparison/utils.ts @@ -0,0 +1,7 @@ +const getRandom = (min:number, max:number) => { + return Math.random() * (max - min) + min; +} + +export const getRandomInt = (min: number, max: number) => { + return Math.round(getRandom(min, max)); +} diff --git a/standard/wallets/comparison/wrappers/HighloadQueryId.ts b/standard/wallets/comparison/wrappers/HighloadQueryId.ts new file mode 100644 index 0000000..4c994c2 --- /dev/null +++ b/standard/wallets/comparison/wrappers/HighloadQueryId.ts @@ -0,0 +1,81 @@ +const BIT_NUMBER_SIZE = 10n; // 10 bit +const SHIFT_SIZE = 13n; // 13 bit +const MAX_BIT_NUMBER = 1022n; +const MAX_SHIFT = 8191n; // 2^13 = 8192 + +export class HighloadQueryId { + private shift: bigint; // [0 .. 8191] + private bitnumber: bigint; // [0 .. 1022] + + constructor() { + this.shift = 0n; + this.bitnumber = 0n; + } + + static fromShiftAndBitNumber(shift: bigint, bitnumber: bigint): HighloadQueryId { + const q = new HighloadQueryId(); + q.shift = shift; + if (q.shift < 0) throw new Error('invalid shift'); + if (q.shift > MAX_SHIFT) throw new Error('invalid shift'); + q.bitnumber = bitnumber; + if (q.bitnumber < 0) throw new Error('invalid bitnumber'); + if (q.bitnumber > MAX_BIT_NUMBER) throw new Error('invalid bitnumber'); + return q; + } + + + getNext() { + let newBitnumber = this.bitnumber + 1n; + let newShift = this.shift; + + if (newShift === MAX_SHIFT && newBitnumber > (MAX_BIT_NUMBER - 1n)) { + throw new Error('Overload'); // NOTE: we left one queryId for emergency withdraw + } + + if (newBitnumber > MAX_BIT_NUMBER) { + newBitnumber = 0n; + newShift += 1n; + if (newShift > MAX_SHIFT) { + throw new Error('Overload') + } + } + + return HighloadQueryId.fromShiftAndBitNumber(newShift, newBitnumber); + } + + hasNext() { + const isEnd = this.bitnumber >= (MAX_BIT_NUMBER - 1n) && this.shift === MAX_SHIFT; // NOTE: we left one queryId for emergency withdraw; + return !isEnd; + } + + getShift(): bigint { + return this.shift; + } + + getBitNumber(): bigint { + return this.bitnumber; + } + + getQueryId(): bigint { + return (this.shift << BIT_NUMBER_SIZE) + this.bitnumber; + } + + static fromQueryId(queryId: bigint): HighloadQueryId { + const shift = queryId >> BIT_NUMBER_SIZE; + const bitnumber = queryId & 1023n; + return this.fromShiftAndBitNumber(shift, bitnumber); + } + + static fromSeqno(i: bigint): HighloadQueryId { + const shift = i / 1023n; + const bitnumber = i % 1023n; + return this.fromShiftAndBitNumber(shift, bitnumber); + } + + /** + * @return {bigint} [0 .. 8380415] + */ + toSeqno(): bigint { + return this.bitnumber + this.shift * 1023n; + } +} \ No newline at end of file diff --git a/standard/wallets/comparison/wrappers/HighloadWalletV3.ts b/standard/wallets/comparison/wrappers/HighloadWalletV3.ts new file mode 100644 index 0000000..005fae6 --- /dev/null +++ b/standard/wallets/comparison/wrappers/HighloadWalletV3.ts @@ -0,0 +1,216 @@ +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + internal as internal_relaxed, + MessageRelaxed, + OutAction, + OutActionSendMsg, + Sender, + SendMode, + storeMessageRelaxed, + storeOutList, + toNano +} from '@ton/core'; +import { sign } from "@ton/crypto"; +import { OP } from "../tests/imports/const"; +import { HighloadQueryId } from "./HighloadQueryId"; + +const HighloadWalletV3CodeHex = "b5ee9c7241021001000228000114ff00f4a413f4bcf2c80b01020120020d02014803040078d020d74bc00101c060b0915be101d0d3030171b0915be0fa4030f828c705b39130e0d31f018210ae42e5a4ba9d8040d721d74cf82a01ed55fb04e030020120050a02027306070011adce76a2686b85ffc00201200809001aabb6ed44d0810122d721d70b3f0018aa3bed44d08307d721d70b1f0201200b0c001bb9a6eed44d0810162d721d70b15800e5b8bf2eda2edfb21ab09028409b0ed44d0810120d721f404f404d33fd315d1058e1bf82325a15210b99f326df82305aa0015a112b992306dde923033e2923033e25230800df40f6fa19ed021d721d70a00955f037fdb31e09130e259800df40f6fa19cd001d721d70a00937fdb31e0915be270801f6f2d48308d718d121f900ed44d0d3ffd31ff404f404d33fd315d1f82321a15220b98e12336df82324aa00a112b9926d32de58f82301de541675f910f2a106d0d31fd4d307d30cd309d33fd315d15168baf2a2515abaf2a6f8232aa15250bcf2a304f823bbf2a35304800df40f6fa199d024d721d70a00f2649130e20e01fe5309800df40f6fa18e13d05004d718d20001f264c858cf16cf8301cf168e1030c824cf40cf8384095005a1a514cf40e2f800c94039800df41704c8cbff13cb1ff40012f40012cb3f12cb15c9ed54f80f21d0d30001f265d3020171b0925f03e0fa4001d70b01c000f2a5fa4031fa0031f401fa0031fa00318060d721d300010f0020f265d2000193d431d19130e272b1fb00b585bf03"; + +export const HighloadWalletV3Code = Cell.fromBoc(Buffer.from(HighloadWalletV3CodeHex, "hex"))[0] + +export type HighloadWalletV3Config = { + publicKey: Buffer, + subwalletId: number, + timeout: number +}; + + +export const TIMESTAMP_SIZE = 64; +export const TIMEOUT_SIZE = 22; + +export function highloadWalletV3ConfigToCell(config: HighloadWalletV3Config): Cell { + return beginCell() + .storeBuffer(config.publicKey) + .storeUint(config.subwalletId, 32) + .storeUint(0, 1 + 1 + TIMESTAMP_SIZE) + .storeUint(config.timeout, TIMEOUT_SIZE) + .endCell(); +} + +export class HighloadWalletV3 implements Contract { + + constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) { + } + + static createFromAddress(address: Address) { + return new HighloadWalletV3(address); + } + + static createFromConfig(config: HighloadWalletV3Config, code: Cell, workchain = 0) { + const data = highloadWalletV3ConfigToCell(config); + const init = { code, data }; + return new HighloadWalletV3(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + bounce: false, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell(), + }); + } + + async sendExternalMessage( + provider: ContractProvider, + secretKey: Buffer, + opts: { + message: MessageRelaxed | Cell, + mode: number, + query_id: bigint | HighloadQueryId, + createdAt: number, + subwalletId: number, + timeout: number, + } + ) { + let messageCell: Cell; + + if (opts.message instanceof Cell) { + messageCell = opts.message + } else { + const messageBuilder = beginCell(); + messageBuilder.store(storeMessageRelaxed(opts.message)) + messageCell = messageBuilder.endCell(); + } + + const queryId = (opts.query_id instanceof HighloadQueryId) ? opts.query_id.getQueryId() : opts.query_id; + + const messageInner = beginCell() + .storeUint(opts.subwalletId, 32) + .storeRef(messageCell) + .storeUint(opts.mode, 8) + .storeUint(queryId, 23) + .storeUint(opts.createdAt, TIMESTAMP_SIZE) + .storeUint(opts.timeout, TIMEOUT_SIZE) + .endCell(); + + await provider.external( + beginCell() + .storeBuffer(sign(messageInner.hash(), secretKey)) + .storeRef(messageInner) + .endCell() + ); + } + + async sendBatch(provider: ContractProvider, secretKey: Buffer, messages: OutActionSendMsg[], subwallet: number, query_id: HighloadQueryId, timeout: number, createdAt?: number, value: bigint = 0n) { + if (createdAt == undefined) { + createdAt = Math.floor(Date.now() / 1000); + } + return await this.sendExternalMessage(provider, secretKey, { + message: this.packActions(messages, value, query_id), + mode: value > 0n ? SendMode.PAY_GAS_SEPARATELY : SendMode.CARRY_ALL_REMAINING_BALANCE, + query_id: query_id, + createdAt: createdAt, + subwalletId: subwallet, + timeout: timeout + }); + } + + static createInternalTransferBody(opts: { + actions: OutAction[] | Cell, + queryId: HighloadQueryId, + }) { + let actionsCell: Cell; + if (opts.actions instanceof Cell) { + actionsCell = opts.actions; + } else { + if (opts.actions.length > 254) { + throw TypeError("Max allowed action count is 254. Use packActions instead."); + } + const actionsBuilder = beginCell(); + storeOutList(opts.actions)(actionsBuilder); + actionsCell = actionsBuilder.endCell(); + } + return beginCell().storeUint(OP.InternalTransfer, 32) + .storeUint(opts.queryId.getQueryId(), 64) + .storeRef(actionsCell) + .endCell(); + + + } + + createInternalTransfer(opts: { + actions: OutAction[] | Cell + queryId: HighloadQueryId, + value: bigint + }) { + + return internal_relaxed({ + to: this.address, + value: opts.value, + body: HighloadWalletV3.createInternalTransferBody(opts) + }); + /*beginCell() + .storeUint(0x10, 6) + .storeAddress(this.address) + .storeCoins(opts.value) + .storeUint(0, 107) + .storeSlice(body.asSlice()) + .endCell(); + */ + } + + packActions(messages: OutAction[], value: bigint = toNano('1'), query_id: HighloadQueryId) { + let batch: OutAction[]; + if (messages.length > 254) { + batch = messages.slice(0, 253); + batch.push({ + type: 'sendMsg', + mode: value > 0n ? SendMode.PAY_GAS_SEPARATELY : SendMode.CARRY_ALL_REMAINING_BALANCE, + outMsg: this.packActions(messages.slice(253), value, query_id) + }); + } else { + batch = messages; + } + return this.createInternalTransfer({ + actions: batch, + queryId: query_id, + value + }); + } + + + async getPublicKey(provider: ContractProvider): Promise { + const res = (await provider.get('get_public_key', [])).stack; + const pubKeyU = res.readBigNumber(); + return Buffer.from(pubKeyU.toString(16).padStart(32 * 2, '0'), 'hex'); + } + + async getSubwalletId(provider: ContractProvider): Promise { + const res = (await provider.get('get_subwallet_id', [])).stack; + return res.readNumber(); + } + + async getTimeout(provider: ContractProvider): Promise { + const res = (await provider.get('get_timeout', [])).stack; + return res.readNumber(); + } + + async getLastCleaned(provider: ContractProvider): Promise { + const res = (await provider.get('get_last_clean_time', [])).stack; + return res.readNumber(); + } + + async getProcessed(provider: ContractProvider, queryId: HighloadQueryId, needClean = true): Promise { + const res = (await provider.get('processed?', [{ 'type': 'int', 'value': queryId.getQueryId() }, { + 'type': 'int', + 'value': needClean ? -1n : 0n + }])).stack; + return res.readBoolean(); + } +} diff --git a/standard/wallets/comparison/wrappers/MsgGenerator.ts b/standard/wallets/comparison/wrappers/MsgGenerator.ts new file mode 100644 index 0000000..8618587 --- /dev/null +++ b/standard/wallets/comparison/wrappers/MsgGenerator.ts @@ -0,0 +1,134 @@ +import { Cell, CommonMessageInfoExternalIn, CommonMessageInfoExternalOut, ExternalAddress, Message, MessageRelaxed, StateInit, beginCell, external, storeMessage, storeMessageRelaxed } from '@ton/core'; +import { randomAddress } from '@ton/test-utils'; +export class MsgGenerator { + constructor(readonly wc: number){} + + generateExternalOutWithBadSource() { + const ssrcInvalid = beginCell() + .storeUint(2, 2) // addr_std$10 + .storeUint(0, 1) // anycast nothing + .storeInt(this.wc, 8) // workchain_id: -1 + .storeUint(1, 10) + .endCell() + + return beginCell() + .storeUint(3, 2) // ext_out_msg_info$11 + .storeBit(0) // src:INVALID + .storeSlice(ssrcInvalid.beginParse()) + .endCell(); + + } + generateExternalOutWithBadDst() { + const src = randomAddress(-1); + return beginCell() + .storeUint(3, 2) // ext_out_msg_info$11 + .storeAddress(src) // src:MsgAddressInt + .storeBit(0) // dest:INVALID + .endCell(); + } + generateExternalInWithBadSource() { + const ssrcInvalid = beginCell() + .storeUint(1, 2) // addrExtern$01 + .storeUint(128, 9) + .storeUint(0, 10) + .endCell() + + return beginCell() + .storeUint(2, 2) //ext_in_msg_info$11 + .storeSlice(ssrcInvalid.beginParse()) // src:INVALID + .endCell(); + } + generateExternalInWithBadDst() { + const src = new ExternalAddress(BigInt(Date.now()), 256); + return beginCell() + .storeUint(2, 2) //ext_in_msg_info$10 + .storeAddress(src) // src:MsgAddressExt + .storeBit(0) // dest:INVALID + .endCell(); + } + generateInternalMessageWithBadGrams() { + const src = randomAddress(this.wc); + const dst = randomAddress(this.wc); + return beginCell() + .storeUint(0, 1) // int_msg_info$0 + .storeUint(0, 1) // ihr_disabled:Bool + .storeUint(0, 1) // bounce:Bool + .storeUint(0, 1) // bounced:Bool + .storeAddress(src) // src:MsgAddress + .storeAddress(dst) // dest:MsgAddress + .storeUint(8, 4) // len of nanograms + .storeUint(1, 1) // INVALID GRAMS amount + .endCell(); + + } + generateInternalMessageWithBadInitStateData() { + const ssrc = randomAddress(this.wc); + const sdest = randomAddress(this.wc); + + const init_state_with_bad_data = beginCell().storeUint(0, 1) // maybe (##5) + .storeUint(1, 1) // Maybe TickTock + .storeUint(1, 1) // bool Tick + .storeUint(0, 1) // bool Tock + .storeUint(1, 1) // code: Maybe Cell^ + .storeUint(1, 1) // data: Maybe Cell^ + .storeUint(1, 1); // library: Maybe ^Cell + // bits for references but no data + + return beginCell() + .storeUint(0, 1) // int_msg_info$0 + .storeUint(0, 1) // ihr_disabled:Bool + .storeUint(0, 1) // bounce:Bool + .storeUint(0, 1) // bounced:Bool + .storeAddress(ssrc) // src:MsgAddress + .storeAddress(sdest) // dest:MsgAddress + .storeCoins(0) // + .storeMaybeRef(null) // extra currencies + .storeCoins(0) // ihr_fee + .storeCoins(0) // fwd_fee + .storeUint(1000, 64) // created_lt:uint64 + .storeUint(1000, 32) // created_at:uint32 + .storeUint(1, 1) // Maybe init_state + .storeUint(1, 1) // Either (X ^X) init state + .storeRef(init_state_with_bad_data.endCell()) + .storeUint(0, 1) // Either (X ^X) body + .endCell(); +} + + *generateBadMsg() { + // Meh + yield this.generateExternalInWithBadDst(); + yield this.generateExternalOutWithBadDst(); + yield this.generateExternalInWithBadSource(); + yield this.generateExternalOutWithBadSource(); + yield this.generateInternalMessageWithBadGrams(); + yield this.generateInternalMessageWithBadInitStateData(); + } + generateExternalInMsg(info?: Partial, body?: Cell, init?: StateInit) { + const msgInfo: CommonMessageInfoExternalIn = { + type: 'external-in', + dest: info?.dest || randomAddress(this.wc), + src: info?.src, + importFee: info?.importFee || 0n + } + const newMsg: Message = { + info: msgInfo, + body: body || Cell.EMPTY, + init + } + return beginCell().store(storeMessage(newMsg)).endCell(); + } + generateExternalOutMsg(info?: Partial, body?: Cell) { + const msgInfo: CommonMessageInfoExternalOut = { + type: 'external-out', + createdAt: info?.createdAt || 0, + createdLt: info?.createdLt || 0n, + src: info?.src || randomAddress(this.wc), + dest: info?.dest + } + const newMsg: MessageRelaxed = { + info: msgInfo, + body: body || Cell.EMPTY, + } + return beginCell().store(storeMessageRelaxed(newMsg)).endCell(); + } +} diff --git a/standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts b/standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts new file mode 100644 index 0000000..d79365e --- /dev/null +++ b/standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts @@ -0,0 +1,177 @@ +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + Sender, + SendMode, + Slice, + StateInit, + storeStateInit, + OutAction, + OutActionSendMsg, + OutActionSetCode, + storeOutList, +} from '@ton/core'; +import { KeyPair, sign } from '@ton/crypto'; + +const MAX_ACTIONS = 255; +const DEFAULT_VALID_UNTIL_OFFSET = 60; + + +export const walletCode = Cell.fromBoc( + Buffer.from( + 'B5EE9C7241010101003D000076FF00DDD40120F90001D0D33FD30FD74CED44D0D3FFD70B0F20A4830FA90822C8CBFFCB0FC9ED5444301046BAF2A1F823BEF2A2F910F2A3F800ED552E766412', + 'hex' + ) +)[0]; + +export type TransferMessage = { + to: Address; + value: bigint; + body?: Cell; + mode?: SendMode; + bounce?: boolean; + init?: StateInit; +}; + +export function createTransferAction(msg: TransferMessage): OutActionSendMsg { + const bounce = msg.bounce ?? true; + + return { + type: 'sendMsg', + mode: msg.mode ?? SendMode.PAY_GAS_SEPARATELY, + outMsg: { + info: { + type: 'internal', + ihrDisabled: true, + bounce: bounce, + bounced: false, + dest: msg.to, + value: { coins: msg.value }, + ihrFee: 0n, + forwardFee: 0n, + createdLt: 0n, + createdAt: 0 + }, + init: msg.init, + body: msg.body || Cell.EMPTY + } + }; +} + +export function createSetCodeAction(code: Cell): OutActionSetCode { + return { + type: 'setCode', + newCode: code + }; +} + + +export class Wallet implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell } + ) { } + + static createFromAddress(address: Address) { + return new Wallet(address); + } + + static createFromPublicKey(publicKey: Buffer, workchain = 0) { + const data = beginCell() + .storeBuffer(publicKey, 32) + .storeUint(0, 16) + .endCell(); + const init = { code: walletCode, data }; + return new Wallet(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: Cell.EMPTY, + }); + } + + async sendExternalMessage( + provider: ContractProvider, + keypair: KeyPair, + actions: OutAction[], + seqno: number, + validUntil?: number + ) { + if (actions.length > MAX_ACTIONS) { + throw new Error(`Maximum ${MAX_ACTIONS} actions allowed`); + } + + if (actions.length === 0) { + throw new Error('At least one action is required'); + } + + if (validUntil === undefined) { + validUntil = Math.floor(Date.now() / 1000) + DEFAULT_VALID_UNTIL_OFFSET; + } + + const actionsCell = beginCell(); + storeOutList(actions)(actionsCell); + + const msgInner = beginCell() + .storeUint(validUntil, 64) + .storeUint(seqno & 0xFFFF, 16) + .storeRef(actionsCell.endCell()) + .endCell(); + const hash = msgInner.hash(); + const signature = sign(hash, keypair.secretKey); + await provider.external( + beginCell().storeBuffer(signature, 64).storeRef(msgInner).endCell() + ); + } + + async sendTransfers( + provider: ContractProvider, + keypair: KeyPair, + transfers: TransferMessage[], + seqno: number, + validUntil?: number + ) { + if (transfers.length === 0) { + throw new Error('At least one transfer is required'); + } + const actions = transfers.map(createTransferAction); + await this.sendExternalMessage(provider, keypair, actions, seqno, validUntil); + } + + async sendSetCode( + provider: ContractProvider, + keypair: KeyPair, + code: Cell, + seqno: number, + validUntil?: number + ) { + const action = createSetCodeAction(code); + await this.sendExternalMessage(provider, keypair, [action], seqno, validUntil); + } + + private async getStorageParams(provider: ContractProvider): Promise<{ publicKey: Buffer; seqno: bigint } | { publicKey: undefined; seqno: bigint }> { + const state = (await provider.getState()).state; + if (state.type == 'active') { + const data = Cell.fromBoc(state.data!)[0].beginParse(); + return { publicKey: data.loadBuffer(32), seqno: data.loadUintBig(16) }; + } + return { publicKey: undefined, seqno: BigInt(0) }; + } + + async getPublicKey(provider: ContractProvider): Promise { + const { publicKey } = await this.getStorageParams(provider); + return publicKey; + } + + async getSeqno(provider: ContractProvider): Promise { + const { seqno } = await this.getStorageParams(provider); + return seqno; + } +} From 6a9b04c7d45b5021a1e8a705478328a59255e080 Mon Sep 17 00:00:00 2001 From: Andrew Gutarev Date: Tue, 11 Nov 2025 19:18:30 +0500 Subject: [PATCH 2/4] o_O --- standard/wallets/comparison/transaction.cpp | 4253 ------------------- 1 file changed, 4253 deletions(-) delete mode 100644 standard/wallets/comparison/transaction.cpp diff --git a/standard/wallets/comparison/transaction.cpp b/standard/wallets/comparison/transaction.cpp deleted file mode 100644 index e8e06e4..0000000 --- a/standard/wallets/comparison/transaction.cpp +++ /dev/null @@ -1,4253 +0,0 @@ -/* - This file is part of TON Blockchain Library. - - TON Blockchain Library is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - TON Blockchain Library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with TON Blockchain Library. If not, see . - - Copyright 2017-2020 Telegram Systems LLP -*/ -#include "block/transaction.h" -#include "block/block.h" -#include "block/block-parse.h" -#include "block/block-auto.h" -#include "crypto/openssl/rand.hpp" -#include "td/utils/bits.h" -#include "td/utils/uint128.h" -#include "ton/ton-shard.h" -#include "vm/vm.h" -#include "td/utils/Timer.h" - -namespace { -/** - * Logger that stores the tail of log messages. - * - * @param max_size The size of the buffer. Default is 256. - */ -class StringLoggerTail : public td::LogInterface { - public: - explicit StringLoggerTail(size_t max_size = 256) : buf(max_size, '\0') {} - - /** - * Appends a slice of data to the buffer. - * - * @param slice The slice of data to be appended. - */ - void append(td::CSlice slice) override { - if (slice.size() > buf.size()) { - slice.remove_prefix(slice.size() - buf.size()); - } - while (!slice.empty()) { - size_t s = std::min(buf.size() - pos, slice.size()); - std::copy(slice.begin(), slice.begin() + s, buf.begin() + pos); - pos += s; - if (pos == buf.size()) { - pos = 0; - truncated = true; - } - slice.remove_prefix(s); - } - } - - /** - * Retrieves the tail of the log. - * - * @returns The log as std::string. - */ - std::string get_log() const { - if (truncated) { - std::string res = buf; - std::rotate(res.begin(), res.begin() + pos, res.end()); - return res; - } else { - return buf.substr(0, pos); - } - } - - private: - std::string buf; - size_t pos = 0; - bool truncated = false; -}; -} - -namespace block { -using td::Ref; - -/** - * Looks up a library among public libraries. - * - * @param key A constant bit pointer representing the key of the library to lookup. - * - * @returns A reference to the library cell if found, null otherwise. - */ -Ref ComputePhaseConfig::lookup_library(td::ConstBitPtr key) const { - return libraries ? vm::lookup_library_in(key, libraries->get_root_cell()) : Ref{}; -} - -/* - * - * ACCOUNTS - * - */ - -/** - * Sets the address of the account. - * - * @param wc The workchain ID of the account. - * @param new_addr The new address of the account. - * - * @returns True if the address was successfully set, false otherwise. - */ -bool Account::set_address(ton::WorkchainId wc, td::ConstBitPtr new_addr) { - workchain = wc; - addr = new_addr; - return true; -} - -/** - * Sets the length of anycast prefix length in the account address. - * - * @param new_length The new rewrite length. - * - * @returns True if the length was successfully set, False otherwise. - */ -bool Account::set_addr_rewrite_length(int new_length) { - if (new_length < 0 || new_length > 30) { - return false; // invalid value - } - if (addr_rewrite_length_set) { - return addr_rewrite_length == new_length; - } else { - addr_rewrite_length = (unsigned char)new_length; - addr_rewrite_length_set = true; - return true; - } -} - -/** - * Checks if the given addr rewrite length is valid for the Account. - * - * @param length The addr rewrite length to be checked. - * - * @returns True if the addr rewrite length is valid, False otherwise. - */ -bool Account::check_addr_rewrite_length(int length) const { - return addr_rewrite_length_set ? (length == addr_rewrite_length) : (length >= 0 && length <= 30); -} - -/** - * Parses anycast data of the account address. - * - * Initializes addr_rewrite. - * - * @param cs The cell slice containing partially-parsed account address. - * - * @returns True if parsing was successful, false otherwise. - */ -bool Account::parse_maybe_anycast(vm::CellSlice& cs) { - int t = (int)cs.fetch_ulong(1); - if (t < 0) { - return false; - } else if (!t) { - return set_addr_rewrite_length(0); - } - int depth; - return cs.fetch_uint_leq(30, depth) // anycast_info$_ depth:(#<= 30) - && depth // { depth >= 1 } - && cs.fetch_bits_to(addr_rewrite.bits(), depth) // rewrite_pfx:(bits depth) - && set_addr_rewrite_length(depth); -} - -/** - * Stores the anycast information to a serialized account address. - * - * @param cb The vm::CellBuilder object to store the information in. - * - * @returns True if the anycast information was successfully stored, false otherwise. - */ -bool Account::store_maybe_anycast(vm::CellBuilder& cb) const { - if (!addr_rewrite_length_set || !addr_rewrite_length) { - return cb.store_bool_bool(false); - } - return cb.store_bool_bool(true) // just$1 - && cb.store_uint_leq(30, addr_rewrite_length) // depth:(#<= 30) - && cb.store_bits_bool(addr_rewrite.cbits(), addr_rewrite_length); // rewrite_pfx:(bits depth) -} - -/** - * Unpacks the address from a given CellSlice. - * - * @param addr_cs The CellSlice containing the address. - * - * @returns True if the address was successfully unpacked, False otherwise. - */ -bool Account::unpack_address(vm::CellSlice& addr_cs) { - int addr_tag = block::gen::t_MsgAddressInt.get_tag(addr_cs); - int new_wc = ton::workchainInvalid; - switch (addr_tag) { - case block::gen::MsgAddressInt::addr_std: - if (!(addr_cs.advance(2) && parse_maybe_anycast(addr_cs) && addr_cs.fetch_int_to(8, new_wc) && - addr_cs.fetch_bits_to(addr_orig.bits(), 256) && addr_cs.empty_ext())) { - return false; - } - break; - case block::gen::MsgAddressInt::addr_var: - // cannot appear in masterchain / basechain - return false; - default: - return false; - } - addr_cs.clear(); - if (new_wc == ton::workchainInvalid) { - return false; - } - if (workchain == ton::workchainInvalid) { - workchain = new_wc; - addr = addr_orig; - addr.bits().copy_from(addr_rewrite.cbits(), addr_rewrite_length); - } else if (addr_rewrite_length) { - ton::StdSmcAddress new_addr = addr_orig; - new_addr.bits().copy_from(addr_rewrite.cbits(), addr_rewrite_length); - if (new_addr != addr) { - LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex() - << " : account header contains different address " << new_addr.to_hex() << " (with splitting depth " - << (int)addr_rewrite_length << ")"; - return false; - } - } else if (addr != addr_orig) { - LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex() - << " : account header contains different address " << addr_orig.to_hex(); - return false; - } - if (workchain != new_wc) { - LOG(ERROR) << "error unpacking account " << workchain << ":" << addr.to_hex() - << " : account header contains different workchain " << new_wc; - return false; - } - addr_rewrite = addr.bits(); // initialize all 32 bits of addr_rewrite - if (!addr_rewrite_length) { - my_addr_exact = my_addr; - } - return true; -} - -/** - * Unpacks storage information from a CellSlice. - * - * Storage information is serialized using StorageInfo TLB-scheme. - * - * @param cs The CellSlice containing the storage information. - * - * @returns True if the unpacking is successful, false otherwise. - */ -bool Account::unpack_storage_info(vm::CellSlice& cs) { - block::gen::StorageInfo::Record info; - block::gen::StorageUsed::Record used; - if (!tlb::unpack_exact(cs, info) || !tlb::csr_unpack(info.used, used)) { - return false; - } - last_paid = info.last_paid; - if (info.storage_extra.write().fetch_long(3) == 1) { - info.storage_extra->prefetch_bits_to(storage_dict_hash.value_force()); - } else { - storage_dict_hash = {}; - } - orig_storage_dict_hash = storage_dict_hash; - if (info.due_payment->prefetch_ulong(1) == 1) { - vm::CellSlice& cs2 = info.due_payment.write(); - cs2.advance(1); - due_payment = block::tlb::t_Grams.as_integer_skip(cs2); - if (due_payment.is_null() || !cs2.empty_ext()) { - return false; - } - } else { - due_payment = td::zero_refint(); - } - unsigned long long u = 0; - u |= storage_used.cells = block::tlb::t_VarUInteger_7.as_uint(*used.cells); - u |= storage_used.bits = block::tlb::t_VarUInteger_7.as_uint(*used.bits); - LOG(DEBUG) << "last_paid=" << last_paid << "; cells=" << storage_used.cells << " bits=" << storage_used.bits; - return (u != std::numeric_limits::max()); -} - -/** - * Unpacks the state of an Account from a CellSlice. - * - * State is serialized using StateInit TLB-scheme. - * Initializes fixed_prefix_length (from account state - StateInit) - * - * @param cs The CellSlice containing the serialized state. - * - * @returns True if the state was successfully unpacked, False otherwise. - */ -bool Account::unpack_state(vm::CellSlice& cs) { - block::gen::StateInit::Record state; - if (!tlb::unpack_exact(cs, state)) { - return false; - } - fixed_prefix_length = 0; - if (state.fixed_prefix_length->size() == 6) { - fixed_prefix_length = (int)state.fixed_prefix_length->prefetch_ulong(6) - 32; - } - if (state.special->size() > 1) { - int z = (int)state.special->prefetch_ulong(3); - if (z < 0) { - return false; - } - tick = z & 2; - tock = z & 1; - LOG(DEBUG) << "tick=" << tick << ", tock=" << tock; - } - code = orig_code = state.code->prefetch_ref(); - data = orig_data = state.data->prefetch_ref(); - library = orig_library = state.library->prefetch_ref(); - return true; -} - -/** - * Computes the address of the account. - * - * @param force If set to true, the address will be recomputed even if it already exists. - * - * @returns True if the address was successfully computed, false otherwise. - */ -bool Account::compute_my_addr(bool force) { - if (!force && my_addr.not_null() && my_addr_exact.not_null()) { - return true; - } - if (workchain == ton::workchainInvalid) { - my_addr.clear(); - return false; - } - vm::CellBuilder cb; - Ref cell, cell2; - if (workchain >= -128 && workchain < 127) { - if (!(cb.store_long_bool(2, 2) // addr_std$10 - && store_maybe_anycast(cb) // anycast:(Maybe Anycast) - && cb.store_long_rchk_bool(workchain, 8) // workchain_id:int8 - && cb.store_bits_bool(addr_orig) // addr:bits256 - && cb.finalize_to(cell) && cb.store_long_bool(4, 3) // addr_std$10 anycast:(Maybe Anycast) - && cb.store_long_rchk_bool(workchain, 8) // workchain_id:int8 - && cb.store_bits_bool(addr) // addr:bits256 - && cb.finalize_to(cell2))) { - return false; - } - } else { - if (!(cb.store_long_bool(3, 2) // addr_var$11 - && store_maybe_anycast(cb) // anycast:(Maybe Anycast) - && cb.store_long_bool(256, 9) // addr_len:(## 9) - && cb.store_long_rchk_bool(workchain, 32) // workchain_id:int32 - && cb.store_bits_bool(addr_orig) // addr:(bits addr_len) - && cb.finalize_to(cell) && cb.store_long_bool(6, 3) // addr_var$11 anycast:(Maybe Anycast) - && cb.store_long_bool(256, 9) // addr_len:(## 9) - && cb.store_long_rchk_bool(workchain, 32) // workchain_id:int32 - && cb.store_bits_bool(addr) // addr:(bits addr_len) - && cb.finalize_to(cell2))) { - return false; - } - } - my_addr = load_cell_slice_ref(std::move(cell)); - my_addr_exact = load_cell_slice_ref(std::move(cell2)); - return true; -} - -/** - * Computes the address of the Account. - * - * Legacy (used only if global_version < 10). - * - * @param tmp_addr A reference to the CellSlice for the result. - * @param fixed_prefix_length The fixed prefix length for the address. - * @param orig_addr_rewrite Address prefix of length fixed_prefix_length. - * - * @returns True if the address was successfully computed, false otherwise. - */ -bool Account::recompute_tmp_addr(Ref& tmp_addr, int fixed_prefix_length, - td::ConstBitPtr orig_addr_rewrite) const { - if (!fixed_prefix_length && my_addr_exact.not_null()) { - tmp_addr = my_addr_exact; - return true; - } - if (fixed_prefix_length == addr_rewrite_length && my_addr.not_null()) { - tmp_addr = my_addr; - return true; - } - if (fixed_prefix_length < 0 || fixed_prefix_length > 30) { - return false; - } - vm::CellBuilder cb; - bool std = (workchain >= -128 && workchain < 128); - if (!cb.store_long_bool(std ? 2 : 3, 2)) { // addr_std$10 or addr_var$11 - return false; - } - if (!fixed_prefix_length) { - if (!cb.store_bool_bool(false)) { // anycast:(Maybe Anycast) - return false; - } - } else if (!(cb.store_bool_bool(true) // just$1 - && cb.store_long_bool(fixed_prefix_length, 5) // depth:(#<= 30) - && cb.store_bits_bool(addr.bits(), fixed_prefix_length))) { // rewrite_pfx:(bits depth) - return false; - } - if (std) { - if (!cb.store_long_rchk_bool(workchain, 8)) { // workchain:int8 - return false; - } - } else if (!(cb.store_long_bool(256, 9) // addr_len:(## 9) - && cb.store_long_bool(workchain, 32))) { // workchain:int32 - return false; - } - Ref cell; - return cb.store_bits_bool(orig_addr_rewrite, fixed_prefix_length) // address:(bits addr_len) or bits256 - && cb.store_bits_bool(addr.bits() + fixed_prefix_length, 256 - fixed_prefix_length) && cb.finalize_to(cell) && - (tmp_addr = vm::load_cell_slice_ref(std::move(cell))).not_null(); -} - -/** - * Sets address rewriting info for a newly-activated account. - * - * @param rewrite_length The fixed prefix length for the account address. - * @param orig_addr_rewrite Address prefix of length fixed_prefix_length. - * - * @returns True if the rewriting info was successfully set, false otherwise. - */ -bool Account::init_rewrite_addr(int rewrite_length, td::ConstBitPtr orig_addr_rewrite) { - if (addr_rewrite_length_set || !set_addr_rewrite_length(rewrite_length)) { - return false; - } - addr_orig = addr; - addr_rewrite = addr.bits(); - addr_orig.bits().copy_from(orig_addr_rewrite, rewrite_length); - return compute_my_addr(true); -} - -/** - * Unpacks the account information from the provided CellSlice. - * - * Used to unpack previously existing accounts. - * - * @param shard_account The ShardAccount to unpack. - * @param now The current Unix time. - * @param special Flag indicating if the account is special. - * - * @returns True if the unpacking is successful, false otherwise. - */ -bool Account::unpack(Ref shard_account, ton::UnixTime now, bool special) { - LOG(DEBUG) << "unpacking " << (special ? "special " : "") << "account " << addr.to_hex(); - if (shard_account.is_null()) { - LOG(ERROR) << "account " << addr.to_hex() << " does not have a valid ShardAccount to unpack"; - return false; - } - if (verbosity > 2) { - FLOG(INFO) { - shard_account->print_rec(sb, 2); - block::gen::t_ShardAccount.print(sb, shard_account); - }; - } - block::gen::ShardAccount::Record acc_info; - if (!(block::tlb::t_ShardAccount.validate_csr(shard_account) && tlb::unpack_exact(shard_account.write(), acc_info))) { - LOG(ERROR) << "account " << addr.to_hex() << " state is invalid"; - return false; - } - last_trans_lt_ = acc_info.last_trans_lt; - last_trans_hash_ = acc_info.last_trans_hash; - now_ = now; - auto account = std::move(acc_info.account); - total_state = orig_total_state = account; - auto acc_cs = load_cell_slice(std::move(account)); - if (block::gen::t_Account.get_tag(acc_cs) == block::gen::Account::account_none) { - is_special = special; - return acc_cs.size_ext() == 1 && init_new(now); - } - block::gen::Account::Record_account acc; - block::gen::AccountStorage::Record storage; - if (!(tlb::unpack_exact(acc_cs, acc) && (my_addr = acc.addr).not_null() && unpack_address(acc.addr.write()) && - compute_my_addr() && unpack_storage_info(acc.storage_stat.write()) && - tlb::csr_unpack(this->storage = std::move(acc.storage), storage) && - std::max(storage.last_trans_lt, 1ULL) > acc_info.last_trans_lt && balance.unpack(std::move(storage.balance)))) { - return false; - } - is_special = special; - last_trans_end_lt_ = storage.last_trans_lt; - switch (block::gen::t_AccountState.get_tag(*storage.state)) { - case block::gen::AccountState::account_uninit: - status = orig_status = acc_uninit; - state_hash = addr; - forget_addr_rewrite_length(); - break; - case block::gen::AccountState::account_frozen: - status = orig_status = acc_frozen; - if (!storage.state->have(2 + 256)) { - return false; - } - state_hash = storage.state->data_bits() + 2; - break; - case block::gen::AccountState::account_active: - status = orig_status = acc_active; - if (storage.state.write().fetch_ulong(1) != 1) { - return false; - } - inner_state = storage.state; - if (!unpack_state(storage.state.write())) { - return false; - } - state_hash.clear(); - break; - default: - return false; - } - LOG(DEBUG) << "end of Account.unpack() for " << workchain << ":" << addr.to_hex() - << " (balance = " << balance.to_str() << " ; last_trans_lt = " << last_trans_lt_ << ".." - << last_trans_end_lt_ << ")"; - return true; -} - -/** - * Initializes a new Account object. - * - * @param now The current Unix time. - * - * @returns True if the initialization is successful, false otherwise. - */ -bool Account::init_new(ton::UnixTime now) { - // only workchain and addr are initialized at this point - if (workchain == ton::workchainInvalid) { - return false; - } - addr_orig = addr; - addr_rewrite = addr.cbits(); - last_trans_lt_ = last_trans_end_lt_ = 0; - last_trans_hash_.set_zero(); - now_ = now; - last_paid = 0; - storage_used = {}; - orig_storage_dict_hash = storage_dict_hash = {}; - due_payment = td::zero_refint(); - balance.set_zero(); - if (my_addr_exact.is_null()) { - vm::CellBuilder cb; - if (workchain >= -128 && workchain < 128) { - CHECK(cb.store_long_bool(4, 3) // addr_std$10 anycast:(Maybe Anycast) - && cb.store_long_rchk_bool(workchain, 8) // workchain:int8 - && cb.store_bits_bool(addr)); // address:bits256 - } else { - CHECK(cb.store_long_bool(0xd00, 12) // addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) - && cb.store_long_rchk_bool(workchain, 32) // workchain:int32 - && cb.store_bits_bool(addr)); // address:(bits addr_len) - } - my_addr_exact = load_cell_slice_ref(cb.finalize()); - } - if (my_addr.is_null()) { - my_addr = my_addr_exact; - } - if (total_state.is_null()) { - vm::CellBuilder cb; - CHECK(cb.store_long_bool(0, 1) // account_none$0 = Account - && cb.finalize_to(total_state)); - orig_total_state = total_state; - } - state_hash = addr_orig; - status = orig_status = acc_nonexist; - addr_rewrite_length_set = false; - return true; -} - -/** - * Removes extra currencies dict from AccountStorage. - * - * This is used for computing account storage stats. - * - * @param storage_cs AccountStorage as CellSlice. - * - * @returns AccountStorage without extra currencies as CellSlice. - */ -static td::Ref storage_without_extra_currencies(td::Ref storage_cs) { - block::gen::AccountStorage::Record rec; - if (!block::gen::csr_unpack(storage_cs, rec)) { - LOG(ERROR) << "failed to unpack AccountStorage"; - return {}; - } - if (rec.balance->size_refs() > 0) { - block::gen::CurrencyCollection::Record balance; - if (!block::gen::csr_unpack(rec.balance, balance)) { - LOG(ERROR) << "failed to unpack AccountStorage"; - return {}; - } - balance.other = vm::CellBuilder{}.store_zeroes(1).as_cellslice_ref(); - if (!block::gen::csr_pack(rec.balance, balance)) { - LOG(ERROR) << "failed to pack AccountStorage"; - return {}; - } - } - td::Ref result; - if (!block::gen::csr_pack(result, rec)) { - LOG(ERROR) << "failed to pack AccountStorage"; - return {}; - } - return result; -} - -/** - * Computes storage dict of the account from scratch. - * This requires storage_dict_hash to be set, as it guarantees that the stored storage_used was computed recently - * (in older versions it included extra currency balance, in newer versions it does not). - * - * @returns Root of the dictionary, or Error - */ -td::Result> Account::compute_account_storage_dict() const { - if (storage.is_null()) { - return td::Status::Error("cannot compute storage dict: empty storage"); - } - if (!storage_dict_hash) { - return td::Status::Error("cannot compute storage dict: storage_dict_hash is not set"); - } - AccountStorageStat stat; - auto storage_for_stat = storage_without_extra_currencies(storage); - if (storage_for_stat.is_null()) { - return td::Status::Error("cannot compute storage dict: invalid storage"); - } - TRY_STATUS(stat.replace_roots(storage_for_stat->prefetch_all_refs())); - // Root of AccountStorage is not counted in AccountStorageStat - td::uint64 expected_cells = stat.get_total_cells() + 1; - td::uint64 expected_bits = stat.get_total_bits() + storage->size(); - if (expected_cells != storage_used.cells || expected_bits != storage_used.bits) { - return td::Status::Error(PSTRING() << "invalid storage_used: computed cells=" << expected_cells - << " bits=" << expected_bits << ", found cells" << storage_used.cells - << " bits=" << storage_used.bits); - } - TRY_RESULT(root_hash, stat.get_dict_hash()); - if (storage_dict_hash.value() != root_hash) { - return td::Status::Error(PSTRING() << "invalid storage dict hash: computed " << root_hash.to_hex() << ", found " - << storage_dict_hash.value().to_hex()); - } - return stat.get_dict_root(); -} - -/** - * Initializes account_storage_stat of the account using the existing dict_root. - * This is not strictly necessary, as the storage stat is recomputed in Transaction. - * However, it can be used to optimize cell usage. - * This requires storage_dict_hash to be set, as it guarantees that the stored storage_used was computed recently - * (in older versions it included extra currency balance, in newer versions it does not). - * - * @param dict_root Root of the storage dictionary. - * - * @returns Status of the operation. - */ -td::Status Account::init_account_storage_stat(Ref dict_root) { - if (storage.is_null()) { - if (dict_root.not_null()) { - return td::Status::Error("storage is null, but dict_root is not null"); - } - account_storage_stat = {}; - return td::Status::OK(); - } - if (!storage_dict_hash) { - return td::Status::Error("cannot init storage dict: storage_dict_hash is not set"); - } - // Root of AccountStorage is not counted in AccountStorageStat - if (storage_used.cells < 1 || storage_used.bits < storage->size()) { - return td::Status::Error(PSTRING() << "storage_used is too small: cells=" << storage_used.cells - << " bits=" << storage_used.bits << " storage_root_bits=" << storage->size()); - } - AccountStorageStat new_stat(std::move(dict_root), storage->prefetch_all_refs(), storage_used.cells - 1, - storage_used.bits - storage->size()); - TRY_RESULT(root_hash, new_stat.get_dict_hash()); - if (storage_dict_hash.value() != root_hash) { - return td::Status::Error(PSTRING() << "invalid storage dict hash: computed " << root_hash.to_hex() << ", found " - << storage_dict_hash.value().to_hex()); - } - account_storage_stat = std::move(new_stat); - return td::Status::OK(); -} - -/** - * Resets the fixed prefix length of the account. - * - * @returns True if the fixed prefix length was successfully reset, false otherwise. - */ -bool Account::forget_addr_rewrite_length() { - addr_rewrite_length_set = false; - addr_rewrite_length = 0; - addr_orig = addr; - my_addr = my_addr_exact; - addr_rewrite = addr.bits(); - return true; -} - -/** - * Deactivates the account. - * - * @returns True if the account was successfully deactivated, false otherwise. - */ -bool Account::deactivate() { - if (status == acc_active) { - return false; - } - // forget special (tick/tock) info - tick = tock = false; - fixed_prefix_length = 0; - if (status == acc_nonexist || status == acc_uninit) { - // forget fixed prefix length and address rewriting info - forget_addr_rewrite_length(); - // forget specific state hash for deleted or uninitialized accounts (revert to addr) - state_hash = addr; - } - // forget code and data (only active accounts remember these) - code.clear(); - data.clear(); - library.clear(); - // if deleted, balance must be zero - if (status == acc_nonexist && !balance.is_zero()) { - return false; - } - return true; -} - -/** - * Checks if the account belongs to a specific shard. - * - * @param shard The shard to check against. - * - * @returns True if the account belongs to the shard, False otherwise. - */ -bool Account::belongs_to_shard(ton::ShardIdFull shard) const { - return workchain == shard.workchain && ton::shard_is_ancestor(shard.shard, addr); -} - -/** - * Adds the partial storage payment to the total sum. - * - * @param payment The total sum to be updated. - * @param delta The time delta for which the payment is calculated. - * @param prices The storage prices. - * @param storage_used Account storage statistics. - * @param is_mc A flag indicating whether the account is in the masterchain. - */ -void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, const block::StoragePrices& prices, - const StorageUsed& storage_used, bool is_mc) { - td::BigInt256 c{(long long)storage_used.cells}, b{(long long)storage_used.bits}; - if (is_mc) { - // storage.cells * prices.mc_cell_price + storage.bits * prices.mc_bit_price; - c.mul_short(prices.mc_cell_price); - b.mul_short(prices.mc_bit_price); - } else { - // storage.cells * prices.cell_price + storage.bits * prices.bit_price; - c.mul_short(prices.cell_price); - b.mul_short(prices.bit_price); - } - b += c; - b.mul_short(delta).normalize(); - CHECK(b.sgn() >= 0); - payment += b; -} - -/** - * Computes the storage fees based on the given parameters. - * - * @param now The current Unix time. - * @param pricing The vector of storage prices. - * @param storage_used Account storage statistics. - * @param last_paid The Unix time when the last payment was made. - * @param is_special A flag indicating if the account is special. - * @param is_masterchain A flag indicating if the account is in the masterchain. - * - * @returns The computed storage fees as RefInt256. - */ -td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector& pricing, - const StorageUsed& storage_used, ton::UnixTime last_paid, - bool is_special, bool is_masterchain) { - if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) { - return td::zero_refint(); - } - std::size_t n = pricing.size(), i = n; - while (i && pricing[i - 1].valid_since > last_paid) { - --i; - } - if (i) { - --i; - } - ton::UnixTime upto = std::max(last_paid, pricing[0].valid_since); - td::RefInt256 total{true, 0}; - for (; i < n && upto < now; i++) { - ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now); - if (upto < valid_until) { - assert(upto >= pricing[i].valid_since); - add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_used, is_masterchain); - } - upto = valid_until; - } - return td::rshift(total, 16, 1); // divide by 2^16 with ceil rounding to obtain nanograms -} - -/** - * Computes the storage fees for the account. - * - * @param now The current Unix time. - * @param pricing The vector of storage prices. - * - * @returns The computed storage fees as RefInt256. - */ -td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const { - return StoragePrices::compute_storage_fees(now, pricing, storage_used, last_paid, is_special, is_masterchain()); -} - -namespace transaction { -/** - * Constructs a new Transaction object. - * - * @param _account The Account object. - * @param ttype The type of the transaction (see transaction.cpp#309). - * @param req_start_lt The minimal logical time of the transaction. - * @param _now The current Unix time. - * @param _inmsg The input message that caused the transaction. - * - * @returns None - */ -Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, - Ref _inmsg) - : trans_type(ttype) - , is_first(_account.transactions.empty()) - , new_tick(_account.tick) - , new_tock(_account.tock) - , new_fixed_prefix_length(_account.fixed_prefix_length) - , now(_now) - , account(_account) - , my_addr(_account.my_addr) - , my_addr_exact(_account.my_addr_exact) - , balance(_account.balance) - , original_balance(_account.balance) - , due_payment(_account.due_payment) - , last_paid(_account.last_paid) - , new_code(_account.code) - , new_data(_account.data) - , new_library(_account.library) - , in_msg(std::move(_inmsg)) { - start_lt = std::max(req_start_lt, account.last_trans_end_lt_); - end_lt = start_lt + 1; - acc_status = (account.status == Account::acc_nonexist ? Account::acc_uninit : account.status); - if (acc_status == Account::acc_frozen) { - frozen_hash = account.state_hash; - } -} - -/** - * Unpacks the input message of a transaction. - * - * @param ihr_delivered A boolean indicating whether the message was delivered using IHR (Instant Hypercube Routing). - * @param cfg Action phase configuration. - * - * @returns A boolean indicating whether the unpacking was successful. - */ -bool Transaction::unpack_input_msg(bool ihr_delivered, const ActionPhaseConfig* cfg) { - if (in_msg.is_null() || in_msg_type) { - return false; - } - if (verbosity > 2) { - FLOG(INFO) { - sb << "unpacking inbound message for a new transaction: "; - block::gen::t_Message_Any.print_ref(sb, in_msg); - load_cell_slice(in_msg).print_rec(sb); - }; - } - auto cs = vm::load_cell_slice(in_msg); - int tag = gen::t_CommonMsgInfo.get_tag(cs); - switch (tag) { - case gen::CommonMsgInfo::int_msg_info: { - if (!(tlb::unpack(cs, in_msg_info) && msg_balance_remaining.unpack(in_msg_info.value))) { - return false; - } - if (in_msg_info.ihr_disabled && ihr_delivered) { - return false; - } - bounce_enabled = in_msg_info.bounce; - in_msg_type = 1; - td::RefInt256 ihr_fee; - if (cfg->global_version >= 12) { - ihr_fee = td::zero_refint(); - td::RefInt256 extra_flags = tlb::t_Grams.as_integer(in_msg_info.extra_flags); - new_bounce_format = extra_flags->get_bit(0); - new_bounce_format_full_body = extra_flags->get_bit(1); - } else { - // Legacy: extra_flags was previously ihr_fee - ihr_fee = tlb::t_Grams.as_integer(in_msg_info.extra_flags); - } - if (ihr_delivered) { - in_fwd_fee = std::move(ihr_fee); - } else { - in_fwd_fee = td::zero_refint(); - msg_balance_remaining += std::move(ihr_fee); - } - if (in_msg_info.created_lt >= start_lt) { - start_lt = in_msg_info.created_lt + 1; - end_lt = start_lt + 1; - } - // ... - break; - } - case gen::CommonMsgInfo::ext_in_msg_info: { - gen::CommonMsgInfo::Record_ext_in_msg_info info; - if (!tlb::unpack(cs, info)) { - return false; - } - in_msg_info.ihr_disabled = in_msg_info.bounce = in_msg_info.bounced = false; - in_msg_info.src = info.src; - in_msg_info.dest = info.dest; - in_msg_info.created_at = in_msg_info.created_lt = 0; - if (cfg->disable_anycast) { - // Check that dest is addr_std without anycast - gen::MsgAddressInt::Record_addr_std rec; - if (!gen::csr_unpack(info.dest, rec)) { - LOG(DEBUG) << "destination address of the external message is not a valid addr_std"; - return false; - } - if (rec.anycast->size() > 1) { - LOG(DEBUG) << "destination address of the external message is an anycast address"; - return false; - } - } - in_msg_type = 2; - in_msg_extern = true; - // compute forwarding fees for this external message - vm::CellStorageStat sstat; // for message size - auto cell_info = sstat.compute_used_storage(cs).move_as_ok(); // message body - sstat.bits -= cs.size(); // bits in the root cells are free - sstat.cells--; // the root cell itself is not counted as a cell - LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; - if (sstat.bits > cfg->size_limits.max_msg_bits || sstat.cells > cfg->size_limits.max_msg_cells) { - LOG(DEBUG) << "inbound external message too large, invalid"; - return false; - } - if (cell_info.max_merkle_depth > max_allowed_merkle_depth) { - LOG(DEBUG) << "inbound external message has too big merkle depth, invalid"; - return false; - } - // fetch message pricing info - CHECK(cfg); - const MsgPrices& msg_prices = cfg->fetch_msg_prices(account.is_masterchain()); - // compute forwarding fees - auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, true); - LOG(DEBUG) << "computed fwd fees = " << fees_c.first << " + " << fees_c.second; - - if (account.is_special) { - LOG(DEBUG) << "computed fwd fees set to zero for special account"; - fees_c.first = fees_c.second = 0; - } - in_fwd_fee = td::make_refint(fees_c.first); - if (balance.grams < in_fwd_fee) { - LOG(DEBUG) << "cannot pay for importing this external message"; - return false; - } - // (tentatively) debit account for importing this external message - balance -= in_fwd_fee; - msg_balance_remaining.set_zero(); // external messages cannot carry value - // ... - break; - } - default: - return false; - } - // init:(Maybe (Either StateInit ^StateInit)) - switch ((int)cs.prefetch_ulong(2)) { - case 2: { // (just$1 (left$0 _:StateInit )) - Ref state_init; - vm::CellBuilder cb; - if (!(cs.advance(2) && block::gen::t_StateInit.fetch_to(cs, state_init) && - cb.append_cellslice_bool(std::move(state_init)) && cb.finalize_to(in_msg_state) && - block::gen::t_StateInitWithLibs.validate_ref(in_msg_state))) { - LOG(DEBUG) << "cannot parse StateInit in inbound message"; - return false; - } - break; - } - case 3: { // (just$1 (right$1 _:^StateInit )) - if (!(cs.advance(2) && cs.fetch_ref_to(in_msg_state) && - block::gen::t_StateInitWithLibs.validate_ref(in_msg_state))) { - LOG(DEBUG) << "cannot parse ^StateInit in inbound message"; - return false; - } - break; - } - default: // nothing$0 - if (!cs.advance(1)) { - LOG(DEBUG) << "invalid init field in an inbound message"; - return false; - } - } - // body:(Either X ^X) - switch ((int)cs.fetch_ulong(1)) { - case 0: // left$0 _:X - in_msg_body = Ref{true, cs}; - break; - case 1: // right$1 _:^X - if (cs.size_ext() != 0x10000) { - LOG(DEBUG) << "body of an inbound message is not represented by exactly one reference"; - return false; - } - in_msg_body = load_cell_slice_ref(cs.prefetch_ref()); - break; - default: - LOG(DEBUG) << "invalid body field in an inbound message"; - return false; - } - total_fees += in_fwd_fee; - if (account.workchain == ton::masterchainId && cfg->mc_blackhole_addr && - cfg->mc_blackhole_addr.value() == account.addr) { - blackhole_burned.grams = msg_balance_remaining.grams; - msg_balance_remaining.grams = td::zero_refint(); - LOG(DEBUG) << "Burning " << blackhole_burned.grams << " nanoton (blackhole address)"; - } - return true; -} - -/** - * Prepares the storage phase of a transaction. - * - * @param cfg The configuration for the storage phase. - * @param force_collect Flag indicating whether to collect fees for frozen accounts. - * @param adjust_msg_value Flag indicating whether to adjust the message value if the account balance becomes less than the message balance. - * - * @returns True if the storage phase was successfully prepared, false otherwise. - */ -bool Transaction::prepare_storage_phase(const StoragePhaseConfig& cfg, bool force_collect, bool adjust_msg_value) { - if (now < account.last_paid) { - return false; - } - auto to_pay = account.compute_storage_fees(now, *(cfg.pricing)) + due_payment; - if (to_pay.not_null() && sgn(to_pay) < 0) { - return false; - } - auto res = std::make_unique(); - res->is_special = account.is_special; - last_paid = res->last_paid_updated = (res->is_special ? 0 : now); - if (to_pay.is_null() || sgn(to_pay) == 0) { - res->fees_collected = res->fees_due = td::zero_refint(); - } else if (to_pay <= balance.grams) { - res->fees_collected = to_pay; - res->fees_due = td::zero_refint(); - balance -= std::move(to_pay); - if (cfg.global_version >= 7) { - due_payment = td::zero_refint(); - } - } else if (acc_status == Account::acc_frozen && !force_collect && to_pay < cfg.delete_due_limit) { - // do not collect fee - res->last_paid_updated = (res->is_special ? 0 : account.last_paid); - res->fees_collected = res->fees_due = td::zero_refint(); - } else { - res->fees_collected = balance.grams; - res->fees_due = std::move(to_pay) - std::move(balance.grams); - balance.grams = td::zero_refint(); - if (!res->is_special) { - auto total_due = res->fees_due; - switch (acc_status) { - case Account::acc_uninit: - case Account::acc_frozen: - if (total_due > cfg.delete_due_limit && balance.extra.is_null()) { - // Keeping accounts with non-null extras is a temporary measure before implementing proper collection of - // extracurrencies from deleted accounts - res->deleted = true; - acc_status = Account::acc_deleted; - if (balance.extra.not_null()) { - // collect extra currencies as a fee - total_fees += block::CurrencyCollection{0, std::move(balance.extra)}; - balance.extra.clear(); - } - } - break; - case Account::acc_active: - if (total_due > cfg.freeze_due_limit) { - res->frozen = true; - was_frozen = true; - acc_status = Account::acc_frozen; - } - break; - } - if (cfg.enable_due_payment) { - due_payment = total_due; - } - } - } - if (adjust_msg_value && msg_balance_remaining.grams > balance.grams) { - msg_balance_remaining.grams = balance.grams; - } - total_fees += res->fees_collected; - storage_phase = std::move(res); - return true; -} - -/** - * Prepares the credit phase of a transaction. - * - * This function creates a CreditPhase object and performs the necessary calculations - * to determine the amount to be credited in the credit phase. It updates the due payment, - * credit, balance, and total fees accordingly. - * - * @returns True if the credit phase is prepared successfully, false otherwise. - */ -bool Transaction::prepare_credit_phase() { - credit_phase = std::make_unique(); - // Due payment is only collected in storage phase. - // For messages with bounce flag, contract always receives the amount specified in message - // auto collected = std::min(msg_balance_remaining.grams, due_payment); - // credit_phase->due_fees_collected = collected; - // due_payment -= collected; - // credit_phase->credit = msg_balance_remaining -= collected; - credit_phase->due_fees_collected = td::zero_refint(); - credit_phase->credit = msg_balance_remaining; - if (!msg_balance_remaining.is_valid()) { - LOG(ERROR) << "cannot compute the amount to be credited in the credit phase of transaction"; - return false; - } - // NB: msg_balance_remaining may be deducted from balance later during bounce phase - balance += msg_balance_remaining; - if (!balance.is_valid()) { - LOG(ERROR) << "cannot credit currency collection to account"; - return false; - } - // total_fees += std::move(collected); - return true; -} -} // namespace transaction - -/** - * Parses the gas limits and prices from a given cell. - * - * @param cell The cell containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. - * @param freeze_due_limit Reference to store the freeze due limit. - * @param delete_due_limit Reference to store the delete due limit. - * - * @returns True if the parsing is successful, false otherwise. - */ -bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, - td::RefInt256& delete_due_limit) { - return cell.not_null() && - parse_GasLimitsPrices(vm::load_cell_slice_ref(std::move(cell)), freeze_due_limit, delete_due_limit); -} - -/** - * Parses the gas limits and prices from a given cell slice. - * - * @param cs The cell slice containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. - * @param freeze_due_limit Reference to store the freeze due limit. - * @param delete_due_limit Reference to store the delete due limit. - * - * @returns True if the parsing is successful, false otherwise. - */ -bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit, - td::RefInt256& delete_due_limit) { - if (cs.is_null()) { - return false; - } - block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; - if (tlb::csr_unpack(cs, flat)) { - return parse_GasLimitsPrices_internal(std::move(flat.other), freeze_due_limit, delete_due_limit, - flat.flat_gas_limit, flat.flat_gas_price); - } else { - return parse_GasLimitsPrices_internal(std::move(cs), freeze_due_limit, delete_due_limit); - } -} - -/** - * Parses the gas limits and prices from a gas limits and prices record. - * - * @param cs The cell slice containing the gas limits and prices serialized using GasLimitsPricing TLB-scheme. - * @param freeze_due_limit A reference to store the freeze due limit. - * @param delete_due_limit A reference to store the delete due limit. - * @param _flat_gas_limit The flat gas limit. - * @param _flat_gas_price The flat gas price. - * - * @returns True if the parsing is successful, false otherwise. - */ -bool ComputePhaseConfig::parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, - td::RefInt256& delete_due_limit, td::uint64 _flat_gas_limit, - td::uint64 _flat_gas_price) { - auto f = [&](const auto& r, td::uint64 spec_limit) { - gas_limit = r.gas_limit; - special_gas_limit = spec_limit; - gas_credit = r.gas_credit; - gas_price = r.gas_price; - freeze_due_limit = td::make_refint(r.freeze_due_limit); - delete_due_limit = td::make_refint(r.delete_due_limit); - }; - block::gen::GasLimitsPrices::Record_gas_prices_ext rec; - if (tlb::csr_unpack(cs, rec)) { - f(rec, rec.special_gas_limit); - } else { - block::gen::GasLimitsPrices::Record_gas_prices rec0; - if (tlb::csr_unpack(std::move(cs), rec0)) { - f(rec0, rec0.gas_limit); - } else { - return false; - } - } - flat_gas_limit = _flat_gas_limit; - flat_gas_price = _flat_gas_price; - compute_threshold(); - return true; -} - -/** - * Checks if an address is suspended according to the ConfigParam(44). - * - * @param wc The workchain ID. - * @param addr The account address address. - * - * @returns True if the address is suspended, False otherwise. - */ -bool ComputePhaseConfig::is_address_suspended(ton::WorkchainId wc, td::Bits256 addr) const { - if (!suspended_addresses) { - return false; - } - try { - vm::CellBuilder key; - key.store_long_bool(wc, 32); - key.store_bits_bool(addr); - return !suspended_addresses->lookup(key.data_bits(), 288).is_null(); - } catch (vm::VmError) { - return false; - } -} - -/** - * Computes the maximum gas fee based on the gas prices and limits. - * - * @param gas_price256 The gas price from config as RefInt256 - * @param gas_limit The gas limit from config - * @param flat_gas_limit The flat gas limit from config - * @param flat_gas_price The flat gas price from config - * - * @returns The maximum gas fee. - */ -static td::RefInt256 compute_max_gas_threshold(const td::RefInt256& gas_price256, td::uint64 gas_limit, - td::uint64 flat_gas_limit, td::uint64 flat_gas_price) { - if (gas_limit > flat_gas_limit) { - return td::rshift(gas_price256 * (gas_limit - flat_gas_limit), 16, 1) + td::make_bigint(flat_gas_price); - } else { - return td::make_refint(flat_gas_price); - } -} - -/** - * Computes the maximum for gas fee based on the gas prices and limits. - * - * Updates max_gas_threshold. - */ -void ComputePhaseConfig::compute_threshold() { - gas_price256 = td::make_refint(gas_price); - max_gas_threshold = compute_max_gas_threshold(gas_price256, gas_limit, flat_gas_limit, flat_gas_price); -} - -/** - * Computes the amount of gas that can be bought for a given amount of nanograms. - * - * @param nanograms The amount of nanograms to compute gas for. - * - * @returns The amount of gas. - */ -td::uint64 ComputePhaseConfig::gas_bought_for(td::RefInt256 nanograms) const { - if (nanograms.is_null() || sgn(nanograms) < 0) { - return 0; - } - if (nanograms >= max_gas_threshold) { - return gas_limit; - } - if (nanograms < flat_gas_price) { - return 0; - } - auto res = td::div((std::move(nanograms) - flat_gas_price) << 16, gas_price256); - return res->to_long() + flat_gas_limit; -} - -/** - * Computes the gas price. - * - * @param gas_used The amount of gas used. - * - * @returns The computed gas price. - */ -td::RefInt256 ComputePhaseConfig::compute_gas_price(td::uint64 gas_used) const { - return gas_used <= flat_gas_limit ? td::make_refint(flat_gas_price) - : td::rshift(gas_price256 * (gas_used - flat_gas_limit), 16, 1) + flat_gas_price; -} - -namespace transaction { - -/** - * Checks if it is required to increase gas_limit (from GasLimitsPrices config) for the transaction - * - * In January 2024 a highload wallet of @wallet Telegram bot in mainnet was stuck because current gas limit (1M) is - * not enough to clean up old queries, thus locking funds inside. - * See comment in crypto/smartcont/highload-wallet-v2-code.fc for details on why this happened. - * Account address: EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu - * It was proposed to validators to increase gas limit for this account to 70M for a limited amount - * of time (until 2024-02-29). - * It is activated by setting global version to 5 in ConfigParam 8. - * This config change also activates new behavior for special accounts in masterchain. - * - * In August 2024 it was decided to unlock other old highload wallets that got into the same situation. - * See https://t.me/tondev_news/129 - * It is activated by setting global version to 9. - * - * @param cfg The compute phase configuration. - * @param now The Unix time of the transaction. - * @param account The account of the transaction. - * - * @returns Overridden gas limit or empty td::optional - */ -static td::optional override_gas_limit(const ComputePhaseConfig& cfg, ton::UnixTime now, - const Account& account) { - struct OverridenGasLimit { - td::uint64 new_limit; - int from_version; - ton::UnixTime until; - }; - static std::map, OverridenGasLimit> accounts = []() { - auto parse_addr = [](const char* s) -> std::pair { - auto r_addr = StdAddress::parse(td::Slice(s)); - r_addr.ensure(); - return {r_addr.ok().workchain, r_addr.ok().addr}; - }; - std::map, OverridenGasLimit> accounts; - - // Increase limit for EQD_v9j1rlsuHHw2FIhcsCFFSD367ldfDdCKcsNmNpIRzUlu until 2024-02-29 00:00:00 UTC - accounts[parse_addr("0:FFBFD8F5AE5B2E1C7C3614885CB02145483DFAEE575F0DD08A72C366369211CD")] = { - .new_limit = 70'000'000, .from_version = 5, .until = 1709164800}; - - // Increase limit for multiple accounts (https://t.me/tondev_news/129) until 2025-03-01 00:00:00 UTC - accounts[parse_addr("UQBeSl-dumOHieZ3DJkNKVkjeso7wZ0VpzR4LCbLGTQ8xr57")] = { - .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; - accounts[parse_addr("EQC3VcQ-43klww9UfimR58TBjBzk7GPupXQ3CNuthoNp-uTR")] = { - .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; - accounts[parse_addr("EQBhwBb8jvokGvfreHRRoeVxI237PrOJgyrsAhLA-4rBC_H5")] = { - .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; - accounts[parse_addr("EQCkoRp4OE-SFUoMEnYfL3vF43T3AzNfW8jyTC4yzk8cJqMS")] = { - .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; - accounts[parse_addr("UQBN5ICras79U8FYEm71ws34n-ZNIQ0LRNpckOUsIV3OebnC")] = { - .new_limit = 70'000'000, .from_version = 9, .until = 1740787200}; - accounts[parse_addr("EQBDanbCeUqI4_v-xrnAN0_I2wRvEIaLg1Qg2ZN5c6Zl1KOh")] = { - .new_limit = 225'000'000, .from_version = 9, .until = 1740787200}; - return accounts; - }(); - auto it = accounts.find({account.workchain, account.addr}); - if (it == accounts.end() || cfg.global_version < it->second.from_version || now >= it->second.until) { - return {}; - } - return it->second.new_limit; -} - -/** - * Computes the amount of gas that can be bought for a given amount of nanograms. - * Usually equal to `cfg.gas_bought_for(nanograms)` - * However, it overrides gas_limit from config in special cases. - * - * @param cfg The compute phase configuration. - * @param nanograms The amount of nanograms to compute gas for. - * - * @returns The amount of gas. - */ -td::uint64 Transaction::gas_bought_for(const ComputePhaseConfig& cfg, td::RefInt256 nanograms) { - if (auto new_limit = override_gas_limit(cfg, now, account)) { - gas_limit_overridden = true; - // Same as ComputePhaseConfig::gas_bought for, but with other gas_limit and max_gas_threshold - auto gas_limit = new_limit.value(); - LOG(INFO) << "overridding gas limit for account " << account.workchain << ":" << account.addr.to_hex() << " to " - << gas_limit; - auto max_gas_threshold = - compute_max_gas_threshold(cfg.gas_price256, gas_limit, cfg.flat_gas_limit, cfg.flat_gas_price); - if (nanograms.is_null() || sgn(nanograms) < 0) { - return 0; - } - if (nanograms >= max_gas_threshold) { - return gas_limit; - } - if (nanograms < cfg.flat_gas_price) { - return 0; - } - auto res = td::div((std::move(nanograms) - cfg.flat_gas_price) << 16, cfg.gas_price256); - return res->to_long() + cfg.flat_gas_limit; - } - return cfg.gas_bought_for(nanograms); -} - -/** - * Computes the gas limits for a transaction. - * - * @param cp The ComputePhase object to store the computed gas limits. - * @param cfg The compute phase configuration. - * - * @returns True if the gas limits were successfully computed, false otherwise. - */ -bool Transaction::compute_gas_limits(ComputePhase& cp, const ComputePhaseConfig& cfg) { - // Compute gas limits - if (account.is_special) { - cp.gas_max = cfg.special_gas_limit; - } else { - cp.gas_max = gas_bought_for(cfg, balance.grams); - } - if (trans_type != tr_ord || (account.is_special && cfg.special_gas_full)) { - // may use all gas that can be bought using remaining balance - cp.gas_limit = cp.gas_max; - } else { - // originally use only gas bought using remaining message balance - // if the message is "accepted" by the smart contract, the gas limit will be set to gas_max - cp.gas_limit = std::min(gas_bought_for(cfg, msg_balance_remaining.grams), cp.gas_max); - } - if (trans_type == tr_ord && !block::tlb::t_Message.is_internal(in_msg)) { - // external messages carry no balance, give them some credit to check whether they are accepted - cp.gas_credit = std::min(cfg.gas_credit, cp.gas_max); - } else { - cp.gas_credit = 0; - } - LOG(DEBUG) << "gas limits: max=" << cp.gas_max << ", limit=" << cp.gas_limit << ", credit=" << cp.gas_credit; - return true; -} - -/** - * Prepares a TVM stack for a transaction. - * - * @param cp The compute phase object. - * - * @returns A reference to the prepared virtual machine stack. - * Returns an empty reference if the transaction type is invalid. - */ -Ref Transaction::prepare_vm_stack(ComputePhase& cp) { - Ref stack_ref{true}; - td::RefInt256 acc_addr{true}; - CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256)); - vm::Stack& stack = stack_ref.write(); - switch (trans_type) { - case tr_tick: - case tr_tock: - stack.push_int(balance.grams); - stack.push_int(std::move(acc_addr)); - stack.push_bool(trans_type == tr_tock); - stack.push_smallint(-2); - return stack_ref; - case tr_ord: - stack.push_int(balance.grams); - stack.push_int(msg_balance_remaining.grams); - stack.push_cell(in_msg); - stack.push_cellslice(in_msg_body); - stack.push_bool(in_msg_extern); - return stack_ref; - default: - LOG(ERROR) << "cannot initialize stack for a transaction of type " << trans_type; - return {}; - } -} - -/** - * Prepares a random seed for a transaction. - * - * @param rand_seed The output random seed. - * @param cfg The configuration for the compute phase. - * - * @returns True if the random seed was successfully prepared, false otherwise. - */ -bool Transaction::prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const { - // we might use SHA256(block_rand_seed . addr . trans_lt) - // instead, we use SHA256(block_rand_seed . addr) - // if the smart contract wants to randomize further, it can use RANDOMIZE instruction - td::BitArray<256 + 256> data; - data.bits().copy_from(cfg.block_rand_seed.cbits(), 256); - if (cfg.global_version >= 8) { - (data.bits() + 256).copy_from(account.addr.cbits(), 256); - } else { - (data.bits() + 256).copy_from(account.addr_rewrite.cbits(), 256); - } - rand_seed.clear(); - data.compute_sha256(rand_seed); - return true; -} - -/** - * Prepares the c7 tuple (virtual machine context) for a compute phase of a transaction. - * - * @param cfg The configuration for the compute phase. - * - * @returns A reference to a Tuple object. - * - * @throws CollatorError if the rand_seed cannot be computed for the transaction. - */ -Ref Transaction::prepare_vm_c7(const ComputePhaseConfig& cfg) const { - td::BitArray<256> rand_seed; - td::RefInt256 rand_seed_int{true}; - if (!(prepare_rand_seed(rand_seed, cfg) && rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false))) { - LOG(ERROR) << "cannot compute rand_seed for transaction"; - throw CollatorError{"cannot generate valid SmartContractInfo"}; - return {}; - } - std::vector tuple = { - td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea - td::zero_refint(), // actions:Integer - td::zero_refint(), // msgs_sent:Integer - td::make_refint(now), // unixtime:Integer - td::make_refint(account.block_lt), // block_lt:Integer - td::make_refint(start_lt), // trans_lt:Integer - std::move(rand_seed_int), // rand_seed:Integer - balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] - my_addr, // myself:MsgAddressInt - vm::StackEntry::maybe(cfg.global_config) // global_config:(Maybe Cell) ] = SmartContractInfo; - }; - if (cfg.global_version >= 4) { - tuple.push_back(vm::StackEntry::maybe(new_code)); // code:Cell - if (msg_balance_remaining.is_valid()) { - tuple.push_back(msg_balance_remaining.as_vm_tuple()); // in_msg_value:[Integer (Maybe Cell)] - } else { - tuple.push_back(block::CurrencyCollection::zero().as_vm_tuple()); - } - tuple.push_back(storage_phase->fees_collected); // storage_fees:Integer - - // See crypto/block/mc-config.cpp#2223 (get_prev_blocks_info) - // [ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer] = BlockId; - // [ last_mc_blocks:[BlockId...] - // prev_key_block:BlockId - // last_mc_blocks_100:[BlockId...] ] : PrevBlocksInfo - // The only context where PrevBlocksInfo (13 parameter of c7) is null is inside emulator - // where it need to be set via transaction_emulator_set_prev_blocks_info (see emulator/emulator-extern.cpp) - // Inside validator, collator and liteserver checking external message contexts - // prev_blocks_info is always not null, since get_prev_blocks_info() - // may only return tuple or raise Error (See crypto/block/mc-config.cpp#2223) - tuple.push_back(vm::StackEntry::maybe(cfg.prev_blocks_info)); - } - if (cfg.global_version >= 6) { - tuple.push_back(vm::StackEntry::maybe(cfg.unpacked_config_tuple)); // unpacked_config_tuple:[...] - tuple.push_back(due_payment.not_null() ? due_payment : td::zero_refint()); // due_payment:Integer - tuple.push_back(compute_phase->precompiled_gas_usage - ? vm::StackEntry(td::make_refint(compute_phase->precompiled_gas_usage.value())) - : vm::StackEntry()); // precompiled_gas_usage:Integer - } - if (cfg.global_version >= 11) { - // in_msg_params:[...] - tuple.push_back(prepare_in_msg_params_tuple(trans_type == tr_ord ? &in_msg_info : nullptr, in_msg_state, - msg_balance_remaining)); - } - auto tuple_ref = td::make_cnt_ref>(std::move(tuple)); - LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple_ref).to_string(); - return vm::make_tuple_ref(std::move(tuple_ref)); -} - -/** - * Prepares tuple with unpacked parameters of the inbound message (for the 17th element of c7). - * `info` is: - * - For internal messages - just int_msg_info of the message - * - For external messages - artificial int_msg_info based on ext_msg_info of the messages. - * - For tick-tock transactions and get methods - nullptr. - * - * @param info Pointer to the message info. - * @param state_init State init of the message (null if absent). - * @param msg_balance_remaining Remaining balance of the message (it's sometimes different from value in info). - * - * @returns Tuple with message parameters. - */ -Ref Transaction::prepare_in_msg_params_tuple(const gen::CommonMsgInfo::Record_int_msg_info* info, - const Ref& state_init, - const CurrencyCollection& msg_balance_remaining) { - std::vector in_msg_params(10); - if (info != nullptr) { - in_msg_params[0] = td::make_refint(info->bounce ? -1 : 0); // bounce - in_msg_params[1] = td::make_refint(info->bounced ? -1 : 0); // bounced - in_msg_params[2] = info->src; // src_addr - in_msg_params[3] = info->fwd_fee.is_null() ? td::zero_refint() : tlb::t_Grams.as_integer(info->fwd_fee); // fwd_fee - in_msg_params[4] = td::make_refint(info->created_lt); // created_lt - in_msg_params[5] = td::make_refint(info->created_at); // created_at - auto value = info->value; - in_msg_params[6] = - info->value.is_null() ? td::zero_refint() : tlb::t_Grams.as_integer_skip(value.write()); // original value - in_msg_params[7] = msg_balance_remaining.is_valid() ? msg_balance_remaining.grams : td::zero_refint(); // value - in_msg_params[8] = msg_balance_remaining.is_valid() ? vm::StackEntry::maybe(msg_balance_remaining.extra) - : vm::StackEntry{}; // value extra - in_msg_params[9] = vm::StackEntry::maybe(state_init); // state_init - } else { - in_msg_params[0] = td::zero_refint(); // bounce - in_msg_params[1] = td::zero_refint(); // bounced - static Ref addr_none = vm::CellBuilder{}.store_zeroes(2).as_cellslice_ref(); - in_msg_params[2] = addr_none; // src_addr - in_msg_params[3] = td::zero_refint(); // fed_fee - in_msg_params[4] = td::zero_refint(); // created_lt - in_msg_params[5] = td::zero_refint(); // created_at - in_msg_params[6] = td::zero_refint(); // original value - in_msg_params[7] = td::zero_refint(); // value - in_msg_params[8] = vm::StackEntry{}; // value extra - in_msg_params[9] = vm::StackEntry{}; // state_init - } - return td::make_cnt_ref>(std::move(in_msg_params)); -} - -/** - * Computes the number of output actions in a list. - * - * @param list c5 cell. - * - * @returns The number of output actions. - */ -int output_actions_count(Ref list) { - int i = -1; - do { - ++i; - bool special = true; - auto cs = load_cell_slice_special(std::move(list), special); - if (special) { - break; - } - list = cs.prefetch_ref(); - } while (list.not_null()); - return i; -} - -/** - * Unpacks the message StateInit. - * - * @param cfg The configuration for the compute phase. - * @param lib_only If true, only unpack libraries from the state. - * @param forbid_public_libs Don't allow public libraries in initstate. - * - * @returns True if the unpacking is successful, false otherwise. - */ -bool Transaction::unpack_msg_state(const ComputePhaseConfig& cfg, bool lib_only, bool forbid_public_libs) { - block::gen::StateInit::Record state; - if (in_msg_state.is_null() || !tlb::unpack_cell(in_msg_state, state)) { - LOG(ERROR) << "cannot unpack StateInit from an inbound message"; - return false; - } - if (lib_only) { - in_msg_library = state.library->prefetch_ref(); - return true; - } - if (state.fixed_prefix_length->size() == 6) { - new_fixed_prefix_length = (signed char)(state.fixed_prefix_length->prefetch_ulong(6) - 32); - } else { - new_fixed_prefix_length = 0; - } - if (!cfg.disable_anycast) { - new_addr_rewrite_length = new_fixed_prefix_length; - } - if (state.special->size() > 1) { - int z = (int)state.special->prefetch_ulong(3); - if (z < 0) { - return false; - } - new_tick = z & 2; - new_tock = z & 1; - LOG(DEBUG) << "tick=" << new_tick << ", tock=" << new_tock; - } - td::Ref old_code = new_code, old_data = new_data, old_library = new_library; - new_code = state.code->prefetch_ref(); - new_data = state.data->prefetch_ref(); - new_library = state.library->prefetch_ref(); - auto size_limits = cfg.size_limits; - if (forbid_public_libs) { - size_limits.max_acc_public_libraries = 0; - } - auto S = check_state_limits(size_limits, cfg.global_version, false); - if (S.is_error()) { - LOG(DEBUG) << "Cannot unpack msg state: " << S.move_as_error(); - new_code = old_code; - new_data = old_data; - new_library = old_library; - return false; - } - return true; -} - -/** - * Computes the set of libraries to be used during TVM execution. - * - * @param cfg The configuration for the compute phase. - * - * @returns A vector of hashmaps with libraries. - */ -std::vector> Transaction::compute_vm_libraries(const ComputePhaseConfig& cfg) { - std::vector> lib_set; - if (in_msg_library.not_null()) { - lib_set.push_back(in_msg_library); - } - if (new_library.not_null()) { - lib_set.push_back(new_library); - } - auto global_libs = cfg.get_lib_root(); - if (global_libs.not_null()) { - lib_set.push_back(std::move(global_libs)); - } - return lib_set; -} - -/** - * Checks if the input message StateInit hash corresponds to the account address. - * - * @param cfg The configuration for the compute phase. - * - * @returns True if the input message state hash is valid, False otherwise. - */ -bool Transaction::check_in_msg_state_hash(const ComputePhaseConfig& cfg) { - CHECK(in_msg_state.not_null()); - CHECK(new_fixed_prefix_length >= 0 && new_fixed_prefix_length < 32); - td::Bits256 in_state_hash = in_msg_state->get_hash().bits(); - int d = new_fixed_prefix_length; - if ((in_state_hash.bits() + d).compare(account.addr.bits() + d, 256 - d)) { - return false; - } - orig_addr_rewrite = in_state_hash.bits(); - orig_addr_rewrite_set = true; - if (cfg.disable_anycast) { - my_addr = my_addr_exact; - return true; - } else { - return account.recompute_tmp_addr(my_addr, d, orig_addr_rewrite.bits()); - } -} - -/** - * Runs the precompiled smart contract and prepares the compute phase. - * - * @param cfg The configuration for the compute phase. - * @param impl Implementation of the smart contract - * - * @returns True if the contract was successfully executed, false otherwise. - */ -bool Transaction::run_precompiled_contract(const ComputePhaseConfig& cfg, precompiled::PrecompiledSmartContract& impl) { - ComputePhase& cp = *compute_phase; - CHECK(cp.precompiled_gas_usage); - td::uint64 gas_usage = cp.precompiled_gas_usage.value(); - td::RealCpuTimer timer; - auto result = - impl.run(my_addr, now, start_lt, balance, new_data, *in_msg_body, in_msg, msg_balance_remaining, in_msg_extern, - compute_vm_libraries(cfg), cfg.global_version, cfg.max_vm_data_depth, new_code, - cfg.unpacked_config_tuple, due_payment.not_null() ? due_payment : td::zero_refint(), gas_usage); - time_tvm = timer.elapsed_both(); - cp.vm_init_state_hash = td::Bits256::zero(); - cp.exit_code = result.exit_code; - cp.out_of_gas = false; - cp.vm_final_state_hash = td::Bits256::zero(); - cp.vm_steps = 0; - cp.gas_used = gas_usage; - cp.accepted = result.accepted; - cp.success = (cp.accepted && result.committed); - LOG(INFO) << "Running precompiled smart contract " << impl.get_name() << ": exit_code=" << result.exit_code - << " accepted=" << result.accepted << " success=" << cp.success << " gas_used=" << gas_usage - << " time=" << time_tvm.real << "s cpu_time=" << time_tvm.cpu; - if (cp.accepted & use_msg_state) { - was_activated = true; - acc_status = Account::acc_active; - } - if (cfg.with_vm_log) { - cp.vm_log = PSTRING() << "Running precompiled smart contract " << impl.get_name() - << ": exit_code=" << result.exit_code << " accepted=" << result.accepted - << " success=" << cp.success << " gas_used=" << gas_usage << " time=" << time_tvm.real << "s"; - } - if (cp.success) { - cp.new_data = impl.get_c4(); - cp.actions = impl.get_c5(); - int out_act_num = output_actions_count(cp.actions); - if (verbosity > 2) { - FLOG(INFO) { - sb << "new smart contract data: "; - bool can_be_special = true; - load_cell_slice_special(cp.new_data, can_be_special).print_rec(sb); - sb << "output actions: "; - block::gen::OutList{out_act_num}.print_ref(sb, cp.actions); - }; - } - } - cp.mode = 0; - cp.exit_arg = 0; - if (!cp.success && result.exit_arg) { - auto value = td::narrow_cast_safe(result.exit_arg.value()); - if (value.is_ok()) { - cp.exit_arg = value.ok(); - } - } - if (cp.accepted) { - if (account.is_special) { - cp.gas_fees = td::zero_refint(); - } else { - cp.gas_fees = cfg.compute_gas_price(cp.gas_used); - total_fees += cp.gas_fees; - balance -= cp.gas_fees; - } - LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " - << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " - << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); - CHECK(td::sgn(balance.grams) >= 0); - } - return true; -} - -/** - * Prepares the compute phase of a transaction, which includes running TVM. - * - * @param cfg The configuration for the compute phase. - * - * @returns True if the compute phase was successfully prepared and executed, false otherwise. - */ -bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { - // TODO: add more skip verifications + sometimes use state from in_msg to re-activate - // ... - compute_phase = std::make_unique(); - ComputePhase& cp = *(compute_phase.get()); - if (cfg.global_version >= 9) { - original_balance = balance; - if (msg_balance_remaining.is_valid()) { - original_balance -= msg_balance_remaining; - } - } else { - original_balance -= total_fees; - } - if (td::sgn(balance.grams) <= 0) { - // no gas - cp.skip_reason = ComputePhase::sk_no_gas; - return true; - } - // Compute gas limits - if (!compute_gas_limits(cp, cfg)) { - compute_phase.reset(); - return false; - } - if (!cp.gas_limit && !cp.gas_credit) { - // no gas - cp.skip_reason = ComputePhase::sk_no_gas; - return true; - } - if (in_msg_state.not_null()) { - LOG(DEBUG) << "HASH(in_msg_state) = " << in_msg_state->get_hash().bits().to_hex(256) - << ", account_state_hash = " << account.state_hash.to_hex(); - } else { - LOG(DEBUG) << "in_msg_state is null"; - } - if (in_msg_state.not_null() && - (acc_status == Account::acc_uninit || - (acc_status == Account::acc_frozen && account.state_hash == in_msg_state->get_hash().bits()))) { - if (acc_status == Account::acc_uninit && cfg.is_address_suspended(account.workchain, account.addr)) { - LOG(DEBUG) << "address is suspended, skipping compute phase"; - cp.skip_reason = ComputePhase::sk_suspended; - return true; - } - use_msg_state = true; - bool forbid_public_libs = - acc_status == Account::acc_uninit && account.is_masterchain(); // Forbid for deploying, allow for unfreezing - if (!(unpack_msg_state(cfg, false, forbid_public_libs) && - account.check_addr_rewrite_length(new_fixed_prefix_length))) { - LOG(DEBUG) << "cannot unpack in_msg_state, or it has bad fixed_prefix_length; cannot init account state"; - cp.skip_reason = ComputePhase::sk_bad_state; - return true; - } - if (acc_status == Account::acc_uninit && !check_in_msg_state_hash(cfg)) { - LOG(DEBUG) << "in_msg_state hash mismatch, cannot init account state"; - cp.skip_reason = ComputePhase::sk_bad_state; - return true; - } - if (cfg.disable_anycast && acc_status == Account::acc_uninit && - new_fixed_prefix_length > cfg.size_limits.max_acc_fixed_prefix_length) { - LOG(DEBUG) << "cannot init account state: too big fixed prefix length (" << new_fixed_prefix_length << ", max " - << cfg.size_limits.max_acc_fixed_prefix_length << ")"; - cp.skip_reason = ComputePhase::sk_bad_state; - return true; - } - } else if (acc_status != Account::acc_active) { - // no state, cannot perform transactions - cp.skip_reason = in_msg_state.not_null() ? ComputePhase::sk_bad_state : ComputePhase::sk_no_state; - return true; - } else if (in_msg_state.not_null()) { - if (cfg.allow_external_unfreeze) { - if (in_msg_extern && account.addr != in_msg_state->get_hash().bits()) { - // only for external messages with non-zero initstate in active accounts - LOG(DEBUG) << "in_msg_state hash mismatch in external message"; - cp.skip_reason = ComputePhase::sk_bad_state; - return true; - } - } - unpack_msg_state(cfg, true); // use only libraries - } - if (!cfg.allow_external_unfreeze) { - if (in_msg_extern && in_msg_state.not_null() && account.addr != in_msg_state->get_hash().bits()) { - LOG(DEBUG) << "in_msg_state hash mismatch in external message"; - cp.skip_reason = ComputePhase::sk_bad_state; - return true; - } - } - if (cfg.disable_anycast) { - my_addr = my_addr_exact; - new_addr_rewrite_length = 0; - force_remove_anycast_address = true; - } - - td::optional precompiled; - if (new_code.not_null() && trans_type == tr_ord) { - precompiled = cfg.precompiled_contracts.get_contract(new_code->get_hash().bits()); - } - - vm::GasLimits gas{(long long)cp.gas_limit, (long long)cp.gas_max, (long long)cp.gas_credit}; - if (precompiled) { - td::uint64 gas_usage = precompiled.value().gas_usage; - cp.precompiled_gas_usage = gas_usage; - if (gas_usage > cp.gas_limit) { - cp.skip_reason = ComputePhase::sk_no_gas; - return true; - } - auto impl = precompiled::get_implementation(new_code->get_hash().bits()); - if (impl != nullptr && !cfg.dont_run_precompiled_ && impl->required_version() <= cfg.global_version) { - return run_precompiled_contract(cfg, *impl); - } - - // Contract is marked as precompiled in global config, but implementation is not available - // In this case we run TVM and override gas_used - LOG(INFO) << "Unknown precompiled contract (code_hash=" << new_code->get_hash().to_hex() - << ", gas_usage=" << gas_usage << "), running VM"; - long long limit = account.is_special ? cfg.special_gas_limit : cfg.gas_limit; - gas = vm::GasLimits{limit, limit, gas.gas_credit ? limit : 0}; - } - - // initialize VM - Ref stack = prepare_vm_stack(cp); - if (stack.is_null()) { - compute_phase.reset(); - return false; - } - // OstreamLogger ostream_logger(error_stream); - // auto log = create_vm_log(error_stream ? &ostream_logger : nullptr); - LOG(DEBUG) << "creating VM"; - - std::unique_ptr logger; - auto vm_log = vm::VmLog(); - if (cfg.with_vm_log) { - size_t log_max_size = 256; - if (cfg.vm_log_verbosity > 4) { - log_max_size = 32 << 20; - } else if (cfg.vm_log_verbosity > 0) { - log_max_size = 1 << 20; - } - logger = std::make_unique(log_max_size); - vm_log.log_interface = logger.get(); - vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false); - if (cfg.vm_log_verbosity > 1) { - vm_log.log_mask |= vm::VmLog::ExecLocation; - if (cfg.vm_log_verbosity > 2) { - vm_log.log_mask |= vm::VmLog::GasRemaining; - if (cfg.vm_log_verbosity > 3) { - vm_log.log_mask |= vm::VmLog::DumpStack; - if (cfg.vm_log_verbosity > 4) { - vm_log.log_mask |= vm::VmLog::DumpStackVerbose; - vm_log.log_mask |= vm::VmLog::DumpC5; - } - } - } - } - } - vm::VmState vm{new_code, cfg.global_version, std::move(stack), gas, 1, new_data, vm_log, compute_vm_libraries(cfg)}; - vm.set_max_data_depth(cfg.max_vm_data_depth); - vm.set_c7(prepare_vm_c7(cfg)); // tuple with SmartContractInfo - vm.set_chksig_always_succeed(cfg.ignore_chksig); - vm.set_stop_on_accept_message(cfg.stop_on_accept_message); - // vm.incr_stack_trace(1); // enable stack dump after each step - - LOG(DEBUG) << "starting VM"; - cp.vm_init_state_hash = vm.get_state_hash(); - td::RealCpuTimer timer; - cp.exit_code = ~vm.run(); - time_tvm = timer.elapsed_both(); - LOG(DEBUG) << "VM terminated with exit code " << cp.exit_code; - cp.out_of_gas = (cp.exit_code == ~(int)vm::Excno::out_of_gas); - cp.vm_final_state_hash = vm.get_final_state_hash(cp.exit_code); - stack = vm.get_stack_ref(); - cp.vm_steps = (int)vm.get_steps_count(); - gas = vm.get_gas_limits(); - cp.gas_used = std::min(gas.gas_consumed(), gas.gas_limit); - cp.accepted = (gas.gas_credit == 0); - cp.success = (cp.accepted && vm.committed()); - if (cp.accepted & use_msg_state) { - was_activated = true; - acc_status = Account::acc_active; - } - if (precompiled) { - cp.gas_used = precompiled.value().gas_usage; - cp.vm_steps = 0; - cp.vm_init_state_hash = cp.vm_final_state_hash = td::Bits256::zero(); - if (cp.out_of_gas) { - LOG(ERROR) << "Precompiled smc got out_of_gas in TVM"; - return false; - } - } - LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas.gas_consumed() << ", max=" << gas.gas_max - << ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit; - LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success - << ", time=" << time_tvm.real << "s, cpu_time=" << time_tvm.cpu; - if (logger != nullptr) { - cp.vm_log = logger->get_log(); - } - if (cp.success) { - cp.new_data = vm.get_committed_state().c4; // c4 -> persistent data - cp.actions = vm.get_committed_state().c5; // c5 -> action list - int out_act_num = output_actions_count(cp.actions); - if (verbosity > 2) { - FLOG(INFO) { - sb << "new smart contract data: "; - bool can_be_special = true; - load_cell_slice_special(cp.new_data, can_be_special).print_rec(sb); - sb << "output actions: "; - block::gen::OutList{out_act_num}.print_ref(sb, cp.actions); - }; - } - } - cp.mode = 0; - cp.exit_arg = 0; - if (!cp.success && stack->depth() > 0) { - td::RefInt256 tos = stack->tos().as_int(); - if (tos.not_null() && tos->signed_fits_bits(32)) { - cp.exit_arg = (int)tos->to_long(); - } - } - if (cp.accepted) { - if (account.is_special) { - cp.gas_fees = td::zero_refint(); - } else { - cp.gas_fees = cfg.compute_gas_price(cp.gas_used); - total_fees += cp.gas_fees; - balance -= cp.gas_fees; - } - LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " - << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " - << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); - CHECK(td::sgn(balance.grams) >= 0); - } - cp.vm_loaded_cells = vm.extract_loaded_cells(); - return true; -} - -/** - * Prepares the action phase of a transaction. - * - * @param cfg The configuration for the action phase. - * - * @returns True if the action phase was prepared successfully, false otherwise. - */ -bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) { - if (!compute_phase || !compute_phase->success) { - return false; - } - action_phase = std::make_unique(); - ActionPhase& ap = *(action_phase.get()); - ap.result_code = -1; - ap.result_arg = 0; - ap.tot_actions = ap.spec_actions = ap.skipped_actions = ap.msgs_created = 0; - Ref list = compute_phase->actions; - assert(list.not_null()); - ap.action_list_hash = list->get_hash().bits(); - ap.remaining_balance = balance; - ap.end_lt = end_lt; - ap.total_fwd_fees = td::zero_refint(); - ap.total_action_fees = td::zero_refint(); - ap.reserved_balance.set_zero(); - ap.action_fine = td::zero_refint(); - - td::Ref old_code = new_code, old_data = new_data, old_library = new_library; - // 1 - ok, 0 - limits exceeded, -1 - fatal error - auto enforce_state_limits = [&]() -> int { - if (account.is_special) { - return 1; - } - auto S = check_state_limits(cfg.size_limits, cfg.global_version); - if (S.is_error()) { - if (S.code() != AccountStorageStat::errorcode_limits_exceeded) { - LOG(ERROR) << "Account storage stat error: " << S.move_as_error(); - return -1; - } - // Rollback changes to state, fail action phase - LOG(INFO) << "Account state size exceeded limits: " << S.move_as_error(); - new_account_storage_stat = {}; - new_code = old_code; - new_data = old_data; - new_library = old_library; - ap.result_code = 50; - ap.state_exceeds_limits = true; - return 0; - } - return 1; - }; - - int n = 0; - while (true) { - ap.action_list.push_back(list); - bool special = true; - auto cs = load_cell_slice_special(std::move(list), special); - if (special) { - ap.result_code = 32; // action list invalid - ap.result_arg = n; - ap.action_list_invalid = true; - LOG(DEBUG) << "action list invalid: special cell"; - return true; - } - if (!cs.size_ext()) { - break; - } - if (!cs.have_refs()) { - ap.result_code = 32; // action list invalid - ap.result_arg = n; - ap.action_list_invalid = true; - LOG(DEBUG) << "action list invalid: entry found with data but no next reference"; - return true; - } - list = cs.prefetch_ref(); - n++; - if (n > cfg.max_actions) { - ap.result_code = 33; // too many actions - ap.result_arg = n; - ap.action_list_invalid = true; - LOG(DEBUG) << "action list too long: more than " << cfg.max_actions << " actions"; - return true; - } - } - - ap.tot_actions = n; - ap.spec_actions = ap.skipped_actions = 0; - for (int i = n - 1; i >= 0; --i) { - ap.result_arg = n - 1 - i; - if (!block::gen::t_OutListNode.validate_ref(ap.action_list[i])) { - if (cfg.message_skip_enabled) { - // try to read mode from action_send_msg even if out_msg scheme is violated - // action should at least contain 40 bits: 32bit tag and 8 bit mode - // if (mode & 2), that is ignore error mode, skip action even for invalid message - // if there is no (mode & 2) but (mode & 16) presents - enable bounce if possible - bool special = true; - auto cs = load_cell_slice_special(ap.action_list[i], special); - if (!special) { - if ((cs.size() >= 40) && ((int)cs.fetch_ulong(32) == 0x0ec3c86d)) { - int mode = (int)cs.fetch_ulong(8); - if (mode & 2) { - ap.skipped_actions++; - ap.action_list[i] = {}; - continue; - } else if ((mode & 16) && cfg.bounce_on_fail_enabled) { - ap.bounce = true; - } - } - } - } - ap.result_code = 34; // action #i invalid or unsupported - ap.action_list_invalid = true; - LOG(DEBUG) << "invalid action " << ap.result_arg << " found while preprocessing action list: error code " - << ap.result_code; - return true; - } - } - ap.valid = true; - for (int i = n - 1; i >= 0; --i) { - if(ap.action_list[i].is_null()) { - continue; - } - ap.result_arg = n - 1 - i; - vm::CellSlice cs = load_cell_slice(ap.action_list[i]); - CHECK(cs.fetch_ref().not_null()); - int tag = block::gen::t_OutAction.get_tag(cs); - CHECK(tag >= 0); - int err_code = 34; - ap.need_bounce_on_fail = false; - switch (tag) { - case block::gen::OutAction::action_set_code: - err_code = try_action_set_code(cs, ap, cfg); - break; - case block::gen::OutAction::action_send_msg: - err_code = try_action_send_msg(cs, ap, cfg); - if (err_code == -2) { - err_code = try_action_send_msg(cs, ap, cfg, 1); - if (err_code == -2) { - err_code = try_action_send_msg(cs, ap, cfg, 2); - } - } - break; - case block::gen::OutAction::action_reserve_currency: - err_code = try_action_reserve_currency(cs, ap, cfg); - break; - case block::gen::OutAction::action_change_library: - err_code = try_action_change_library(cs, ap, cfg); - break; - } - if (err_code) { - ap.result_code = (err_code == -1 ? 34 : err_code); - ap.end_lt = end_lt; - if (err_code == -1 || err_code == 34) { - ap.action_list_invalid = true; - } - if (err_code == 37 || err_code == 38) { - ap.no_funds = true; - } - LOG(DEBUG) << "invalid action " << ap.result_arg << " in action list: error code " << ap.result_code; - // This is required here because changes to libraries are applied even if action phase fails - if (enforce_state_limits() == -1) { - return false; - } - if (cfg.action_fine_enabled) { - ap.action_fine = std::min(ap.action_fine, balance.grams); - ap.total_action_fees = ap.action_fine; - balance.grams -= ap.action_fine; - total_fees += ap.action_fine; - } - if (ap.need_bounce_on_fail) { - ap.bounce = true; - } - return true; - } - } - - if (cfg.action_fine_enabled) { - ap.total_action_fees += ap.action_fine; - } - end_lt = ap.end_lt; - if (ap.new_code.not_null()) { - new_code = ap.new_code; - } - new_data = compute_phase->new_data; // tentative persistent data update applied - int res = enforce_state_limits(); - if (res == -1) { - return false; - } - if (res == 0) { - if (cfg.extra_currency_v2) { - end_lt = ap.end_lt = start_lt + 1; - if (cfg.action_fine_enabled) { - ap.action_fine = std::min(ap.action_fine, balance.grams); - ap.total_action_fees = ap.action_fine; - balance.grams -= ap.action_fine; - total_fees += ap.action_fine; - } - } - return true; - } - - ap.result_arg = 0; - ap.result_code = 0; - CHECK(ap.remaining_balance.grams->sgn() >= 0); - CHECK(ap.reserved_balance.grams->sgn() >= 0); - ap.remaining_balance += ap.reserved_balance; - CHECK(ap.remaining_balance.is_valid()); - if (ap.acc_delete_req) { - CHECK(cfg.extra_currency_v2 ? ap.remaining_balance.grams->sgn() == 0 : ap.remaining_balance.is_zero()); - ap.acc_status_change = ActionPhase::acst_deleted; - acc_status = (ap.remaining_balance.is_zero() ? Account::acc_deleted : Account::acc_uninit); - was_deleted = true; - } - ap.success = true; - out_msgs = std::move(ap.out_msgs); - total_fees += - ap.total_action_fees; // NB: forwarding fees are not accounted here (they are not collected by the validators in this transaction) - balance = ap.remaining_balance; - return true; -} - -/** - * Tries to set the code for an account. - * - * @param cs The CellSlice containing the action data serialized as action_set_code TLB-scheme. - * @param ap The action phase object. - * @param cfg The action phase configuration. - * - * @returns 0 if the code was successfully set, -1 otherwise. - */ -int Transaction::try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { - block::gen::OutAction::Record_action_set_code rec; - if (!tlb::unpack_exact(cs, rec)) { - return -1; - } - ap.new_code = std::move(rec.new_code); - ap.code_changed = true; - ap.spec_actions++; - return 0; -} - -/** - * Tries to change the library in the transaction. - * - * @param cs The cell slice containing the action data serialized as action_change_library TLB-scheme. - * @param ap The action phase object. - * @param cfg The action phase configuration. - * - * @returns 0 if the action was successfully performed, - * -1 if there was an error unpacking the data or the mode is invalid, - * 41 if the library reference is required but is null, - * 43 if the number of cells in the library exceeds the limit, - * 42 if there was a VM error during the operation. - */ -int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { - block::gen::OutAction::Record_action_change_library rec; - if (!tlb::unpack_exact(cs, rec)) { - return -1; - } - // mode: +0 = remove library, +1 = add private library, +2 = add public library, +16 - bounce on fail - if (rec.mode & 16) { - if (!cfg.bounce_on_fail_enabled) { - return -1; - } - ap.need_bounce_on_fail = true; - rec.mode &= ~16; - } - if (rec.mode > 2) { - return -1; - } - Ref lib_ref = rec.libref->prefetch_ref(); - ton::Bits256 hash; - if (lib_ref.not_null()) { - hash = lib_ref->get_hash().bits(); - } else { - CHECK(rec.libref.write().fetch_ulong(1) == 0 && rec.libref.write().fetch_bits_to(hash)); - } - try { - vm::Dictionary dict{new_library, 256}; - if (!rec.mode) { - // remove library - dict.lookup_delete(hash); - LOG(DEBUG) << "removed " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex(); - } else { - auto val = dict.lookup(hash); - if (val.not_null()) { - bool is_public = val->prefetch_ulong(1); - auto ref = val->prefetch_ref(); - if (hash == ref->get_hash().bits()) { - lib_ref = ref; - if (is_public == (rec.mode >> 1)) { - // library already in required state - ap.spec_actions++; - return 0; - } - } - } - if (lib_ref.is_null()) { - // library code not found - return 41; - } - vm::CellStorageStat sstat; - auto cell_info = sstat.compute_used_storage(lib_ref).move_as_ok(); - if (sstat.cells > cfg.size_limits.max_library_cells || cell_info.max_merkle_depth > max_allowed_merkle_depth) { - return 43; - } - vm::CellBuilder cb; - CHECK(cb.store_bool_bool(rec.mode >> 1) && cb.store_ref_bool(std::move(lib_ref))); - CHECK(dict.set_builder(hash, cb)); - LOG(DEBUG) << "added " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex(); - } - new_library = std::move(dict).extract_root_cell(); - } catch (vm::VmError&) { - return 42; - } - ap.spec_actions++; - return 0; -} -} // namespace transaction - -/** - * Computes the forward fees for a message based on the number of cells and bits. - * - * msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms - * ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms - * bits in the root cell of a message are not included in msg.bits (lump_price pays for them) - * - * @param cells The number of cells in the message. - * @param bits The number of bits in the message. - * - * @returns The computed forward fees for the message. - */ -td::uint64 MsgPrices::compute_fwd_fees(td::uint64 cells, td::uint64 bits) const { - return lump_price + td::uint128(bit_price) - .mult(bits) - .add(td::uint128(cell_price).mult(cells)) - .add(td::uint128(0xffffu)) - .shr(16) - .lo(); -} - -/** - * Computes the forward fees for a message based on the number of cells and bits. - * Return the result as td::RefInt256 - * - * msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms - * ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms - * bits in the root cell of a message are not included in msg.bits (lump_price pays for them) - * - * @param cells The number of cells in the message. - * @param bits The number of bits in the message. - * - * @returns The computed forward fees for the message as td::RefInt256j. - */ -td::RefInt256 MsgPrices::compute_fwd_fees256(td::uint64 cells, td::uint64 bits) const { - return td::make_refint(lump_price) + - td::rshift(td::make_refint(bit_price) * bits + td::make_refint(cell_price) * cells, 16, - 1); // divide by 2^16 with ceil rounding -} - -/** - * Computes the forward fees and IHR fees for a message with the given number of cells and bits. - * - * @param cells The number of cells. - * @param bits The number of bits. - * @param ihr_disabled Flag indicating whether IHR is disabled. - * - * @returns A pair of values representing the forward fees and IHR fees. - */ -std::pair MsgPrices::compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits, - bool ihr_disabled) const { - td::uint64 fwd = compute_fwd_fees(cells, bits); - if (ihr_disabled) { - return std::pair(fwd, 0); - } - return std::pair(fwd, td::uint128(fwd).mult(ihr_factor).shr(16).lo()); -} - -/** - * Computes the part of the fees that go to the total fees of the current block. - * - * @param total The amount of fees. - * - * @returns The the part of the fees that go to the total fees of the current block. - */ -td::RefInt256 MsgPrices::get_first_part(td::RefInt256 total) const { - return (std::move(total) * first_frac) >> 16; -} - -/** - * Computes the part of the fees that go to the total fees of the current block. - * - * @param total The amount of fees. - * - * @returns The the part of the fees that go to the total fees of the current block. - */ -td::uint64 MsgPrices::get_first_part(td::uint64 total) const { - return td::uint128(total).mult(first_frac).shr(16).lo(); -} - -/** - * Computes the part of the fees that go to the total fees of the transit block. - * - * @param total The amount of fees. - * - * @returns The the part of the fees that go to the total fees of the transit block. - */ -td::RefInt256 MsgPrices::get_next_part(td::RefInt256 total) const { - return (std::move(total) * next_frac) >> 16; -} - -namespace transaction { -/** - * Checks if the source address is addr_none and replaces is with the account address. - * - * @param src_addr A reference to the source address of the message. - * - * @returns True if the source address is addr_none or is equal to the account address. - */ -bool Transaction::check_replace_src_addr(Ref& src_addr) const { - int t = (int)src_addr->prefetch_ulong(2); - if (!t && src_addr->size_ext() == 2) { - // addr_none$00 --> replace with the address of current smart contract - src_addr = my_addr; - return true; - } - if (t != 2) { - // invalid address (addr_extern and addr_var cannot be source addresses) - return false; - } - if (src_addr->contents_equal(*my_addr) || src_addr->contents_equal(*my_addr_exact)) { - // source address matches that of the current account - return true; - } - // only one valid case remaining: rewritten source address used, replace with the complete one - // (are we sure we want to allow this?) - return false; -} - -/** - * Checks the destination address of a message, rewrites it if it is an anycast address. - * - * @param dest_addr A reference to the destination address of the transaction. - * @param cfg The configuration for the action phase. - * @param is_mc A pointer to a boolean where it will be stored whether the destination is in the masterchain. - * @param allow_anycast Allow anycast the address. - * - * @returns True if the destination address is valid, false otherwise. - */ -bool Transaction::check_rewrite_dest_addr(Ref& dest_addr, const ActionPhaseConfig& cfg, - bool* is_mc, bool allow_anycast) const { - if (!dest_addr->prefetch_ulong(1)) { - // all external addresses allowed - if (is_mc) { - *is_mc = false; - } - return true; - } - bool repack = false; - int tag = block::gen::t_MsgAddressInt.get_tag(*dest_addr); - - block::gen::MsgAddressInt::Record_addr_var rec; - - if (tag == block::gen::MsgAddressInt::addr_var) { - if (!tlb::csr_unpack(dest_addr, rec)) { - // cannot unpack addr_var - LOG(DEBUG) << "cannot unpack addr_var in a destination address"; - return false; - } - if (rec.addr_len == 256 && rec.workchain_id >= -128 && rec.workchain_id < 128) { - LOG(DEBUG) << "destination address contains an addr_var to be repacked into addr_std"; - repack = true; - } - } else if (tag == block::gen::MsgAddressInt::addr_std) { - block::gen::MsgAddressInt::Record_addr_std recs; - if (!tlb::csr_unpack(dest_addr, recs)) { - // cannot unpack addr_std - LOG(DEBUG) << "cannot unpack addr_std in a destination address"; - return false; - } - rec.anycast = std::move(recs.anycast); - rec.addr_len = 256; - rec.workchain_id = recs.workchain_id; - rec.address = td::make_bitstring_ref(recs.address); - } else { - // unknown address format (not a MsgAddressInt) - LOG(DEBUG) << "destination address does not have a MsgAddressInt tag"; - return false; - } - if (rec.workchain_id != ton::masterchainId) { - // recover destination workchain info from configuration - auto it = cfg.workchains->find(rec.workchain_id); - if (it == cfg.workchains->end()) { - // undefined destination workchain - LOG(DEBUG) << "destination address contains unknown workchain_id " << rec.workchain_id; - return false; - } - if (!it->second->accept_msgs) { - // workchain does not accept new messages - LOG(DEBUG) << "destination address belongs to workchain " << rec.workchain_id << " not accepting new messages"; - return false; - } - if (!it->second->is_valid_addr_len(rec.addr_len)) { - // invalid address length for specified workchain - LOG(DEBUG) << "destination address has length " << rec.addr_len << " invalid for destination workchain " - << rec.workchain_id; - return false; - } - } - if (rec.anycast->size() > 1) { - if (!allow_anycast) { - return false; - } - // destination address is an anycast - vm::CellSlice cs{*rec.anycast}; - int d = (int)cs.fetch_ulong(6) - 32; - if (d <= 0 || d > 30) { - // invalid anycast prefix length - return false; - } - unsigned pfx = (unsigned)cs.fetch_ulong(d); - unsigned my_pfx = (unsigned)account.addr.cbits().get_uint(d); - if (pfx != my_pfx) { - // rewrite destination address - vm::CellBuilder cb; - CHECK(cb.store_long_bool(32 + d, 6) // just$1 depth:(#<= 30) - && cb.store_long_bool(my_pfx, d) // rewrite_pfx:(bits depth) - && (rec.anycast = load_cell_slice_ref(cb.finalize())).not_null()); - repack = true; - } - } - if (is_mc) { - *is_mc = (rec.workchain_id == ton::masterchainId); - } - if (!repack) { - return true; - } - if (rec.addr_len == 256 && rec.workchain_id >= -128 && rec.workchain_id < 128) { - // repack as an addr_std - vm::CellBuilder cb; - CHECK(cb.store_long_bool(2, 2) // addr_std$10 - && cb.append_cellslice_bool(std::move(rec.anycast)) // anycast:(Maybe Anycast) ... - && cb.store_long_bool(rec.workchain_id, 8) // workchain_id:int8 - && cb.append_bitstring(std::move(rec.address)) // address:bits256 - && (dest_addr = load_cell_slice_ref(cb.finalize())).not_null()); - } else { - // repack as an addr_var - CHECK(tlb::csr_pack(dest_addr, std::move(rec))); - } - CHECK(block::gen::t_MsgAddressInt.validate_csr(dest_addr)); - return true; -} - -/** - * Tries to send a message. - * - * @param cs0 The cell slice containing the action data serialized as action_send_msg TLB-scheme. - * @param ap The action phase. - * @param cfg The action phase configuration. - * @param redoing The index of the attempt, starting from 0. On later attempts tries to move message body and StateInit to separate cells. - * - * @returns 0 if the message is successfully sent or if the error may be ignored, error code otherwise. - * Returns -2 if the action should be attempted again. - */ -int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, const ActionPhaseConfig& cfg, - int redoing) { - block::gen::OutAction::Record_action_send_msg act_rec; - // mode: - // +128 = attach all remaining balance - // +64 = attach all remaining balance of the inbound message - // +32 = delete smart contract if balance becomes zero - // +1 = pay message fees - // +2 = skip if message cannot be sent - // +16 = bounce if action fails - vm::CellSlice cs{cs0}; - if (!tlb::unpack_exact(cs, act_rec)) { - return -1; - } - if ((act_rec.mode & 16) && cfg.bounce_on_fail_enabled) { - act_rec.mode &= ~16; - ap.need_bounce_on_fail = true; - } - if ((act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) { - return -1; - } - bool skip_invalid = (act_rec.mode & 2); - auto check_skip_invalid = [&](unsigned error_code) -> unsigned int { - if (skip_invalid) { - if (cfg.message_skip_enabled) { - ap.skipped_actions++; - } - return 0; - } - return error_code; - }; - // try to parse suggested message in act_rec.out_msg - td::RefInt256 fwd_fee, ihr_fee; - block::gen::MessageRelaxed::Record msg; - if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) { - return -1; - } - if (!block::tlb::validate_message_relaxed_libs(act_rec.out_msg)) { - LOG(DEBUG) << "outbound message has invalid libs in StateInit"; - return -1; - } - if (redoing >= 1) { - if (msg.init->size_refs() >= 2) { - LOG(DEBUG) << "moving the StateInit of a suggested outbound message into a separate cell"; - // init:(Maybe (Either StateInit ^StateInit)) - // transform (just (left z:StateInit)) into (just (right z:^StateInit)) - CHECK(msg.init.write().fetch_ulong(2) == 2); - vm::CellBuilder cb; - Ref cell; - CHECK(cb.append_cellslice_bool(std::move(msg.init)) // StateInit - && cb.finalize_to(cell) // -> ^StateInit - && cb.store_long_bool(3, 2) // (just (right ... )) - && cb.store_ref_bool(std::move(cell)) // z:^StateInit - && cb.finalize_to(cell)); - msg.init = vm::load_cell_slice_ref(cell); - } else { - redoing = 2; - } - } - if (redoing >= 2 && msg.body->size_ext() > 1 && msg.body->prefetch_ulong(1) == 0) { - LOG(DEBUG) << "moving the body of a suggested outbound message into a separate cell"; - // body:(Either X ^X) - // transform (left x:X) into (right x:^X) - CHECK(msg.body.write().fetch_ulong(1) == 0); - vm::CellBuilder cb; - Ref cell; - CHECK(cb.append_cellslice_bool(std::move(msg.body)) // X - && cb.finalize_to(cell) // -> ^X - && cb.store_long_bool(1, 1) // (right ... ) - && cb.store_ref_bool(std::move(cell)) // x:^X - && cb.finalize_to(cell)); - msg.body = vm::load_cell_slice_ref(cell); - } - - block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info; - bool ext_msg = msg.info->prefetch_ulong(1); - if (ext_msg) { - // ext_out_msg_info$11 constructor of CommonMsgInfoRelaxed - block::gen::CommonMsgInfoRelaxed::Record_ext_out_msg_info erec; - if (!tlb::csr_unpack(msg.info, erec)) { - return -1; - } - if (act_rec.mode & ~3) { - return -1; // invalid mode for an external message - } - info.src = std::move(erec.src); - info.dest = std::move(erec.dest); - // created_lt and created_at are ignored - info.ihr_disabled = true; - info.bounce = false; - info.bounced = false; - fwd_fee = ihr_fee = td::zero_refint(); - } else { - // int_msg_info$0 constructor - if (!tlb::csr_unpack(msg.info, info) || !block::tlb::t_CurrencyCollection.validate_csr(info.value)) { - return -1; - } - if (cfg.disable_custom_fess) { - fwd_fee = ihr_fee = td::zero_refint(); - } else { - fwd_fee = tlb::t_Grams.as_integer(info.fwd_fee); - ihr_fee = cfg.global_version >= 12 ? td::zero_refint() : tlb::t_Grams.as_integer(info.extra_flags); - } - if (cfg.disable_ihr_flag) { - info.ihr_disabled = true; - } - } - // set created_at and created_lt to correct values - info.created_at = now; - info.created_lt = ap.end_lt; - // always clear bounced flag - info.bounced = false; - // have to check source address - // it must be either our source address, or empty - if (!check_replace_src_addr(info.src)) { - LOG(DEBUG) << "invalid source address in a proposed outbound message"; - return 35; // invalid source address - } - bool to_mc = false; - if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc, !cfg.disable_anycast)) { - LOG(DEBUG) << "invalid destination address in a proposed outbound message"; - return check_skip_invalid(36); // invalid destination address - } - if (!ext_msg && cfg.extra_currency_v2) { - CurrencyCollection value; - if (!value.unpack(info.value)) { - LOG(DEBUG) << "invalid value:ExtraCurrencies in a proposed outbound message"; - return check_skip_invalid(37); // invalid value:CurrencyCollection - } - if (!CurrencyCollection::remove_zero_extra_currencies(value.extra, cfg.size_limits.max_msg_extra_currencies)) { - LOG(DEBUG) << "invalid value:ExtraCurrencies in a proposed outbound message: too many currencies (max " - << cfg.size_limits.max_msg_extra_currencies << ")"; - // Dict should be valid, since it was checked in t_OutListNode.validate_ref, so error here means limit exceeded - return check_skip_invalid(44); // invalid value:CurrencyCollection : too many extra currencies - } - info.value = value.pack(); - } - - // fetch message pricing info - const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain()); - // If action fails, account is required to pay fine_per_cell for every visited cell - // Number of visited cells is limited depending on available funds - unsigned max_cells = cfg.size_limits.max_msg_cells; - td::uint64 fine_per_cell = 0; - if (cfg.action_fine_enabled && !account.is_special) { - fine_per_cell = (msg_prices.cell_price >> 16) / 4; - td::RefInt256 funds = ap.remaining_balance.grams; - if (!ext_msg && !(act_rec.mode & 0x80) && !(act_rec.mode & 1)) { - if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) { - LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message"; - return check_skip_invalid(37); - } - block::CurrencyCollection value; - CHECK(value.unpack(info.value)); - CHECK(value.grams.not_null()); - td::RefInt256 new_funds = value.grams; - if (act_rec.mode & 0x40) { - if (msg_balance_remaining.is_valid()) { - new_funds += msg_balance_remaining.grams; - } - if (compute_phase) { - new_funds -= compute_phase->gas_fees; - } - new_funds -= ap.action_fine; - if (new_funds->sgn() < 0) { - LOG(DEBUG) - << "not enough value to transfer with the message: all of the inbound message value has been consumed"; - return check_skip_invalid(37); - } - } - funds = std::min(funds, new_funds); - } - if (funds->cmp(max_cells * fine_per_cell) < 0) { - max_cells = static_cast((funds / td::make_refint(fine_per_cell))->to_long()); - } - } - // compute size of message - vm::CellStorageStat sstat(max_cells); // for message size - // preliminary storage estimation of the resulting message - unsigned max_merkle_depth = 0; - auto add_used_storage = [&](const auto& x, unsigned skip_root_count) -> td::Status { - if (x.not_null()) { - TRY_RESULT(res, sstat.add_used_storage(x, true, skip_root_count)); - max_merkle_depth = std::max(max_merkle_depth, res.max_merkle_depth); - } - return td::Status::OK(); - }; - add_used_storage(msg.init, 3); // message init - add_used_storage(msg.body, 3); // message body (the root cell itself is not counted) - if (!ext_msg && !cfg.extra_currency_v2) { - add_used_storage(info.value->prefetch_ref(), 0); - } - auto collect_fine = [&] { - if (cfg.action_fine_enabled && !account.is_special) { - td::uint64 fine = fine_per_cell * std::min(max_cells, sstat.cells); - if (ap.remaining_balance.grams->cmp(fine) < 0) { - fine = ap.remaining_balance.grams->to_long(); - } - ap.action_fine += fine; - ap.remaining_balance.grams -= fine; - } - }; - if (sstat.cells > max_cells && max_cells < cfg.size_limits.max_msg_cells) { - LOG(DEBUG) << "not enough funds to process a message (max_cells=" << max_cells << ")"; - collect_fine(); - return check_skip_invalid(40); - } - if (sstat.bits > cfg.size_limits.max_msg_bits || sstat.cells > max_cells) { - LOG(DEBUG) << "message too large, invalid"; - collect_fine(); - return check_skip_invalid(40); - } - if (max_merkle_depth > max_allowed_merkle_depth) { - LOG(DEBUG) << "message has too big merkle depth, invalid"; - collect_fine(); - return check_skip_invalid(40); - } - LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits"; - - // compute forwarding fees - auto fees_c = msg_prices.compute_fwd_ihr_fees(sstat.cells, sstat.bits, info.ihr_disabled); - LOG(DEBUG) << "computed fwd fees = " << fees_c.first << " + " << fees_c.second; - - if (account.is_special) { - LOG(DEBUG) << "computed fwd fees set to zero for special account"; - fees_c.first = fees_c.second = 0; - } - - // set fees to computed values - if (fwd_fee->unsigned_fits_bits(63) && fwd_fee->to_long() < (long long)fees_c.first) { - fwd_fee = td::make_refint(fees_c.first); - } - if (fees_c.second && ihr_fee->unsigned_fits_bits(63) && ihr_fee->to_long() < (long long)fees_c.second) { - ihr_fee = td::make_refint(fees_c.second); - } - - Ref new_msg; - td::RefInt256 fees_collected, fees_total; - unsigned new_msg_bits; - - if (!ext_msg) { - // Process outbound internal message - // check value, check/compute ihr_fees, fwd_fees - // ... - if (!block::tlb::t_CurrencyCollection.validate_csr(info.value)) { - LOG(DEBUG) << "invalid value:CurrencyCollection in proposed outbound message"; - collect_fine(); - return check_skip_invalid(37); - } - if (info.ihr_disabled) { - // if IHR is disabled, IHR fees will be always zero - ihr_fee = td::zero_refint(); - } - // extract value to be carried by the message - block::CurrencyCollection req; - CHECK(req.unpack(info.value)); - CHECK(req.grams.not_null()); - - if (act_rec.mode & 0x80) { - // attach all remaining balance to this message - if (cfg.extra_currency_v2) { - req.grams = ap.remaining_balance.grams; - } else { - req = ap.remaining_balance; - } - act_rec.mode &= ~1; // pay fees from attached value - } else if (act_rec.mode & 0x40) { - // attach all remaining balance of the inbound message (in addition to the original value) - if (cfg.extra_currency_v2) { - req.grams += msg_balance_remaining.grams; - } else { - req += msg_balance_remaining; - } - if (!(act_rec.mode & 1)) { - req -= ap.action_fine; - if (compute_phase) { - req -= compute_phase->gas_fees; - } - if (!req.is_valid()) { - LOG(DEBUG) - << "not enough value to transfer with the message: all of the inbound message value has been consumed"; - collect_fine(); - return check_skip_invalid(37); - } - } - } - - // compute req_grams + fees - td::RefInt256 req_grams_brutto = req.grams; - fees_total = fwd_fee + ihr_fee; - if (act_rec.mode & 1) { - // we are going to pay the fees - req_grams_brutto += fees_total; - } else if (req.grams < fees_total) { - // receiver pays the fees (but cannot) - LOG(DEBUG) << "not enough value attached to the message to pay forwarding fees : have " << req.grams << ", need " - << fees_total; - collect_fine(); - return check_skip_invalid(37); // not enough grams - } else { - // decrease message value - req.grams -= fees_total; - } - - // check that we have at least the required value - if (ap.remaining_balance.grams < req_grams_brutto) { - LOG(DEBUG) << "not enough grams to transfer with the message : remaining balance is " - << ap.remaining_balance.to_str() << ", need " << req_grams_brutto << " (including forwarding fees)"; - collect_fine(); - return check_skip_invalid(37); // not enough grams - } - - if (cfg.extra_currency_v2 && !req.check_extra_currency_limit(cfg.size_limits.max_msg_extra_currencies)) { - LOG(DEBUG) << "too many extra currencies in the message : max " << cfg.size_limits.max_msg_extra_currencies; - return check_skip_invalid(44); // to many extra currencies - } - - Ref new_extra; - - if (!block::sub_extra_currency(ap.remaining_balance.extra, req.extra, new_extra)) { - LOG(DEBUG) << "not enough extra currency to send with the message: " - << block::CurrencyCollection{0, req.extra}.to_str() << " required, only " - << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " available"; - collect_fine(); - return check_skip_invalid(38); // not enough (extra) funds - } - if (ap.remaining_balance.extra.not_null() || req.extra.not_null()) { - LOG(DEBUG) << "subtracting extra currencies: " - << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() << " minus " - << block::CurrencyCollection{0, req.extra}.to_str() << " equals " - << block::CurrencyCollection{0, new_extra}.to_str(); - } - - auto fwd_fee_mine = msg_prices.get_first_part(fwd_fee); - auto fwd_fee_remain = fwd_fee - fwd_fee_mine; - - // re-pack message value - CHECK(req.pack_to(info.value)); - CHECK(block::tlb::t_Grams.pack_integer(info.fwd_fee, fwd_fee_remain)); - if (cfg.global_version < 12) { - CHECK(block::tlb::t_Grams.pack_integer(info.extra_flags, ihr_fee)); - } - - // serialize message - CHECK(tlb::csr_pack(msg.info, info)); - vm::CellBuilder cb; - if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) { - LOG(DEBUG) << "outbound message does not fit into a cell after rewriting"; - if (redoing == 2) { - collect_fine(); - return check_skip_invalid(39); - } - return -2; - } - - new_msg_bits = cb.size(); - new_msg = cb.finalize(); - - // clear msg_balance_remaining if it has been used - if (act_rec.mode & 0xc0) { - if (cfg.extra_currency_v2) { - msg_balance_remaining.grams = td::zero_refint(); - } else { - msg_balance_remaining.set_zero(); - } - } - - // update balance - ap.remaining_balance -= req_grams_brutto; - ap.remaining_balance.extra = std::move(new_extra); - CHECK(ap.remaining_balance.is_valid()); - CHECK(ap.remaining_balance.grams->sgn() >= 0); - fees_total = fwd_fee + ihr_fee; - fees_collected = fwd_fee_mine; - } else { - // external messages also have forwarding fees - if (ap.remaining_balance.grams < fwd_fee) { - LOG(DEBUG) << "not enough funds to pay for an outbound external message"; - collect_fine(); - return check_skip_invalid(37); // not enough grams - } - // repack message - // ext_out_msg_info$11 constructor of CommonMsgInfo - block::gen::CommonMsgInfo::Record_ext_out_msg_info erec; - erec.src = info.src; - erec.dest = info.dest; - erec.created_at = info.created_at; - erec.created_lt = info.created_lt; - CHECK(tlb::csr_pack(msg.info, erec)); - vm::CellBuilder cb; - if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) { - LOG(DEBUG) << "outbound message does not fit into a cell after rewriting"; - if (redoing == 2) { - collect_fine(); - return check_skip_invalid(39); - } - return -2; - } - - new_msg_bits = cb.size(); - new_msg = cb.finalize(); - - // update balance - ap.remaining_balance -= fwd_fee; - CHECK(ap.remaining_balance.is_valid()); - CHECK(td::sgn(ap.remaining_balance.grams) >= 0); - fees_collected = fees_total = fwd_fee; - } - - if (!block::tlb::t_Message.validate_ref(new_msg)) { - LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to hand-written check"; - collect_fine(); - return -1; - } - if (!block::gen::t_Message_Any.validate_ref(new_msg)) { - LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to automated check"; - FLOG(INFO) { - block::gen::t_Message_Any.print_ref(sb, new_msg); - vm::load_cell_slice(new_msg).print_rec(sb); - }; - collect_fine(); - return -1; - } - if (verbosity > 2) { - FLOG(INFO) { - sb << "converted outbound message: "; - block::gen::t_Message_Any.print_ref(sb, new_msg); - }; - } - - ap.msgs_created++; - ap.end_lt++; - - ap.out_msgs.push_back(std::move(new_msg)); - ap.total_action_fees += fees_collected; - ap.total_fwd_fees += fees_total; - - if ((act_rec.mode & 0xa0) == 0xa0) { - if (cfg.extra_currency_v2) { - CHECK(ap.remaining_balance.grams->sgn() == 0); - ap.acc_delete_req = ap.reserved_balance.grams->sgn() == 0; - } else { - CHECK(ap.remaining_balance.is_zero()); - ap.acc_delete_req = ap.reserved_balance.is_zero(); - } - } - - ap.tot_msg_bits += sstat.bits + new_msg_bits; - ap.tot_msg_cells += sstat.cells + 1; - - return 0; -} - -/** - * Tries to reserve a currency an action phase. - * - * @param cs The cell slice containing the action data serialized as action_reserve_currency TLB-scheme. - * @param ap The action phase. - * @param cfg The action phase configuration. - * - * @returns 0 if the currency is successfully reserved, error code otherwise. - */ -int Transaction::try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) { - block::gen::OutAction::Record_action_reserve_currency rec; - if (!tlb::unpack_exact(cs, rec)) { - return -1; - } - if ((rec.mode & 16) && cfg.bounce_on_fail_enabled) { - rec.mode &= ~16; - ap.need_bounce_on_fail = true; - } - if (rec.mode & ~15) { - return -1; - } - int mode = rec.mode; - LOG(INFO) << "in try_action_reserve_currency(" << mode << ")"; - CurrencyCollection reserve, newc; - if (!reserve.validate_unpack(std::move(rec.currency))) { - LOG(DEBUG) << "cannot parse currency field in action_reserve_currency"; - return -1; - } - if (cfg.extra_currency_v2 && reserve.has_extra()) { - LOG(DEBUG) << "cannot reserve extra currencies"; - return -1; - } - LOG(DEBUG) << "action_reserve_currency: mode=" << mode << ", reserve=" << reserve.to_str() - << ", balance=" << ap.remaining_balance.to_str() << ", original balance=" << original_balance.to_str(); - if (mode & 4) { - if (mode & 8) { - if (cfg.extra_currency_v2) { - reserve.grams = original_balance.grams - reserve.grams; - } else { - reserve = original_balance - reserve; - } - } else { - if (cfg.extra_currency_v2) { - reserve.grams += original_balance.grams; - } else { - reserve += original_balance; - } - } - } else if (mode & 8) { - LOG(DEBUG) << "invalid reserve mode " << mode; - return -1; - } - if (!reserve.is_valid() || td::sgn(reserve.grams) < 0) { - LOG(DEBUG) << "cannot reserve a negative amount: " << reserve.to_str(); - return -1; - } - if (mode & 2) { - if (cfg.reserve_extra_enabled) { - if (!reserve.clamp(ap.remaining_balance)) { - LOG(DEBUG) << "failed to clamp reserve amount " << mode; - return -1; - } - } else { - reserve.grams = std::min(reserve.grams, ap.remaining_balance.grams); - } - } - if (reserve.grams > ap.remaining_balance.grams) { - LOG(DEBUG) << "cannot reserve " << reserve.grams << " nanograms : only " << ap.remaining_balance.grams - << " available"; - return 37; // not enough grams - } - if (!block::sub_extra_currency(ap.remaining_balance.extra, reserve.extra, newc.extra)) { - LOG(DEBUG) << "not enough extra currency to reserve: " << block::CurrencyCollection{0, reserve.extra}.to_str() - << " required, only " << block::CurrencyCollection{0, ap.remaining_balance.extra}.to_str() - << " available"; - return 38; // not enough (extra) funds - } - newc.grams = ap.remaining_balance.grams - reserve.grams; - if (mode & 1) { - // leave only res_grams, reserve everything else - if (cfg.extra_currency_v2) { - std::swap(newc.grams, reserve.grams); - } else { - std::swap(newc, reserve); - } - } - // set remaining_balance to new_grams and new_extra - ap.remaining_balance = std::move(newc); - // increase reserved_balance by res_grams and res_extra - ap.reserved_balance += std::move(reserve); - CHECK(ap.reserved_balance.is_valid()); - CHECK(ap.remaining_balance.is_valid()); - LOG(INFO) << "changed remaining balance to " << ap.remaining_balance.to_str() << ", reserved balance to " - << ap.reserved_balance.to_str(); - ap.spec_actions++; - return 0; -} - -/** - * Calculates the number of public libraries in the dictionary. - * - * @param libraries The dictionary of account libraries. - * - * @returns The number of public libraries in the dictionary. - */ -static td::uint32 get_public_libraries_count(const td::Ref& libraries) { - td::uint32 count = 0; - vm::Dictionary dict{libraries, 256}; - dict.check_for_each([&](td::Ref value, td::ConstBitPtr key, int) { - if (block::is_public_library(key, std::move(value))) { - ++count; - } - return true; - }); - return count; -} - -/** - * Calculates the number of changes of public libraries in the dictionary. - * - * @param old_libraries The dictionary of account libraries before the transaction. - * @param new_libraries The dictionary of account libraries after the transaction. - * - * @returns The number of changed public libraries. - */ -static td::uint32 get_public_libraries_diff_count(const td::Ref& old_libraries, - const td::Ref& new_libraries) { - td::uint32 count = 0; - vm::Dictionary dict1{old_libraries, 256}; - vm::Dictionary dict2{new_libraries, 256}; - dict1.scan_diff(dict2, [&](td::ConstBitPtr key, int n, Ref val1, Ref val2) -> bool { - CHECK(n == 256); - bool is_public1 = val1.not_null() && block::is_public_library(key, val1); - bool is_public2 = val2.not_null() && block::is_public_library(key, val2); - if (is_public1 != is_public2) { - ++count; - } - return true; - }); - return count; -} - -/** - * Checks that the new account state fits in the limits. - * This function is not called for special accounts. - * - * @param size_limits The size limits configuration. - * @param global_version Global version (ConfigParam 8). - * @param is_account_stat Store storage stat in the Transaction's AccountStorageStat. - * - * @returns A `td::Status` indicating the result of the check. - * - If the state limits are within the allowed range, returns OK. - * - If the state limits exceed the maximum allowed range, returns an error with AccountStorageStat::errorcode_limits_exceeded code. - * - If an error occurred during storage stat calculation, returns other error. - */ -td::Status Transaction::check_state_limits(const SizeLimitsConfig& size_limits, int global_version, - bool is_account_stat) { - auto cell_equal = [](const td::Ref& a, const td::Ref& b) -> bool { - return a.is_null() || b.is_null() ? a.is_null() == b.is_null() : a->get_hash() == b->get_hash(); - }; - if (cell_equal(account.code, new_code) && cell_equal(account.data, new_data) && - cell_equal(account.library, new_library)) { - return td::Status::OK(); - } - AccountStorageStat storage_stat; - if (is_account_stat && account.account_storage_stat) { - storage_stat = AccountStorageStat{&account.account_storage_stat.value()}; - } - { - TD_PERF_COUNTER(transaction_storage_stat_a); - td::RealCpuTimer timer; - SCOPE_EXIT { - LOG_IF(INFO, timer.elapsed_real() > 0.1) << "Compute used storage (1) took " << timer.elapsed_real() << "s"; - if (is_account_stat) { - time_storage_stat += timer.elapsed_both(); - } - }; - if (is_account_stat && compute_phase) { - storage_stat.add_hint(compute_phase->vm_loaded_cells); - } - StorageStatCalculationContext context{is_account_stat}; - StorageStatCalculationContext::Guard guard{&context}; - if (is_account_stat) { - storage_stat_updates.push_back(new_code); - storage_stat_updates.push_back(new_data); - storage_stat_updates.push_back(new_library); - } - TRY_STATUS(storage_stat.replace_roots({new_code, new_data, new_library}, /* check_merkle_depth = */ true)); - } - - td::uint32 max_cells = account.is_masterchain() && global_version >= 12 ? size_limits.max_mc_acc_state_cells - : size_limits.max_acc_state_cells; - if (storage_stat.get_total_cells() > max_cells) { - return td::Status::Error(AccountStorageStat::errorcode_limits_exceeded, - PSTRING() << "account state is too big: cells=" << storage_stat.get_total_cells() - << " (max cells=" << max_cells << ")"); - } - if (account.is_masterchain() && !cell_equal(account.library, new_library)) { - auto libraries_count = get_public_libraries_count(new_library); - if (libraries_count > size_limits.max_acc_public_libraries) { - return td::Status::Error(AccountStorageStat::errorcode_limits_exceeded, - PSTRING() << "too many public libraries: " << libraries_count << " (max " - << size_limits.max_acc_public_libraries << ")"); - } - } - if (is_account_stat) { - // storage_stat will be reused in compute_state() - new_account_storage_stat.value_force() = std::move(storage_stat); - } - return td::Status::OK(); -} - -/** - * Prepares the bounce phase of a transaction. - * - * @param cfg The configuration for the action phase. - * - * @returns True if the bounce phase was successfully prepared, false otherwise. - */ -bool Transaction::prepare_bounce_phase(const ActionPhaseConfig& cfg) { - if (in_msg.is_null() || !bounce_enabled) { - return false; - } - bounce_phase = std::make_unique(); - BouncePhase& bp = *bounce_phase; - gen::Message::Record msg; - gen::CommonMsgInfo::Record_int_msg_info info; - auto cs = vm::load_cell_slice(in_msg); - if (!(tlb::unpack(cs, info) && gen::t_Maybe_Either_StateInit_Ref_StateInit.skip(cs) && cs.have(1) && - cs.have_refs((int)cs.prefetch_ulong(1)))) { - bounce_phase.reset(); - return false; - } - if (cs.fetch_ulong(1)) { - cs = vm::load_cell_slice(cs.prefetch_ref()); - } - - vm::CellBuilder body; - if (new_bounce_format) { - body.store_long(0xfffffffeU, 32); // new_bounce_body#fffffffe - if (new_bounce_format_full_body) { // original_body:^Cell - body.store_ref(vm::CellBuilder().append_cellslice(in_msg_body).finalize_novm()); - } else { - body.store_ref(vm::CellBuilder().store_bits(in_msg_body->as_bitslice()).finalize_novm()); - } - body.store_ref(vm::CellBuilder() - .append_cellslice(in_msg_info.value) // value:CurrencyCollection - .store_long(in_msg_info.created_lt, 64) // created_lt:uint64 - .store_long(in_msg_info.created_at, 32) // created_at:uint32 - .finalize_novm()); // original_info:^NewBounceOriginalInfo - if (compute_phase->skip_reason != ComputePhase::sk_none) { - body.store_long(0, 8); // bounced_by_phase:uint8 - body.store_long(-compute_phase->skip_reason, 32); // exit_code:int32 - } else if (!compute_phase->success) { - body.store_long(1, 8); // bounced_by_phase:uint8 - body.store_long(compute_phase->exit_code, 32); // exit_code:int32 - } else { - body.store_long(2, 8); // bounced_by_phase:uint8 - body.store_long(action_phase->result_code, 32); // exit_code:int32 - } - // compute_phase:(Maybe NewBounceComputePhaseInfo) - if (compute_phase->skip_reason != ComputePhase::sk_none) { - body.store_long(0, 1); - } else { - body.store_long(1, 1); - body.store_long(compute_phase->gas_used, 32); // gas_used:uint32 - body.store_long(compute_phase->vm_steps, 32); // vm_steps:uint32 - } - } else if (cfg.bounce_msg_body) { - int body_bits = std::min((int)cs.size(), cfg.bounce_msg_body); - body.store_long_bool(-1, 32); // 0xffffffff tag - body.append_bitslice(cs.prefetch_bits(body_bits)); // truncated message body - } - - info.ihr_disabled = true; - info.bounce = false; - info.bounced = true; - std::swap(info.src, info.dest); - bool to_mc = false; - if (!check_rewrite_dest_addr(info.dest, cfg, &to_mc)) { - LOG(DEBUG) << "invalid destination address in a bounced message"; - bounce_phase.reset(); - return false; - } - // fetch message pricing info - const MsgPrices& msg_prices = cfg.fetch_msg_prices(to_mc || account.is_masterchain()); - // compute size of message - vm::CellStorageStat sstat; // for message size - // preliminary storage estimation of the resulting message - sstat.add_used_storage(info.value->prefetch_ref()); - sstat.add_used_storage(body.get_refs()); - bp.msg_bits = sstat.bits; - bp.msg_cells = sstat.cells; - // compute forwarding fees - bp.fwd_fees = msg_prices.compute_fwd_fees(sstat.cells, sstat.bits); - // check whether the message has enough funds - auto msg_balance = msg_balance_remaining; - if (compute_phase && compute_phase->gas_fees.not_null()) { - msg_balance.grams -= compute_phase->gas_fees; - } - if (action_phase && action_phase->action_fine.not_null()) { - msg_balance.grams -= action_phase->action_fine; - } - if ((msg_balance.grams < 0) || - (msg_balance.grams->signed_fits_bits(64) && msg_balance.grams->to_long() < (long long)bp.fwd_fees)) { - // not enough funds - bp.nofunds = true; - return true; - } - // debit msg_balance_remaining from account's (tentative) balance - balance -= msg_balance; - CHECK(balance.is_valid()); - // debit total forwarding fees from the message's balance, then split forwarding fees into our part and remaining part - msg_balance -= td::make_refint(bp.fwd_fees); - bp.fwd_fees_collected = msg_prices.get_first_part(bp.fwd_fees); - bp.fwd_fees -= bp.fwd_fees_collected; - total_fees += td::make_refint(bp.fwd_fees_collected); - // serialize outbound message - info.created_lt = start_lt + 1 + out_msgs.size(); - end_lt++; - info.created_at = now; - vm::CellBuilder cb; - CHECK(cb.store_long_bool(5, 4) // int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool - && cb.append_cellslice_bool(info.src) // src:MsgAddressInt - && cb.append_cellslice_bool(info.dest) // dest:MsgAddressInt - && msg_balance.store(cb) // value:CurrencyCollection - && block::tlb::t_Grams.store_long(cb, 0) // extra_flags:(VarUInteger 16) - && block::tlb::t_Grams.store_long(cb, bp.fwd_fees) // fwd_fee:Grams - && cb.store_long_bool(info.created_lt, 64) // created_lt:uint64 - && cb.store_long_bool(info.created_at, 32) // created_at:uint32 - && cb.store_bool_bool(false)); // init:(Maybe ...) - if (cb.can_extend_by(1 + body.size(), body.size_refs())) { - // body:(Either X ^X) -> left X - CHECK(cb.store_bool_bool(false) && cb.append_builder_bool(body)); - } else { - // body:(Either X ^X) -> right ^X - CHECK(cb.store_bool_bool(true) && cb.store_builder_ref_bool(std::move(body))); - } - CHECK(cb.finalize_to(bp.out_msg)); - if (verbosity > 2) { - FLOG(INFO) { - sb << "generated bounced message: "; - block::gen::t_Message_Any.print_ref(sb, bp.out_msg); - }; - } - out_msgs.push_back(bp.out_msg); - bp.ok = true; - return true; -} -} // namespace transaction - -/* - * - * SERIALIZE PREPARED TRANSACTION - * - */ - -/** - * Stores the account status in a CellBuilder object. - * - * @param cb The CellBuilder object to store the account status in. - * @param acc_status The account status to store. - * - * @returns True if the account status was successfully stored, false otherwise. - */ -bool Account::store_acc_status(vm::CellBuilder& cb, int acc_status) const { - int v; - switch (acc_status) { - case acc_nonexist: - case acc_deleted: - v = 3; // acc_state_nonexist$11 - break; - case acc_uninit: - v = 0; // acc_state_uninit$00 - break; - case acc_frozen: - v = 1; // acc_state_frozen$01 - break; - case acc_active: - v = 2; // acc_state_active$10 - break; - default: - return false; - } - return cb.store_long_bool(v, 2); -} - -namespace transaction { -/** - * Computes the new state of the account. - * - * @param cfg The configuration for the serialization phase. - * - * @returns True if the state computation is successful, false otherwise. - */ -bool Transaction::compute_state(const SerializeConfig& cfg) { - if (new_total_state.not_null()) { - return true; - } - if (acc_status == Account::acc_uninit && !was_activated && balance.is_zero()) { - LOG(DEBUG) << "account is uninitialized and has zero balance, deleting it back"; - acc_status = Account::acc_nonexist; - was_created = false; - } - if (acc_status == Account::acc_deleted && !balance.is_zero()) { - acc_status = Account::acc_uninit; - } - if (acc_status == Account::acc_nonexist || acc_status == Account::acc_deleted) { - CHECK(balance.is_zero()); - vm::CellBuilder cb; - CHECK(cb.store_long_bool(0, 1) // account_none$0 - && cb.finalize_to(new_total_state)); - return true; - } - vm::CellBuilder cb; - CHECK(cb.store_long_bool(end_lt, 64) // account_storage$_ last_trans_lt:uint64 - && balance.store(cb)); // balance:CurrencyCollection - int ticktock = new_tick * 2 + new_tock; - unsigned si_pos = 0; - int fixed_prefix_length = cfg.disable_anycast ? new_fixed_prefix_length : account.addr_rewrite_length; - if (acc_status == Account::acc_uninit) { - CHECK(cb.store_long_bool(0, 2)); // account_uninit$00 = AccountState - } else if (acc_status == Account::acc_frozen) { - if (was_frozen) { - vm::CellBuilder cb2; - CHECK(fixed_prefix_length ? cb2.store_long_bool(fixed_prefix_length + 32, 6) // _ ... = StateInit - : cb2.store_long_bool(0, 1)); // ... fixed_prefix_length:(Maybe (## 5)) - CHECK(ticktock ? cb2.store_long_bool(ticktock | 4, 3) : cb2.store_long_bool(0, 1)); // special:(Maybe TickTock) - CHECK(cb2.store_maybe_ref(new_code) && cb2.store_maybe_ref(new_data) && cb2.store_maybe_ref(new_library)); - // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) - auto frozen_state = cb2.finalize(); - frozen_hash = frozen_state->get_hash().bits(); - if (verbosity >= 3 * 1) { // !!!DEBUG!!! - FLOG(INFO) { - sb << "freezing state of smart contract: "; - block::gen::t_StateInit.print_ref(sb, frozen_state); - CHECK(block::gen::t_StateInit.validate_ref(frozen_state)); - CHECK(block::tlb::t_StateInit.validate_ref(frozen_state)); - sb << "with hash " << frozen_hash.to_hex(); - }; - } - } - new_code.clear(); - new_data.clear(); - new_library.clear(); - if (frozen_hash == account.addr_orig) { - // if frozen_hash equals account's "original" address (before rewriting), do not need storing hash - CHECK(cb.store_long_bool(0, 2)); // account_uninit$00 = AccountState - } else { - CHECK(cb.store_long_bool(1, 2) // account_frozen$01 - && cb.store_bits_bool(frozen_hash)); // state_hash:bits256 - } - } else { - CHECK(acc_status == Account::acc_active && !was_frozen && !was_deleted); - si_pos = cb.size_ext() + 1; - CHECK(fixed_prefix_length ? cb.store_long_bool(fixed_prefix_length + 96, 7) // account_active$1 _:StateInit - : cb.store_long_bool(2, 2)); // ... fixed_prefix_length:(Maybe (## 5)) - CHECK(ticktock ? cb.store_long_bool(ticktock | 4, 3) : cb.store_long_bool(0, 1)); // special:(Maybe TickTock) - CHECK(cb.store_maybe_ref(new_code) && cb.store_maybe_ref(new_data) && cb.store_maybe_ref(new_library)); - // code:(Maybe ^Cell) data:(Maybe ^Cell) library:(HashmapE 256 SimpleLib) - } - auto storage = cb.finalize(); - new_storage = td::Ref(true, vm::NoVm(), storage); - if (si_pos) { - auto cs_ref = load_cell_slice_ref(storage); - CHECK(cs_ref.unique_write().skip_ext(si_pos)); - new_inner_state = std::move(cs_ref); - } else { - new_inner_state.clear(); - } - - td::Ref old_storage_for_stat = account.storage; - td::Ref new_storage_for_stat = new_storage; - if (cfg.extra_currency_v2) { - new_storage_for_stat = storage_without_extra_currencies(new_storage); - if (new_storage_for_stat.is_null()) { - return false; - } - if (old_storage_for_stat.not_null()) { - old_storage_for_stat = storage_without_extra_currencies(old_storage_for_stat); - if (old_storage_for_stat.is_null()) { - return false; - } - } - } else if (cfg.store_storage_dict_hash) { - LOG(ERROR) << "unsupported store_storage_dict_hash=true, extra_currency_v2=false"; - return false; - } - - bool storage_refs_changed = false; - if (old_storage_for_stat.is_null() || new_storage_for_stat->size_refs() != old_storage_for_stat->size_refs()) { - storage_refs_changed = true; - } else { - for (unsigned i = 0; i < new_storage_for_stat->size_refs(); i++) { - if (new_storage_for_stat->prefetch_ref(i)->get_hash() != old_storage_for_stat->prefetch_ref(i)->get_hash()) { - storage_refs_changed = true; - break; - } - } - } - - bool store_storage_dict_hash = cfg.store_storage_dict_hash && !account.is_masterchain(); - if (storage_refs_changed || - (store_storage_dict_hash && !account.storage_dict_hash && account.storage_used.cells > 25)) { - TD_PERF_COUNTER(transaction_storage_stat_b); - td::Timer timer; - if (!new_account_storage_stat && account.account_storage_stat) { - new_account_storage_stat = AccountStorageStat(&account.account_storage_stat.value()); - if (compute_phase) { - new_account_storage_stat.value().add_hint(compute_phase->vm_loaded_cells); - } - } - AccountStorageStat& stats = new_account_storage_stat.value_force(); - // Don't check Merkle depth and size here - they were checked in check_state_limits - auto roots = new_storage_for_stat->prefetch_all_refs(); - storage_stat_updates.insert(storage_stat_updates.end(), roots.begin(), roots.end()); - { - td::RealCpuTimer timer; - StorageStatCalculationContext context{true}; - StorageStatCalculationContext::Guard guard{&context}; - td::Status S = stats.replace_roots(roots); - time_storage_stat += timer.elapsed_both(); - if (S.is_error()) { - LOG(ERROR) << "Cannot recompute storage stats for account " << account.addr.to_hex() << ": " << S.move_as_error(); - return false; - } - } - // Root of AccountStorage is not counted in AccountStorageStat - new_storage_used.cells = stats.get_total_cells() + 1; - new_storage_used.bits = stats.get_total_bits() + new_storage_for_stat->size(); - if (store_storage_dict_hash && new_storage_used.cells >= cfg.size_limits.acc_state_cells_for_storage_dict) { - auto r_hash = stats.get_dict_hash(); - if (r_hash.is_error()) { - LOG(ERROR) << "Cannot compute storage dict hash for account " << account.addr.to_hex() << ": " - << r_hash.move_as_error(); - return false; - } - new_storage_dict_hash = r_hash.move_as_ok(); - } - if (timer.elapsed() > 0.1) { - LOG(INFO) << "Compute used storage (2) took " << timer.elapsed() << "s"; - } - } else { - new_storage_used = account.storage_used; - new_storage_used.bits -= old_storage_for_stat->size(); - new_storage_used.bits += new_storage_for_stat->size(); - new_account_storage_stat = {}; - if (store_storage_dict_hash) { - new_storage_dict_hash = account.storage_dict_hash; - } - } - - CHECK(cb.store_long_bool(1, 1) // account$1 - && cb.append_cellslice_bool(cfg.disable_anycast ? my_addr : account.my_addr) // addr:MsgAddressInt - && block::store_UInt7(cb, new_storage_used.cells) // storage_used$_ cells:(VarUInteger 7) - && block::store_UInt7(cb, new_storage_used.bits) // bits:(VarUInteger 7) - && cb.store_long_bool(new_storage_dict_hash ? 1 : 0, 3) // extra:StorageExtraInfo - && (!new_storage_dict_hash || cb.store_bits_bool(new_storage_dict_hash.value())) // dict_hash:uint256 - && cb.store_long_bool(last_paid, 32)); // last_paid:uint32 - if (due_payment.not_null() && td::sgn(due_payment) != 0) { - CHECK(cb.store_long_bool(1, 1) && block::tlb::t_Grams.store_integer_ref(cb, due_payment)); - // due_payment:(Maybe Grams) - } else { - CHECK(cb.store_long_bool(0, 1)); - } - CHECK(cb.append_cellslice_bool(new_storage)); - new_total_state = cb.finalize(); - if (verbosity > 2) { - FLOG(INFO) { - sb << "new account state: "; - block::gen::t_Account.print_ref(sb, new_total_state); - }; - } - CHECK(block::tlb::t_Account.validate_ref(new_total_state)); - return true; -} - -/** - * Serializes the transaction object using Transaction TLB-scheme. - * - * Updates root. - * - * @param cfg The configuration for the serialization. - * - * @returns True if the serialization is successful, False otherwise. - */ -bool Transaction::serialize(const SerializeConfig& cfg) { - if (root.not_null()) { - return true; - } - if (!compute_state(cfg)) { - return false; - } - vm::Dictionary dict{15}; - for (unsigned i = 0; i < out_msgs.size(); i++) { - td::BitArray<15> key{i}; - if (!dict.set_ref(key, out_msgs[i], vm::Dictionary::SetMode::Add)) { - return false; - } - } - vm::CellBuilder cb, cb2; - if (!(cb.store_long_bool(7, 4) // transaction$0111 - && cb.store_bits_bool(account.addr) // account_addr:bits256 - && cb.store_long_bool(start_lt) // lt:uint64 - && cb.store_bits_bool(account.last_trans_hash_) // prev_trans_hash:bits256 - && cb.store_long_bool(account.last_trans_lt_, 64) // prev_trans_lt:uint64 - && cb.store_long_bool(account.now_, 32) // now:uint32 - && cb.store_ulong_rchk_bool(out_msgs.size(), 15) // outmsg_cnt:uint15 - && account.store_acc_status(cb) // orig_status:AccountStatus - && account.store_acc_status(cb, acc_status) // end_status:AccountStatus - && cb2.store_maybe_ref(in_msg) // ^[ in_msg:(Maybe ^(Message Any)) ... - && std::move(dict).append_dict_to_bool(cb2) // out_msgs:(HashmapE 15 ^(Message Any)) - && cb.store_ref_bool(cb2.finalize()) // ] - && total_fees.store(cb) // total_fees:CurrencyCollection - && cb2.store_long_bool(0x72, 8) // update_hashes#72 - && cb2.store_bits_bool(account.total_state->get_hash().bits(), 256) // old_hash:bits256 - && cb2.store_bits_bool(new_total_state->get_hash().bits(), 256) // new_hash:bits256 - && cb.store_ref_bool(cb2.finalize()))) { // state_update:^(HASH_UPDATE Account) - return false; - } - - switch (trans_type) { - case tr_tick: // fallthrough - case tr_tock: { - vm::CellBuilder cb3; - bool act = compute_phase->success; - bool act_ok = act && action_phase->success; - CHECK(cb2.store_long_bool(trans_type == tr_tick ? 2 : 3, 4) // trans_tick_tock$000 is_tock:Bool - && serialize_storage_phase(cb2) // storage:TrStoragePhase - && serialize_compute_phase(cb2) // compute_ph:TrComputePhase - && cb2.store_bool_bool(act) // action:(Maybe - && (!act || (serialize_action_phase(cb3) // ^TrActionPhase) - && cb2.store_ref_bool(cb3.finalize()))) && - cb2.store_bool_bool(!act_ok) // aborted:Bool - && cb2.store_bool_bool(was_deleted) // destroyed:Bool - && cb.store_ref_bool(cb2.finalize()) && cb.finalize_to(root)); - break; - } - case tr_ord: { - vm::CellBuilder cb3; - bool have_storage = (bool)storage_phase; - bool have_credit = (bool)credit_phase; - bool have_bounce = (bool)bounce_phase; - bool act = compute_phase->success; - bool act_ok = act && action_phase->success; - CHECK(cb2.store_long_bool(0, 4) // trans_ord$0000 - && cb2.store_long_bool(!bounce_enabled, 1) // credit_first:Bool - && cb2.store_bool_bool(have_storage) // storage_ph:(Maybe - && (!have_storage || serialize_storage_phase(cb2)) // TrStoragePhase) - && cb2.store_bool_bool(have_credit) // credit_ph:(Maybe - && (!have_credit || serialize_credit_phase(cb2)) // TrCreditPhase) - && serialize_compute_phase(cb2) // compute_ph:TrComputePhase - && cb2.store_bool_bool(act) // action:(Maybe - && (!act || (serialize_action_phase(cb3) && cb2.store_ref_bool(cb3.finalize()))) // ^TrActionPhase) - && cb2.store_bool_bool(!act_ok) // aborted:Bool - && cb2.store_bool_bool(have_bounce) // bounce:(Maybe - && (!have_bounce || serialize_bounce_phase(cb2)) // TrBouncePhase - && cb2.store_bool_bool(was_deleted) // destroyed:Bool - && cb.store_ref_bool(cb2.finalize()) && cb.finalize_to(root)); - break; - } - default: - return false; - } - if (verbosity >= 3 * 1) { - FLOG(INFO) { - sb << "new transaction: "; - block::gen::t_Transaction.print_ref(sb, root); - vm::load_cell_slice(root).print_rec(sb); - }; - } - - if (!block::gen::t_Transaction.validate_ref(4096, root)) { - LOG(ERROR) << "newly-generated transaction failed to pass automated validation:"; - FLOG(INFO) { - vm::load_cell_slice(root).print_rec(sb); - block::gen::t_Transaction.print_ref(sb, root); - }; - root.clear(); - return false; - } - if (!block::tlb::t_Transaction.validate_ref(4096, root)) { - LOG(ERROR) << "newly-generated transaction failed to pass hand-written validation:"; - FLOG(INFO) { - vm::load_cell_slice(root).print_rec(sb); - block::gen::t_Transaction.print_ref(sb, root); - }; - root.clear(); - return false; - } - - return true; -} - -/** - * Serializes the storage phase of a transaction. - * - * @param cb The CellBuilder to store the serialized data. - * - * @returns True if the serialization is successful, false otherwise. - */ -bool Transaction::serialize_storage_phase(vm::CellBuilder& cb) { - if (!storage_phase) { - return false; - } - StoragePhase& sp = *storage_phase; - bool ok; - // tr_phase_storage$_ storage_fees_collected:Grams - if (sp.fees_collected.not_null()) { - ok = block::tlb::t_Grams.store_integer_ref(cb, sp.fees_collected); - } else { - ok = block::tlb::t_Grams.null_value(cb); - } - // storage_fees_due:(Maybe Grams) - ok &= block::store_Maybe_Grams_nz(cb, sp.fees_due); - // status_change:AccStatusChange - if (sp.deleted || sp.frozen) { - ok &= cb.store_long_bool(sp.deleted ? 3 : 2, 2); // acst_frozen$10 acst_deleted$11 - } else { - ok &= cb.store_long_bool(0, 1); // acst_unchanged$0 = AccStatusChange - } - return ok; -} - -/** - * Serializes the credit phase of a transaction. - * - * @param cb The CellBuilder to store the serialized data. - * - * @returns True if the credit phase was successfully serialized, false otherwise. - */ -bool Transaction::serialize_credit_phase(vm::CellBuilder& cb) { - if (!credit_phase) { - return false; - } - CreditPhase& cp = *credit_phase; - // tr_phase_credit$_ due_fees_collected:(Maybe Grams) credit:CurrencyCollection - return block::store_Maybe_Grams_nz(cb, cp.due_fees_collected) && cp.credit.store(cb); -} - -/** - * Serializes the compute phase of a transaction. - * - * @param cb The CellBuilder to store the serialized data. - * - * @returns True if the serialization was successful, false otherwise. - */ -bool Transaction::serialize_compute_phase(vm::CellBuilder& cb) { - if (!compute_phase) { - return false; - } - ComputePhase& cp = *compute_phase; - switch (cp.skip_reason) { - // tr_compute_phase_skipped$0 reason:ComputeSkipReason; - case ComputePhase::sk_no_state: - return cb.store_long_bool(0, 3); // cskip_no_state$00 = ComputeSkipReason; - case ComputePhase::sk_bad_state: - return cb.store_long_bool(1, 3); // cskip_bad_state$01 = ComputeSkipReason; - case ComputePhase::sk_no_gas: - return cb.store_long_bool(2, 3); // cskip_no_gas$10 = ComputeSkipReason; - case ComputePhase::sk_suspended: - return cb.store_long_bool(0b0110, 4); // cskip_suspended$110 = ComputeSkipReason; - case ComputePhase::sk_none: - break; - default: - return false; - } - vm::CellBuilder cb2; - bool ok, credit = (cp.gas_credit != 0), exarg = (cp.exit_arg != 0); - ok = cb.store_long_bool(1, 1) // tr_phase_compute_vm$1 - && cb.store_long_bool(cp.success, 1) // success:Bool - && cb.store_long_bool(cp.msg_state_used, 1) // msg_state_used:Bool - && cb.store_long_bool(cp.account_activated, 1) // account_activated:Bool - && block::tlb::t_Grams.store_integer_ref(cb, cp.gas_fees) // gas_fees:Grams - && block::store_UInt7(cb2, cp.gas_used) // ^[ gas_used:(VarUInteger 7) - && block::store_UInt7(cb2, cp.gas_limit) // gas_limit:(VarUInteger 7) - && cb2.store_long_bool(credit, 1) // gas_credit:(Maybe (VarUInteger 3)) - && (!credit || block::tlb::t_VarUInteger_3.store_long(cb2, cp.gas_credit)) && - cb2.store_long_rchk_bool(cp.mode, 8) // mode:int8 - && cb2.store_long_bool(cp.exit_code, 32) // exit_code:int32 - && cb2.store_long_bool(exarg, 1) // exit_arg:(Maybe int32) - && (!exarg || cb2.store_long_bool(cp.exit_arg, 32)) && - cb2.store_ulong_rchk_bool(cp.vm_steps, 32) // vm_steps:uint32 - && cb2.store_bits_bool(cp.vm_init_state_hash) // vm_init_state_hash:bits256 - && cb2.store_bits_bool(cp.vm_final_state_hash) // vm_final_state_hash:bits256 - && cb.store_ref_bool(cb2.finalize()); // ] = TrComputePhase - return ok; -} - -/** - * Serializes the action phase of a transaction. - * - * @param cb The CellBuilder to store the serialized data. - * - * @returns True if the serialization is successful, false otherwise. - */ -bool Transaction::serialize_action_phase(vm::CellBuilder& cb) { - if (!action_phase) { - return false; - } - ActionPhase& ap = *action_phase; - bool ok, arg = (ap.result_arg != 0); - ok = cb.store_long_bool(ap.success, 1) // tr_phase_action$_ success:Bool - && cb.store_long_bool(ap.valid, 1) // valid:Bool - && cb.store_long_bool(ap.no_funds, 1) // no_funds:Bool - && cb.store_long_bool(ap.acc_status_change, (ap.acc_status_change >> 1) + 1) // status_change:AccStatusChange - && block::store_Maybe_Grams_nz(cb, ap.total_fwd_fees) // total_fwd_fees:(Maybe Grams) - && block::store_Maybe_Grams_nz(cb, ap.total_action_fees) // total_action_fees:(Maybe Grams) - && cb.store_long_bool(ap.result_code, 32) // result_code:int32 - && cb.store_long_bool(arg, 1) // result_arg:(Maybe - && (!arg || cb.store_long_bool(ap.result_arg, 32)) // uint32) - && cb.store_ulong_rchk_bool(ap.tot_actions, 16) // tot_actions:uint16 - && cb.store_ulong_rchk_bool(ap.spec_actions, 16) // spec_actions:uint16 - && cb.store_ulong_rchk_bool(ap.skipped_actions, 16) // skipped_actions:uint16 - && cb.store_ulong_rchk_bool(ap.msgs_created, 16) // msgs_created:uint16 - && cb.store_bits_bool(ap.action_list_hash) // action_list_hash:bits256 - && block::store_UInt7(cb, ap.tot_msg_cells, ap.tot_msg_bits); // tot_msg_size:StorageUsed - return ok; -} - -/** - * Serializes the bounce phase of a transaction. - * - * @param cb The CellBuilder to store the serialized data. - * - * @returns True if the bounce phase was successfully serialized, false otherwise. - */ -bool Transaction::serialize_bounce_phase(vm::CellBuilder& cb) { - if (!bounce_phase) { - return false; - } - BouncePhase& bp = *bounce_phase; - if (!(bp.ok ^ bp.nofunds)) { - return false; - } - if (bp.nofunds) { - return cb.store_long_bool(1, 2) // tr_phase_bounce_nofunds$01 - && block::store_UInt7(cb, bp.msg_cells, bp.msg_bits) // msg_size:StorageUsed - && block::tlb::t_Grams.store_long(cb, bp.fwd_fees); // req_fwd_fees:Grams - } else { - return cb.store_long_bool(1, 1) // tr_phase_bounce_ok$1 - && block::store_UInt7(cb, bp.msg_cells, bp.msg_bits) // msg_size:StorageUsed - && block::tlb::t_Grams.store_long(cb, bp.fwd_fees_collected) // msg_fees:Grams - && block::tlb::t_Grams.store_long(cb, bp.fwd_fees); // fwd_fees:Grams - } -} - -/** - * Estimates the block storage profile increment if the transaction is added to the block. - * - * @param store_stat The current storage statistics of the block. - * @param usage_tree The usage tree of the block. - * - * @returns The estimated block storage profile increment. - * Returns Error if the transaction is not serialized or if its new state is not computed. - */ -td::Result Transaction::estimate_block_storage_profile_incr( - const vm::NewCellStorageStat& store_stat, const vm::CellUsageTree* usage_tree) const { - if (root.is_null()) { - return td::Status::Error("Cannot estimate the size profile of a transaction before it is serialized"); - } - if (new_total_state.is_null()) { - return td::Status::Error("Cannot estimate the size profile of a transaction before its new state is computed"); - } - return store_stat.tentative_add_proof(new_total_state, usage_tree) + store_stat.tentative_add_cell(root); -} - -/** - * Updates the limits status of a block. - * - * @param blimst The block limit status object to update. - * @param with_size Flag indicating whether to update the size limits. - * - * @returns True if the limits were successfully updated, False otherwise. - */ -bool Transaction::update_limits(block::BlockLimitStatus& blimst, bool with_gas, bool with_size) const { - if (!(blimst.update_lt(end_lt) && blimst.update_gas(with_gas ? gas_used() : 0))) { - return false; - } - if (with_size) { - if (!(blimst.add_proof(new_total_state) && blimst.add_cell(root) && blimst.add_transaction() && - blimst.add_account(is_first))) { - return false; - } - if (account.is_masterchain()) { - if (was_frozen || was_deleted) { - blimst.public_library_diff += get_public_libraries_count(account.orig_library); - } else { - blimst.public_library_diff += get_public_libraries_diff_count(account.orig_library, new_library); - } - } - } - return true; -} - -/* - * - * COMMIT TRANSACTION - * - */ - -/** - * Commits a transaction for a given account. - * - * @param acc The account to commit the transaction for. - * - * @returns A reference to the root cell of the serialized transaction. - */ -Ref Transaction::commit(Account& acc) { - CHECK(account.last_trans_end_lt_ <= start_lt && start_lt < end_lt); - CHECK(root.not_null()); - CHECK(new_total_state.not_null()); - CHECK((const void*)&acc == (const void*)&account); - // export all fields modified by the Transaction into original account - // NB: this is the only method that modifies account - if (force_remove_anycast_address) { - CHECK(acc.forget_addr_rewrite_length()); - } else if (orig_addr_rewrite_set && new_addr_rewrite_length >= 0 && acc.status != Account::acc_active && - acc_status == Account::acc_active) { - LOG(DEBUG) << "setting address rewriting info for newly-activated account " << acc.addr.to_hex() - << " with addr_rewrite_length=" << new_addr_rewrite_length - << ", orig_addr_rewrite=" << orig_addr_rewrite.bits().to_hex(new_addr_rewrite_length); - CHECK(acc.init_rewrite_addr(new_addr_rewrite_length, orig_addr_rewrite.bits())); - } - acc.status = (acc_status == Account::acc_deleted ? Account::acc_nonexist : acc_status); - acc.last_trans_lt_ = start_lt; - acc.last_trans_end_lt_ = end_lt; - acc.last_trans_hash_ = root->get_hash().bits(); - acc.last_paid = last_paid; - acc.storage_used = new_storage_used; - if (new_account_storage_stat) { - if (acc.account_storage_stat) { - acc.account_storage_stat.value().apply_child_stat(std::move(new_account_storage_stat.value())); - } else { - acc.account_storage_stat = std::move(new_account_storage_stat); - } - } - acc.storage_dict_hash = new_storage_dict_hash; - acc.storage = new_storage; - acc.balance = std::move(balance); - acc.due_payment = std::move(due_payment); - acc.total_state = std::move(new_total_state); - acc.inner_state = std::move(new_inner_state); - if (was_frozen) { - acc.state_hash = frozen_hash; - } - acc.my_addr = std::move(my_addr); - // acc.my_addr_exact = std::move(my_addr_exact); - acc.code = std::move(new_code); - acc.data = std::move(new_data); - acc.library = std::move(new_library); - if (acc.status == Account::acc_active) { - acc.tick = new_tick; - acc.tock = new_tock; - acc.fixed_prefix_length = new_fixed_prefix_length; - } else { - CHECK(acc.deactivate()); - } - end_lt = 0; - acc.push_transaction(root, start_lt); - return root; -} - -/** - * Extracts the output message at the specified index from the transaction. - * - * @param i The index of the output message to extract. - * - * @returns A pair of the logical time and the extracted output message. - */ -LtCellRef Transaction::extract_out_msg(unsigned i) { - return {start_lt + i + 1, std::move(out_msgs.at(i))}; -} - -/** - * Extracts the output message at index i from the transaction. - * - * @param i The index of the output message to extract. - * - * @returns A triple of the logical time, the extracted output message and the transaction root. - */ -NewOutMsg Transaction::extract_out_msg_ext(unsigned i) { - return {start_lt + i + 1, std::move(out_msgs.at(i)), root, i}; -} - -/** - * Extracts the outgoing messages from the transaction and adds them to the given list. - * - * @param list The list to which the outgoing messages will be added. - */ -void Transaction::extract_out_msgs(std::vector& list) { - for (unsigned i = 0; i < out_msgs.size(); i++) { - list.emplace_back(start_lt + i + 1, std::move(out_msgs[i])); - } -} -} // namespace transaction - -/** - * Adds a transaction to the account's transaction list. - * - * @param trans_root The root of the transaction cell. - * @param trans_lt The logical time of the transaction. - */ -void Account::push_transaction(Ref trans_root, ton::LogicalTime trans_lt) { - transactions.emplace_back(trans_lt, std::move(trans_root)); -} - -/** - * Serializes an account block for the account using AccountBlock TLB-scheme. - * - * @param cb The CellBuilder used to store the serialized data. - * - * @returns True if the account block was successfully created, false otherwise. - */ -bool Account::create_account_block(vm::CellBuilder& cb) { - if (transactions.empty()) { - return false; - } - if (!(cb.store_long_bool(5, 4) // acc_trans#5 - && cb.store_bits_bool(addr))) { // account_addr:bits256 - return false; - } - vm::AugmentedDictionary dict{64, block::tlb::aug_AccountTransactions}; - for (auto& z : transactions) { - if (!dict.set_ref(td::BitArray<64>{(long long)z.first}, z.second, vm::Dictionary::SetMode::Add)) { - LOG(ERROR) << "error creating the list of transactions for account " << addr.to_hex() - << " : cannot add transaction with lt=" << z.first; - return false; - } - } - Ref dict_root = std::move(dict).extract_root_cell(); - // transactions:(HashmapAug 64 ^Transaction Grams) - if (dict_root.is_null() || !cb.append_cellslice_bool(vm::load_cell_slice(std::move(dict_root)))) { - return false; - } - vm::CellBuilder cb2; - return cb2.store_long_bool(0x72, 8) // update_hashes#72 - && cb2.store_bits_bool(orig_total_state->get_hash().bits(), 256) // old_hash:bits256 - && cb2.store_bits_bool(total_state->get_hash().bits(), 256) // new_hash:bits256 - && cb.store_ref_bool(cb2.finalize()); // state_update:^(HASH_UPDATE Account) -} - -/** - * Checks if the libraries stored in the account object have changed. - * - * @returns True if the libraries have changed, False otherwise. - */ -bool Account::libraries_changed() const { - bool s = orig_library.not_null(); - bool t = library.not_null(); - if (s & t) { - return orig_library->get_hash() != library->get_hash(); - } else { - return s != t; - } -} - -/** - * Fetches and initializes various configuration parameters from masterchain config for transaction processing. - * - * @param config The masterchain configuration. - * @param old_mparams Pointer to store a dictionary of mandatory parameters (ConfigParam 9). - * @param storage_prices Pointer to store the storage prices. - * @param storage_phase_cfg Pointer to store the storage phase configuration. - * @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`. - * @param compute_phase_cfg Pointer to store the compute phase configuration. - * @param action_phase_cfg Pointer to store the action phase configuration. - * @param serialize_cfg Pointer to store the serialize phase configuration. - * @param masterchain_create_fee Pointer to store the masterchain create fee. - * @param basechain_create_fee Pointer to store the basechain create fee. - * @param wc The workchain ID. - * @param now The current Unix time. - */ -td::Status FetchConfigParams::fetch_config_params( - const block::ConfigInfo& config, Ref* old_mparams, std::vector* storage_prices, - StoragePhaseConfig* storage_phase_cfg, td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, - ActionPhaseConfig* action_phase_cfg, SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee, - td::RefInt256* basechain_create_fee, ton::WorkchainId wc, ton::UnixTime now) { - auto prev_blocks_info = config.get_prev_blocks_info(); - if (prev_blocks_info.is_error()) { - return prev_blocks_info.move_as_error_prefix( - td::Status::Error(-668, "cannot fetch prev blocks info from masterchain configuration: ")); - } - return fetch_config_params(config, prev_blocks_info.move_as_ok(), old_mparams, storage_prices, storage_phase_cfg, - rand_seed, compute_phase_cfg, action_phase_cfg, serialize_cfg, masterchain_create_fee, - basechain_create_fee, wc, now); -} - -/** - * Fetches and initializes various configuration parameters from masterchain config for transaction processing. - * - * @param config The masterchain configuration. - * @param prev_blocks_info The tuple with information about previous blocks. - * @param old_mparams Pointer to store a dictionary of mandatory parameters (ConfigParam 9). - * @param storage_prices Pointer to store the storage prices. - * @param storage_phase_cfg Pointer to store the storage phase configuration. - * @param rand_seed Pointer to the random seed. Generates a new seed if the value is `td::Bits256::zero()`. - * @param compute_phase_cfg Pointer to store the compute phase configuration. - * @param action_phase_cfg Pointer to store the action phase configuration. - * @param serialize_cfg Pointer to store the serialize phase configuration. - * @param masterchain_create_fee Pointer to store the masterchain create fee. - * @param basechain_create_fee Pointer to store the basechain create fee. - * @param wc The workchain ID. - * @param now The current Unix time. - */ -td::Status FetchConfigParams::fetch_config_params( - const block::Config& config, td::Ref prev_blocks_info, Ref* old_mparams, - std::vector* storage_prices, StoragePhaseConfig* storage_phase_cfg, - td::BitArray<256>* rand_seed, ComputePhaseConfig* compute_phase_cfg, ActionPhaseConfig* action_phase_cfg, - SerializeConfig* serialize_cfg, td::RefInt256* masterchain_create_fee, td::RefInt256* basechain_create_fee, - ton::WorkchainId wc, ton::UnixTime now) { - *old_mparams = config.get_config_param(9); - { - auto res = config.get_storage_prices(); - if (res.is_error()) { - return res.move_as_error(); - } - *storage_prices = res.move_as_ok(); - } - if (rand_seed->is_zero()) { - // generate rand seed - prng::rand_gen().strong_rand_bytes(rand_seed->data(), 32); - LOG(DEBUG) << "block random seed set to " << rand_seed->to_hex(); - } - TRY_RESULT(size_limits, config.get_size_limits_config()); - { - // compute compute_phase_cfg / storage_phase_cfg - auto cell = config.get_config_param(wc == ton::masterchainId ? 20 : 21); - if (cell.is_null()) { - return td::Status::Error(-668, "cannot fetch current gas prices and limits from masterchain configuration"); - } - if (!compute_phase_cfg->parse_GasLimitsPrices(std::move(cell), storage_phase_cfg->freeze_due_limit, - storage_phase_cfg->delete_due_limit)) { - return td::Status::Error(-668, "cannot unpack current gas prices and limits from masterchain configuration"); - } - TRY_RESULT_PREFIX(mc_gas_prices, config.get_gas_limits_prices(true), - "cannot unpack masterchain gas prices and limits: "); - compute_phase_cfg->mc_gas_prices = std::move(mc_gas_prices); - compute_phase_cfg->special_gas_full = config.get_global_version() >= 5; - storage_phase_cfg->enable_due_payment = config.get_global_version() >= 4; - storage_phase_cfg->global_version = config.get_global_version(); - compute_phase_cfg->block_rand_seed = *rand_seed; - compute_phase_cfg->max_vm_data_depth = size_limits.max_vm_data_depth; - compute_phase_cfg->global_config = config.get_root_cell(); - compute_phase_cfg->global_version = config.get_global_version(); - if (compute_phase_cfg->global_version >= 4) { - compute_phase_cfg->prev_blocks_info = std::move(prev_blocks_info); - } - if (compute_phase_cfg->global_version >= 6) { - compute_phase_cfg->unpacked_config_tuple = config.get_unpacked_config_tuple(now); - } - compute_phase_cfg->suspended_addresses = config.get_suspended_addresses(now); - compute_phase_cfg->size_limits = size_limits; - compute_phase_cfg->precompiled_contracts = config.get_precompiled_contracts_config(); - compute_phase_cfg->allow_external_unfreeze = compute_phase_cfg->global_version >= 8; - compute_phase_cfg->disable_anycast = config.get_global_version() >= 10; - } - { - // compute action_phase_cfg - block::gen::MsgForwardPrices::Record rec; - auto cell = config.get_config_param(24); - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { - return td::Status::Error(-668, "cannot fetch masterchain message transfer prices from masterchain configuration"); - } - action_phase_cfg->fwd_mc = - block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, - (unsigned)rec.first_frac, (unsigned)rec.next_frac}; - cell = config.get_config_param(25); - if (cell.is_null() || !tlb::unpack_cell(std::move(cell), rec)) { - return td::Status::Error(-668, "cannot fetch standard message transfer prices from masterchain configuration"); - } - action_phase_cfg->fwd_std = - block::MsgPrices{rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, - (unsigned)rec.first_frac, (unsigned)rec.next_frac}; - action_phase_cfg->workchains = &config.get_workchain_list(); - action_phase_cfg->bounce_msg_body = (config.has_capability(ton::capBounceMsgBody) ? 256 : 0); - action_phase_cfg->size_limits = size_limits; - action_phase_cfg->action_fine_enabled = config.get_global_version() >= 4; - action_phase_cfg->bounce_on_fail_enabled = config.get_global_version() >= 4; - action_phase_cfg->message_skip_enabled = config.get_global_version() >= 8; - action_phase_cfg->disable_custom_fess = config.get_global_version() >= 8; - action_phase_cfg->reserve_extra_enabled = config.get_global_version() >= 9; - action_phase_cfg->mc_blackhole_addr = config.get_burning_config().blackhole_addr; - action_phase_cfg->extra_currency_v2 = config.get_global_version() >= 10; - action_phase_cfg->disable_anycast = config.get_global_version() >= 10; - action_phase_cfg->disable_ihr_flag = config.get_global_version() >= 11; - action_phase_cfg->global_version = config.get_global_version(); - } - { - serialize_cfg->extra_currency_v2 = config.get_global_version() >= 10; - serialize_cfg->disable_anycast = config.get_global_version() >= 10; - serialize_cfg->store_storage_dict_hash = config.get_global_version() >= 11; - serialize_cfg->size_limits = size_limits; - } - { - // fetch block_grams_created - auto cell = config.get_config_param(14); - if (cell.is_null()) { - *basechain_create_fee = *masterchain_create_fee = td::zero_refint(); - } else { - block::gen::BlockCreateFees::Record create_fees; - if (!(tlb::unpack_cell(cell, create_fees) && - block::tlb::t_Grams.as_integer_to(create_fees.masterchain_block_fee, *masterchain_create_fee) && - block::tlb::t_Grams.as_integer_to(create_fees.basechain_block_fee, *basechain_create_fee))) { - return td::Status::Error(-668, "cannot unpack BlockCreateFees from configuration parameter #14"); - } - } - } - return td::Status::OK(); -} - -} // namespace block \ No newline at end of file From d67566a734ac42ad5724fe533ccab8132f86c813 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Wed, 12 Nov 2025 19:08:23 +0300 Subject: [PATCH 3/4] improve --- standard/wallets/comparison/.prettierrc | 14 +- standard/wallets/comparison/README.md | 2 + standard/wallets/comparison/jest.config.js | 23 +- standard/wallets/comparison/package.json | 54 +- .../tests/WalletFeeComparison.spec.ts | 847 ------------------ .../wallets/comparison/tests/get-results.ts | 524 +++++++++++ .../wallets/comparison/tests/imports/const.ts | 16 +- .../wallets/comparison/tests/print-tables.ts | 390 ++++++++ .../tests/results/wallet-fee-comparison.md | 364 +++++--- .../comparison/tests/utils/fee-extraction.ts | 72 ++ .../comparison/tests/utils/feeExtraction.ts | 73 -- .../comparison/tests/utils/gas-utils.ts | 379 ++++++++ .../comparison/tests/utils/gasUtils.ts | 329 ------- .../tests/wallet-fee-comparison.spec.ts | 183 ++++ standard/wallets/comparison/tsconfig.json | 47 +- standard/wallets/comparison/utils.ts | 10 +- .../comparison/wrappers/HighloadQueryId.ts | 81 -- .../comparison/wrappers/HighloadWalletV3.ts | 216 ----- .../comparison/wrappers/MsgGenerator.ts | 134 --- .../wrappers/PreprocessedWalletV2.ts | 177 ---- .../comparison/wrappers/highload-query-id.ts | 80 ++ .../comparison/wrappers/highload-wallet-v3.ts | 233 +++++ .../comparison/wrappers/msg-generator.ts | 149 +++ .../wrappers/preprocessed-wallet-v2.ts | 172 ++++ 24 files changed, 2482 insertions(+), 2087 deletions(-) delete mode 100644 standard/wallets/comparison/tests/WalletFeeComparison.spec.ts create mode 100644 standard/wallets/comparison/tests/get-results.ts create mode 100644 standard/wallets/comparison/tests/print-tables.ts create mode 100644 standard/wallets/comparison/tests/utils/fee-extraction.ts delete mode 100644 standard/wallets/comparison/tests/utils/feeExtraction.ts create mode 100644 standard/wallets/comparison/tests/utils/gas-utils.ts delete mode 100644 standard/wallets/comparison/tests/utils/gasUtils.ts create mode 100644 standard/wallets/comparison/tests/wallet-fee-comparison.spec.ts delete mode 100644 standard/wallets/comparison/wrappers/HighloadQueryId.ts delete mode 100644 standard/wallets/comparison/wrappers/HighloadWalletV3.ts delete mode 100644 standard/wallets/comparison/wrappers/MsgGenerator.ts delete mode 100644 standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts create mode 100644 standard/wallets/comparison/wrappers/highload-query-id.ts create mode 100644 standard/wallets/comparison/wrappers/highload-wallet-v3.ts create mode 100644 standard/wallets/comparison/wrappers/msg-generator.ts create mode 100644 standard/wallets/comparison/wrappers/preprocessed-wallet-v2.ts diff --git a/standard/wallets/comparison/.prettierrc b/standard/wallets/comparison/.prettierrc index fa7224f..1e7c0d0 100644 --- a/standard/wallets/comparison/.prettierrc +++ b/standard/wallets/comparison/.prettierrc @@ -1,8 +1,8 @@ { - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 100, - "tabWidth": 2, - "arrowParens": "always" -} \ No newline at end of file + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "always" +} diff --git a/standard/wallets/comparison/README.md b/standard/wallets/comparison/README.md index 4554cf3..d49ff61 100644 --- a/standard/wallets/comparison/README.md +++ b/standard/wallets/comparison/README.md @@ -3,12 +3,14 @@ This workspace provides a reproducible test harness for benchmarking transaction fees across several TON wallet implementations. The suite focuses on measuring gas usage, total fees, and per-message costs for different payload sizes and batch configurations. ## Layout + - `tests/WalletFeeComparison.spec.ts` — main Jest suite that orchestrates the fee measurements and outputs markdown reports. - `tests/utils` — helper utilities for fee extraction and TON gas calculations. - `wrappers/` — contract wrappers required to deploy and interact with wallets inside the sandbox. - `build/` — precompiled wallet artifacts referenced by the wrappers. ## Getting Started + 1. Install dependencies: `yarn install` 2. Run the benchmark suite: `yarn test` diff --git a/standard/wallets/comparison/jest.config.js b/standard/wallets/comparison/jest.config.js index 1a021bd..8ba99d7 100644 --- a/standard/wallets/comparison/jest.config.js +++ b/standard/wallets/comparison/jest.config.js @@ -1,14 +1,13 @@ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testPathIgnorePatterns: ['/node_modules/', '/dist/'], - collectCoverage: false, - coverageDirectory: 'coverage', - coveragePathIgnorePatterns: ['/node_modules/', '/dist/'], - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - transform: { - '^.+\\.ts$': 'ts-jest', - }, - testMatch: ['**/tests/**/*.spec.ts'], + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + collectCoverage: false, + coverageDirectory: 'coverage', + coveragePathIgnorePatterns: ['/node_modules/', '/dist/'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, + testMatch: ['**/tests/**/*.spec.ts'], }; - diff --git a/standard/wallets/comparison/package.json b/standard/wallets/comparison/package.json index 4002430..28879e4 100644 --- a/standard/wallets/comparison/package.json +++ b/standard/wallets/comparison/package.json @@ -1,28 +1,28 @@ { - "name": "example", - "version": "0.0.1", - "description": "Automated fee comparison tests for multiple TON wallet implementations", - "scripts": { - "build": "tsc", - "test": "jest", - "deploy": "ts-node scripts/deploy.ts", - "lint": "prettier --check .", - "format": "prettier --write ." - }, - "devDependencies": { - "@ton/blueprint": "^0.40.0", - "@ton/core": "^0.62.0", - "@ton/crypto": "^3.2.0", - "@ton/sandbox": "^0.37.2", - "@ton/test-utils": "^0.12.0", - "@ton/ton": "^15.3.1", - "@types/jest": "^29.5.0", - "@types/node": "^20.2.5", - "jest": "^29.5.0", - "prettier": "^3.1.0", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "typescript": "^5.3.2" - }, - "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} \ No newline at end of file + "name": "example", + "version": "0.0.1", + "description": "Automated fee comparison tests for multiple TON wallet implementations", + "scripts": { + "build": "tsc", + "test": "jest", + "deploy": "ts-node scripts/deploy.ts", + "lint": "prettier --check .", + "format": "prettier --write ." + }, + "devDependencies": { + "@ton/blueprint": "^0.40.0", + "@ton/core": "^0.62.0", + "@ton/crypto": "^3.2.0", + "@ton/sandbox": "^0.37.2", + "@ton/test-utils": "^0.12.0", + "@ton/ton": "^15.3.1", + "@types/jest": "^29.5.0", + "@types/node": "^20.2.5", + "jest": "^29.5.0", + "prettier": "^3.6.2", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "^5.3.2" + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" +} diff --git a/standard/wallets/comparison/tests/WalletFeeComparison.spec.ts b/standard/wallets/comparison/tests/WalletFeeComparison.spec.ts deleted file mode 100644 index 257754f..0000000 --- a/standard/wallets/comparison/tests/WalletFeeComparison.spec.ts +++ /dev/null @@ -1,847 +0,0 @@ -import { writeFileSync, mkdirSync } from 'fs'; -import path from 'path'; -import { Blockchain } from '@ton/sandbox'; -import { - Cell, - SendMode, - internal as internal_relaxed, - toNano, - MessageRelaxed, - OutActionSendMsg, - beginCell, - Address, - fromNano, -} from '@ton/core'; -import { - WalletContractV2R1, - WalletContractV2R2, - WalletContractV3R1, - WalletContractV3R2, - WalletContractV4, - WalletContractV5R1 -} from '@ton/ton'; -import { KeyPair, keyPairFromSeed, getSecureRandomBytes } from '@ton/crypto'; -import { randomAddress } from '@ton/test-utils'; -import { HighloadWalletV3Code, HighloadWalletV3 } from '../wrappers/HighloadWalletV3'; -import { HighloadQueryId } from '../wrappers/HighloadQueryId'; -import { Wallet as PreprocessedWalletV2 } from '../wrappers/PreprocessedWalletV2'; -import { SUBWALLET_ID, DEFAULT_TIMEOUT } from './imports/const'; -import { extractTransactionFees } from './utils/feeExtraction'; - -type MessageBodyResolver = (messageIndex: number) => Cell; - -type MessageBodyConfig = { - name: string; - resolveBody: MessageBodyResolver; -}; - -type TestRunConfig = { - messageCount: number; - bodyResolver: MessageBodyResolver; - bodyName: string; -}; - -type WalletKey = - | 'v2r1' - | 'v2r2' - | 'v3r1' - | 'v3r2' - | 'v4r2' - | 'v5r1' - | 'preprocessedV2' - | 'highloadV3'; - -type EnabledWallets = Record; -type WalletNames = Record; - -type Config = { - constants: { - messageValue: bigint; - deployValue: bigint; - }; - requestTimings: { - realSeconds: number; - theoreticalSeconds: number; - }; - messageCounts: number[]; - messageBodyVariants: MessageBodyConfig[]; - enabledWallets: EnabledWallets; - walletNames: WalletNames; - displayFields: { - requests: boolean; - totalGas: boolean; - gasPerMsg: boolean; - totalFee: boolean; - feePerMsg: boolean; - percentToBestGas: boolean; - percentToBestFee: boolean; - time: boolean; - theoreticalTime: boolean; - }; - testRuns: TestRunConfig[]; -}; - -const CONFIG: Config = (() => { - const messageCounts = [1, 4, 200, 1000]; - const messageBodyVariants: MessageBodyConfig[] = [ - { name: 'Empty', resolveBody: () => Cell.EMPTY }, - { name: 'Comment', resolveBody: commentBodyResolver }, - { name: 'Jetton', resolveBody: jettonBodyResolver }, - ]; - - // Wallet selection (true = enabled, false = disabled) - const enabledWallets = { - v2r1: true, - v2r2: true, - v3r1: true, - v3r2: true, - v4r2: true, - v5r1: true, - preprocessedV2: false, - highloadV3: true, - } satisfies EnabledWallets; - - // Wallet names for reporting - const walletNames = { - v2r1: 'Wallet V2R1', - v2r2: 'Wallet V2R2', - v3r1: 'Wallet V3R1', - v3r2: 'Wallet V3R2', - v4r2: 'Wallet V4R2', - v5r1: 'Wallet V5R1', - preprocessedV2: 'Preprocessed Wallet V2', - highloadV3: 'Highload Wallet V3', - } satisfies WalletNames; - - // Columns to include - const displayFields = { - requests: true, - totalGas: true, - gasPerMsg: true, - totalFee: true, - feePerMsg: true, - percentToBestGas: true, - percentToBestFee: true, - time: true, - theoreticalTime: true, - }; - - return { - constants: { - messageValue: toNano('0.01'), - deployValue: toNano('1000'), - }, - requestTimings: { - realSeconds: 13, - theoreticalSeconds: 4, - }, - messageCounts, - messageBodyVariants, - enabledWallets, - walletNames, - displayFields, - testRuns: buildTestRuns(messageBodyVariants, messageCounts), - }; -})(); - -const toCoins = (value: bigint): number => { - return Number(fromNano(value)); -}; - -const formatSeconds = (seconds: number): string => { - const totalSeconds = Math.round(seconds); - if (totalSeconds < 60) { - return `${totalSeconds}s`; - } - const minutes = Math.floor(totalSeconds / 60); - const secs = totalSeconds % 60; - return `${minutes}m ${secs}s`; -}; - -const extractGasUsed = (tx: any): bigint => { - if (tx.description.type !== 'generic') return 0n; - if (tx.description.computePhase.type !== 'vm') return 0n; - return tx.description.computePhase.gasUsed as bigint; -}; - -const createMessages = ( - startIndex: number, - count: number, - resolveBody: MessageBodyResolver, -): MessageRelaxed[] => - Array.from({ length: count }, (_, offset) => - internal_relaxed({ - to: randomAddress(), - value: CONFIG.constants.messageValue, - bounce: false, - body: resolveBody(startIndex + offset), - }), - ); - -function buildTestRuns(bodyVariants: MessageBodyConfig[], counts: number[]): TestRunConfig[] { - return bodyVariants.flatMap((variant) => - counts.map((messageCount) => ({ - messageCount, - bodyResolver: variant.resolveBody, - bodyName: variant.name, - })), - ); -} - -function commentBodyResolver(messageIndex: number): Cell { - return beginCell().storeUint(0, 32).storeStringTail(randomString(12, messageIndex)).endCell(); -} - -function jettonBodyResolver(messageIndex: number): Cell { - return beginCell() - .storeUint(0xf8a7ea5, 32) - .storeUint(messageIndex, 64) - .storeCoins(1) - .storeAddress(randomAddress()) - .storeAddress(randomAddress()) - .storeMaybeRef(null) - .storeCoins(0) - .storeMaybeRef(commentBodyResolver(messageIndex)) - .endCell(); -} - -function randomString(size: number, seed: number): string { - return generateSeededString(seed, size); -} - -function generateSeededString( - seed: number, - size: number, - characterSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', -): string { - const randomFunc = mulberry32(seed); - - let result = ''; - for (let i = 0; i < size; i++) { - const randomIndex = Math.floor(randomFunc() * characterSet.length); - result += characterSet.charAt(randomIndex); - } - return result; -} - -function mulberry32(seed: number): () => number { - return function () { - let t = (seed += 0x6d2b79f5); - t = Math.imul(t ^ (t >>> 15), t | 1); - t ^= t + Math.imul(t ^ (t >>> 7), t | 61); - return ((t ^ (t >>> 14)) >>> 0) / 4294967296; - }; -} - -type WalletTestResult = { - walletName: string; - requests: number; - totalGas: bigint; - totalFee: bigint; - messageCount: number; - bodyName: string; -}; - -type BatchExecutionContext = { - blockchain: Blockchain; - wallet: any; - seqno: bigint; - batchCount: number; - bodyResolver: MessageBodyResolver; - startIndex: number; -}; - -type BatchExecutionResult = { - gas: bigint; - fee: bigint; - nextSeqno?: bigint; -}; - -type WalletMeasurementOptions = { - walletName: string; - messageCount: number; - bodyResolver: MessageBodyResolver; - bodyName: string; - batchSize: number; - createWallet: (blockchain: Blockchain) => Promise | any; - deploy: (blockchain: Blockchain, wallet: any) => Promise; - executeBatch: (context: BatchExecutionContext) => Promise; -}; - -async function measureWalletBatches(options: WalletMeasurementOptions): Promise { - const { walletName, messageCount, bodyResolver, bodyName, batchSize, createWallet, deploy, executeBatch } = options; - - const blockchain = await Blockchain.create(); - const wallet = await createWallet(blockchain); - await deploy(blockchain, wallet); - - const balanceBefore = (await blockchain.getContract(wallet.address)).balance; - - let totalGas = 0n; - let totalFee = 0n; - let totalGasVirtual = 0n; - let totalFeeVirtual = 0n; - let requests = 0; - let sentMessagesCount = 0; - - let seqno: bigint = BigInt(await wallet.getSeqno()); - let nextMessageIndex = 0; - let lastBatch: { batchCount: number; gas: bigint; fee: bigint } | null = null; - - for (let i = 0; i < messageCount; i += batchSize) { - const batchCount = Math.min(batchSize, messageCount - i); - - if (lastBatch && batchCount === lastBatch.batchCount) { - totalGasVirtual += lastBatch.gas; - totalFeeVirtual += lastBatch.fee; - requests++; - continue; - } - - const { gas, fee, nextSeqno } = await executeBatch({ - blockchain, - wallet, - seqno, - batchCount, - bodyResolver, - startIndex: nextMessageIndex, - }); - - totalGas += gas; - totalFee += fee; - sentMessagesCount += batchCount; - requests++; - - seqno = nextSeqno ?? seqno + 1n; - nextMessageIndex += batchCount; - lastBatch = { batchCount, gas, fee }; - } - - const balanceAfter = (await blockchain.getContract(wallet.address)).balance; - const balanceDiff = balanceBefore - balanceAfter; - const totalMessageValue = CONFIG.constants.messageValue * BigInt(sentMessagesCount); - - expect(balanceDiff).toBe(totalMessageValue + totalFee); - - return { - walletName, - requests, - totalGas: totalGas + totalGasVirtual, - totalFee: totalFee + totalFeeVirtual, - messageCount, - bodyName, - }; -} - -describe('Wallet Fee Comparison', () => { - let keyPair: KeyPair; - const allResults: WalletTestResult[][] = []; // Results collected for each run - - beforeAll(async () => { - keyPair = keyPairFromSeed(await getSecureRandomBytes(32)); - }); - - const deployWallet = async (blockchain: Blockchain, wallet: any) => { - const deployer = await blockchain.treasury('deployer'); - await deployer.send({ - value: CONFIG.constants.deployValue, - to: wallet.address, - init: wallet.init, - }); - }; - - async function measureStandardWallet( - walletName: string, - createWallet: (blockchain: Blockchain) => any, - batchSize: number, - messageCount: number, - bodyResolver: MessageBodyResolver, - bodyName: string - ) { - return measureWalletBatches({ - walletName, - messageCount, - bodyResolver, - bodyName, - batchSize, - createWallet, - deploy: deployWallet, - executeBatch: async ({ wallet, seqno, batchCount, bodyResolver, blockchain, startIndex }) => { - const messages = createMessages(startIndex, batchCount, bodyResolver); - - const transfer = await wallet.createTransfer({ - seqno: Number(seqno), - secretKey: keyPair.secretKey, - messages, - sendMode: SendMode.NONE, - }); - const result = await wallet.send(transfer); - - const externalTx = result.transactions.find((tx: any) => tx.inMessage?.info.type === 'external-in'); - if (!externalTx) throw new Error('No external-in transaction'); - - const gas = extractGasUsed(externalTx); - const txFees = extractTransactionFees(externalTx, blockchain); - const fee = txFees.import_fee + txFees.storage_fee + txFees.gas_fees; - - return { gas, fee, nextSeqno: seqno + 1n }; - }, - }); - } - - async function measurePreprocessedWalletV2( - walletName: string, - messageCount: number, - bodyResolver: MessageBodyResolver, - bodyName: string - ) { - return measureWalletBatches({ - walletName, - messageCount, - bodyResolver, - bodyName, - batchSize: 255, - createWallet: (blockchain) => - blockchain.openContract(PreprocessedWalletV2.createFromPublicKey(keyPair.publicKey)), - deploy: async (blockchain, wallet) => { - const deployer = await blockchain.treasury('deployer'); - await wallet.sendDeploy(deployer.getSender(), CONFIG.constants.deployValue); - }, - executeBatch: async ({ wallet, seqno, batchCount, bodyResolver, blockchain, startIndex }) => { - const transfers = Array.from({ length: batchCount }, (_, offset) => ({ - to: randomAddress(), - value: CONFIG.constants.messageValue, - bounce: false, - body: bodyResolver(startIndex + offset), - mode: SendMode.NONE, - })); - - const result = await wallet.sendTransfers(keyPair, transfers, Number(seqno)); - - const externalTx = result.transactions.find((tx: any) => tx.inMessage?.info.type === 'external-in'); - if (!externalTx) throw new Error('No external-in transaction'); - - const gas = extractGasUsed(externalTx); - const txFees = extractTransactionFees(externalTx, blockchain); - const fee = txFees.import_fee + txFees.storage_fee + txFees.gas_fees; - - return { gas, fee, nextSeqno: seqno + 1n }; - }, - }); - } - - async function measureHighloadV3( - walletName: string, - messageCount: number, - bodyResolver: MessageBodyResolver, - bodyName: string - ) { - const blockchain = await Blockchain.create(); - blockchain.now = 1000; - let queryId = new HighloadQueryId(); - - const wallet = blockchain.openContract( - HighloadWalletV3.createFromConfig( - { publicKey: keyPair.publicKey, subwalletId: SUBWALLET_ID, timeout: DEFAULT_TIMEOUT }, - HighloadWalletV3Code - ) - ); - - const deployer = await blockchain.treasury('deployer'); - await wallet.sendDeploy(deployer.getSender(), CONFIG.constants.deployValue); - - const balanceBefore = (await blockchain.getContract(wallet.address)).balance; - const totalMessageValue = CONFIG.constants.messageValue * BigInt(messageCount); - - let totalGas = 0n; - let totalFee = 0n; - let requests = 0; - const batchSize = 254; - - for (let i = 0; i < messageCount; i += batchSize) { - const batchCount = Math.min(batchSize, messageCount - i); - const actions: OutActionSendMsg[] = Array.from({ length: batchCount }, (_, offset) => ({ - type: 'sendMsg', - mode: SendMode.NONE, - outMsg: internal_relaxed({ - to: randomAddress(), - value: CONFIG.constants.messageValue, - bounce: false, - body: bodyResolver(i + offset), - }), - })); - - const result = await wallet.sendBatch( - keyPair.secretKey, - actions, - SUBWALLET_ID, - queryId, - DEFAULT_TIMEOUT, - blockchain.now - ); - queryId = queryId.getNext(); - - const externalTx = result.transactions.find((tx: any) => tx.inMessage?.info.type === 'external-in'); - if (!externalTx) throw new Error('No external-in transaction'); - - const externalFees = extractTransactionFees(externalTx, blockchain); - const externalFee = - externalFees.import_fee + externalFees.storage_fee + externalFees.gas_fees + externalFees.out_fwd_fees; - - const internalTx = result.transactions.find( - (tx: any) => - tx.inMessage?.info.type === 'internal' && - tx.inMessage?.info.src?.equals?.(wallet.address) && - tx.inMessage?.info.dest?.equals?.(wallet.address) - ); - if (!internalTx) throw new Error('No internal self-call transaction'); - - const internalFees = extractTransactionFees(internalTx, blockchain); - const internalFee = internalFees.storage_fee + internalFees.gas_fees; - - totalGas += extractGasUsed(externalTx) + extractGasUsed(internalTx); - totalFee += externalFee + internalFee; - requests++; - } - - const balanceAfter = (await blockchain.getContract(wallet.address)).balance; - const balanceDiff = balanceBefore - balanceAfter; - - // Verify balance calculation - expect(balanceDiff).toBe(totalMessageValue + totalFee); - - return { - walletName, - requests, - totalGas, - totalFee, - messageCount, - bodyName, - }; - } - - CONFIG.testRuns.forEach((testRun, runIndex) => { - describe(`Run ${runIndex + 1}: ${testRun.messageCount} messages, body: ${testRun.bodyName}`, () => { - const results: WalletTestResult[] = []; - - // Wallet V2R1 - if (CONFIG.enabledWallets.v2r1) { - it(`Measure ${CONFIG.walletNames.v2r1}`, async () => { - const result = await measureStandardWallet( - CONFIG.walletNames.v2r1, - (blockchain) => - blockchain.openContract( - WalletContractV2R1.create({ workchain: 0, publicKey: keyPair.publicKey }) - ), - 4, // V2R1 supports up to 4 messages per transaction - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Wallet V2R2 - if (CONFIG.enabledWallets.v2r2) { - it(`Measure ${CONFIG.walletNames.v2r2}`, async () => { - const result = await measureStandardWallet( - CONFIG.walletNames.v2r2, - (blockchain) => - blockchain.openContract( - WalletContractV2R2.create({ workchain: 0, publicKey: keyPair.publicKey }) - ), - 4, // V2R2 supports up to 4 messages per transaction - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Wallet V3R1 - if (CONFIG.enabledWallets.v3r1) { - it(`Measure ${CONFIG.walletNames.v3r1}`, async () => { - const result = await measureStandardWallet( - CONFIG.walletNames.v3r1, - (blockchain) => - blockchain.openContract( - WalletContractV3R1.create({ workchain: 0, publicKey: keyPair.publicKey }) - ), - 4, // V3R1 supports up to 4 messages per transaction - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Wallet V3R2 - if (CONFIG.enabledWallets.v3r2) { - it(`Measure ${CONFIG.walletNames.v3r2}`, async () => { - const result = await measureStandardWallet( - CONFIG.walletNames.v3r2, - (blockchain) => - blockchain.openContract( - WalletContractV3R2.create({ workchain: 0, publicKey: keyPair.publicKey }) - ), - 4, // V3R2 supports up to 4 messages per transaction - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Wallet V4R2 - if (CONFIG.enabledWallets.v4r2) { - it(`Measure ${CONFIG.walletNames.v4r2}`, async () => { - const result = await measureStandardWallet( - CONFIG.walletNames.v4r2, - (blockchain) => - blockchain.openContract( - WalletContractV4.create({ workchain: 0, publicKey: keyPair.publicKey }) - ), - 4, // V4 supports up to 4 messages per transaction - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Wallet V5R1 - if (CONFIG.enabledWallets.v5r1) { - it(`Measure ${CONFIG.walletNames.v5r1}`, async () => { - const result = await measureStandardWallet( - CONFIG.walletNames.v5r1, - (blockchain) => - blockchain.openContract( - WalletContractV5R1.create({ workchain: 0, publicKey: keyPair.publicKey }) - ), - 255, // V5 supports up to 255 messages per transaction - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Preprocessed Wallet V2 - if (CONFIG.enabledWallets.preprocessedV2) { - it(`Measure ${CONFIG.walletNames.preprocessedV2}`, async () => { - const result = await measurePreprocessedWalletV2( - CONFIG.walletNames.preprocessedV2, - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - // Highload Wallet V3 - if (CONFIG.enabledWallets.highloadV3) { - it(`Measure ${CONFIG.walletNames.highloadV3}`, async () => { - const result = await measureHighloadV3( - CONFIG.walletNames.highloadV3, - testRun.messageCount, - testRun.bodyResolver, - testRun.bodyName - ); - results.push(result); - }); - } - - afterAll(() => { - allResults.push(results); - }); - }); - }); - - afterAll(() => { - if (allResults.length === 0) return; - - const markdownLines: string[] = ['# Wallet Fee Comparison Results', '']; - const numberFormatter = new Intl.NumberFormat('en-US'); - const tonFormatter = new Intl.NumberFormat('en-US', { - minimumFractionDigits: 2, - maximumFractionDigits: 9, - }); - allResults.forEach((results, runIndex) => { - if (results.length === 0) return; - - const testRun = CONFIG.testRuns[runIndex]; - markdownLines.push( - `## Run ${runIndex + 1}: ${testRun.messageCount} messages, Body: ${testRun.bodyName}`, - '' - ); - - const gasPerMsgValues = results.map((r) => r.totalGas / BigInt(r.messageCount)); - const feePerMsgValues = results.map((r) => r.totalFee / BigInt(r.messageCount)); - const minGasPerMsg = gasPerMsgValues.reduce((min, val) => (val < min ? val : min), gasPerMsgValues[0]); - const minFeePerMsg = feePerMsgValues.reduce((min, val) => (val < min ? val : min), feePerMsgValues[0]); - - const formatPercentDiffPlain = (value: bigint, baseline: bigint): string => { - if (baseline === 0n) { - return 'N/A'; - } - const diff = Number(((value - baseline) * 10000n) / baseline) / 100; - if (!Number.isFinite(diff)) { - return 'N/A'; - } - if (diff === 0) { - return '0.00%'; - } - const prefix = diff > 0 ? '+' : ''; - return `${prefix}${diff.toFixed(2)}%`; - }; - - const formatPercentDiffMarkdown = (value: bigint, baseline: bigint): string => { - if (baseline === 0n) { - return 'N/A'; - } - if (value === baseline) { - return '**Best**'; - } - return formatPercentDiffPlain(value, baseline); - }; - - const isHighloadResult = (result: WalletTestResult) => result.walletName === CONFIG.walletNames.highloadV3; - - // Simplified assumption: Highload Wallet V3 can handle multiple batches per block, but the exact limit depends on network settings and payload size. - const formatRealTime = (result: WalletTestResult): string => { - const seconds = isHighloadResult(result) - ? CONFIG.requestTimings.realSeconds - : result.requests * CONFIG.requestTimings.realSeconds; - return formatSeconds(seconds); - }; - - const formatTheoreticalTime = (result: WalletTestResult): string => { - const seconds = isHighloadResult(result) - ? CONFIG.requestTimings.theoreticalSeconds - : result.requests * CONFIG.requestTimings.theoreticalSeconds; - return formatSeconds(seconds); - }; - - const columns: { - header: string; - markdownAccessor: (result: WalletTestResult, index: number) => string; - consoleAccessor: (result: WalletTestResult, index: number) => string | number; - }[] = [ - { - header: 'Wallet Version', - markdownAccessor: (result, idx) => { - const gasPerMsg = gasPerMsgValues[idx]; - const feePerMsg = feePerMsgValues[idx]; - const isGasBest = gasPerMsg === minGasPerMsg; - const isFeeBest = feePerMsg === minFeePerMsg; - const isBest = isGasBest || isFeeBest; - return isBest ? `**${result.walletName}** ${isFeeBest ? '✅' : ''}` : result.walletName; - }, - consoleAccessor: (result) => result.walletName, - }, - ]; - - if (CONFIG.displayFields.requests) { - columns.push({ - header: 'Requests', - markdownAccessor: (result) => numberFormatter.format(result.requests), - consoleAccessor: (result) => result.requests, - }); - } - if (CONFIG.displayFields.totalGas) { - columns.push({ - header: 'Total Gas', - markdownAccessor: (result) => numberFormatter.format(Number(result.totalGas)), - consoleAccessor: (result) => Number(result.totalGas), - }); - } - if (CONFIG.displayFields.gasPerMsg) { - columns.push({ - header: 'Gas per Msg', - markdownAccessor: (_result, idx) => numberFormatter.format(Number(gasPerMsgValues[idx])), - consoleAccessor: (_result, idx) => Number(gasPerMsgValues[idx]), - }); - } - if (CONFIG.displayFields.totalFee) { - columns.push({ - header: 'Total Fee (TON)', - markdownAccessor: (result) => tonFormatter.format(toCoins(result.totalFee)), - consoleAccessor: (result) => toCoins(result.totalFee), - }); - } - if (CONFIG.displayFields.feePerMsg) { - columns.push({ - header: 'Fee per Msg (TON)', - markdownAccessor: (_result, idx) => tonFormatter.format(toCoins(feePerMsgValues[idx])), - consoleAccessor: (_result, idx) => toCoins(feePerMsgValues[idx]), - }); - } - if (CONFIG.displayFields.percentToBestGas) { - columns.push({ - header: 'Gas delta (%)', - markdownAccessor: (_result, idx) => formatPercentDiffMarkdown(gasPerMsgValues[idx], minGasPerMsg), - consoleAccessor: (_result, idx) => formatPercentDiffPlain(gasPerMsgValues[idx], minGasPerMsg), - }); - } - if (CONFIG.displayFields.percentToBestFee) { - columns.push({ - header: 'Fee delta (%)', - markdownAccessor: (_result, idx) => formatPercentDiffMarkdown(feePerMsgValues[idx], minFeePerMsg), - consoleAccessor: (_result, idx) => formatPercentDiffPlain(feePerMsgValues[idx], minFeePerMsg), - }); - } - if (CONFIG.displayFields.time) { - columns.push({ - header: 'Real Time (sec)', - markdownAccessor: (result) => formatRealTime(result), - consoleAccessor: (result) => formatRealTime(result), - }); - } - if (CONFIG.displayFields.theoreticalTime) { - columns.push({ - header: 'Theoretical Time (sec)', - markdownAccessor: (result) => formatTheoreticalTime(result), - consoleAccessor: (result) => formatTheoreticalTime(result), - }); - } - - const headerRow = `| ${columns.map((column) => column.header).join(' | ')} |`; - const separatorRow = `| ${columns.map(() => '---').join(' | ')} |`; - - markdownLines.push(headerRow, separatorRow); - - results.forEach((result, idx) => { - const rowCells = columns.map((column) => column.markdownAccessor(result, idx)); - markdownLines.push(`| ${rowCells.join(' | ')} |`); - }); - - const consoleRows = results.map((result, idx) => { - const row: Record = {}; - columns.forEach((column) => { - row[column.header] = column.consoleAccessor(result, idx); - }); - return row; - }); - - console.log(`Run ${runIndex + 1}: ${testRun.messageCount} messages, Body: ${testRun.bodyName}`); - console.table(consoleRows); - - markdownLines.push(''); - }); - - const outputDir = path.resolve(__dirname, 'results'); - const outputFile = path.join(outputDir, 'wallet-fee-comparison.md'); - mkdirSync(outputDir, { recursive: true }); - writeFileSync(outputFile, markdownLines.join('\n'), { encoding: 'utf-8' }); - console.log(`Markdown report saved to ${outputFile}`); - }); -}); - - diff --git a/standard/wallets/comparison/tests/get-results.ts b/standard/wallets/comparison/tests/get-results.ts new file mode 100644 index 0000000..1fadbd9 --- /dev/null +++ b/standard/wallets/comparison/tests/get-results.ts @@ -0,0 +1,524 @@ +import { Blockchain } from '@ton/sandbox'; +import { + Cell, + MessageRelaxed, + internal as internal_relaxed, + fromNano, + SendMode, + toNano, + OutActionSendMsg, +} from '@ton/core'; +import { + WalletContractV2R1, + WalletContractV2R2, + WalletContractV3R1, + WalletContractV3R2, + WalletContractV4, + WalletContractV5R1, +} from '@ton/ton'; +import { KeyPair, keyPairFromSeed, getSecureRandomBytes } from '@ton/crypto'; +import { randomAddress } from '@ton/test-utils'; +import { HighloadWalletV3Code, HighloadWalletV3 } from '../wrappers/highload-wallet-v3'; +import { HighloadQueryId } from '../wrappers/highload-query-id'; +import { Wallet as PreprocessedWalletV2 } from '../wrappers/preprocessed-wallet-v2'; +import { SUBWALLET_ID, DEFAULT_TIMEOUT } from './imports/const'; +import { extractTransactionFees } from './utils/fee-extraction'; +import { setStoragePrices } from './utils/gas-utils'; + +export type MessageBodyResolver = (messageIndex: number) => Cell; + +export type WalletTestResult = { + walletName: string; + requests: number; + totalGas: bigint; + totalFee: bigint; + messageCount: number; + bodyName: string; +}; + +export type TestConstants = { + messageValue: bigint; + deployValue: bigint; +}; + +export type MeasureWalletFunction = ( + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string, + constants: TestConstants, +) => Promise; + +export type WalletConfig = { + key: string; + name: string; + measureFunction: MeasureWalletFunction; +}; + +export const toCoins = (value: bigint): number => { + return Number(fromNano(value)); +}; + +const extractGasUsed = (tx: any): bigint => { + if (tx.description.type !== 'generic') return 0n; + if (tx.description.computePhase.type !== 'vm') return 0n; + return tx.description.computePhase.gasUsed as bigint; +}; + +const createMessages = ( + startIndex: number, + count: number, + resolveBody: MessageBodyResolver, + messageValue: bigint, +): MessageRelaxed[] => + Array.from({ length: count }, (_, offset) => + internal_relaxed({ + to: randomAddress(), + value: messageValue, + bounce: false, + body: resolveBody(startIndex + offset), + }), + ); + +const setup = async () => { + const blockchain = await Blockchain.create(); + + const config = blockchain.config; + blockchain.setConfig( + setStoragePrices(config, { + utime_sice: 0, + bit_price_ps: 0n, + cell_price_ps: 0n, + mc_bit_price_ps: 0n, + mc_cell_price_ps: 0n, + }), + ); + + const keyPair = keyPairFromSeed(await getSecureRandomBytes(32)); + return { blockchain, keyPair }; +}; + +async function measureStandardWallet( + walletName: string, + createWallet: (blockchain: Blockchain, keyPair: KeyPair) => any, + batchSize: number, + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string, + constants: TestConstants, +): Promise { + const { blockchain, keyPair } = await setup(); + const wallet = createWallet(blockchain, keyPair); + + // Deploy wallet + const deployer = await blockchain.treasury('deployer'); + await deployer.send({ + value: constants.deployValue, + to: wallet.address, + init: wallet.init, + }); + + const balanceBefore = (await blockchain.getContract(wallet.address)).balance; + + let totalGas = 0n; + let totalFee = 0n; + let totalGasVirtual = 0n; + let totalFeeVirtual = 0n; + let requests = 0; + let sentMessagesCount = 0; + + let seqno: bigint = BigInt(await wallet.getSeqno()); + let nextMessageIndex = 0; + let lastBatch: { batchCount: number; gas: bigint; fee: bigint } | null = null; + + for (let i = 0; i < messageCount; i += batchSize) { + const batchCount = Math.min(batchSize, messageCount - i); + + if (lastBatch && batchCount === lastBatch.batchCount) { + totalGasVirtual += lastBatch.gas; + totalFeeVirtual += lastBatch.fee; + requests++; + continue; + } + + const messages = createMessages( + nextMessageIndex, + batchCount, + bodyResolver, + constants.messageValue, + ); + + const transfer = await wallet.createTransfer({ + seqno: Number(seqno), + secretKey: keyPair.secretKey, + messages, + sendMode: SendMode.NONE, + }); + const result = await wallet.send(transfer); + + const externalTx = result.transactions.find( + (tx: any) => tx.inMessage?.info.type === 'external-in', + ); + if (!externalTx) throw new Error('No external-in transaction'); + + const gas = extractGasUsed(externalTx); + const txFees = extractTransactionFees(externalTx, blockchain); + const fee = txFees.import_fee + txFees.storage_fee + txFees.gas_fees; + + totalGas += gas; + totalFee += fee; + sentMessagesCount += batchCount; + requests++; + + seqno = seqno + 1n; + nextMessageIndex += batchCount; + lastBatch = { batchCount, gas, fee }; + } + + const balanceAfter = (await blockchain.getContract(wallet.address)).balance; + const balanceDiff = balanceBefore - balanceAfter; + const totalMessageValue = constants.messageValue * BigInt(sentMessagesCount); + + if (balanceDiff !== totalMessageValue + totalFee) { + throw new Error( + `Balance mismatch: expected ${totalMessageValue + totalFee}, got ${balanceDiff}`, + ); + } + + return { + walletName, + requests, + totalGas: totalGas + totalGasVirtual, + totalFee: totalFee + totalFeeVirtual, + messageCount, + bodyName, + }; +} + +async function measurePreprocessedWalletV2( + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string, + constants: TestConstants, +): Promise { + const walletName = 'Preprocessed Wallet V2'; + const batchSize = 255; + const { blockchain, keyPair } = await setup(); + + const wallet = blockchain.openContract( + PreprocessedWalletV2.createFromPublicKey(keyPair.publicKey), + ); + + // Deploy + const deployer = await blockchain.treasury('deployer'); + await wallet.sendDeploy(deployer.getSender(), constants.deployValue); + + const balanceBefore = (await blockchain.getContract(wallet.address)).balance; + + let totalGas = 0n; + let totalFee = 0n; + let totalGasVirtual = 0n; + let totalFeeVirtual = 0n; + let requests = 0; + let sentMessagesCount = 0; + + let seqno: bigint = BigInt(await wallet.getSeqno()); + let nextMessageIndex = 0; + let lastBatch: { batchCount: number; gas: bigint; fee: bigint } | null = null; + + for (let i = 0; i < messageCount; i += batchSize) { + const batchCount = Math.min(batchSize, messageCount - i); + + if (lastBatch && batchCount === lastBatch.batchCount) { + totalGasVirtual += lastBatch.gas; + totalFeeVirtual += lastBatch.fee; + requests++; + continue; + } + + const transfers = Array.from({ length: batchCount }, (_, offset) => ({ + to: randomAddress(), + value: constants.messageValue, + bounce: false, + body: bodyResolver(nextMessageIndex + offset), + mode: SendMode.NONE, + })); + + const result = await wallet.sendTransfers(keyPair, transfers, Number(seqno)); + + const externalTx = result.transactions.find( + (tx: any) => tx.inMessage?.info.type === 'external-in', + ); + if (!externalTx) throw new Error('No external-in transaction'); + + const gas = extractGasUsed(externalTx); + const txFees = extractTransactionFees(externalTx, blockchain); + const fee = txFees.import_fee + txFees.storage_fee + txFees.gas_fees; + + totalGas += gas; + totalFee += fee; + sentMessagesCount += batchCount; + requests++; + + seqno = seqno + 1n; + nextMessageIndex += batchCount; + lastBatch = { batchCount, gas, fee }; + } + + const balanceAfter = (await blockchain.getContract(wallet.address)).balance; + const balanceDiff = balanceBefore - balanceAfter; + const totalMessageValue = constants.messageValue * BigInt(sentMessagesCount); + + if (balanceDiff !== totalMessageValue + totalFee) { + throw new Error( + `Balance mismatch: expected ${totalMessageValue + totalFee}, got ${balanceDiff}`, + ); + } + + return { + walletName, + requests, + totalGas: totalGas + totalGasVirtual, + totalFee: totalFee + totalFeeVirtual, + messageCount, + bodyName, + }; +} + +async function measureHighloadV3( + messageCount: number, + bodyResolver: MessageBodyResolver, + bodyName: string, + constants: TestConstants, +): Promise { + const walletName = 'Highload Wallet V3'; + const { blockchain, keyPair } = await setup(); + blockchain.now = 1000; + let queryId = new HighloadQueryId(); + + const wallet = blockchain.openContract( + HighloadWalletV3.createFromConfig( + { publicKey: keyPair.publicKey, subwalletId: SUBWALLET_ID, timeout: DEFAULT_TIMEOUT }, + HighloadWalletV3Code, + ), + ); + + const deployer = await blockchain.treasury('deployer'); + await wallet.sendDeploy(deployer.getSender(), constants.deployValue); + + const balanceBefore = (await blockchain.getContract(wallet.address)).balance; + const totalMessageValue = constants.messageValue * BigInt(messageCount); + + let totalGas = 0n; + let totalFee = 0n; + let requests = 0; + const batchSize = 254; + + for (let i = 0; i < messageCount; i += batchSize) { + const batchCount = Math.min(batchSize, messageCount - i); + const actions: OutActionSendMsg[] = Array.from({ length: batchCount }, (_, offset) => ({ + type: 'sendMsg', + mode: SendMode.NONE, + outMsg: internal_relaxed({ + to: randomAddress(), + value: constants.messageValue, + bounce: false, + body: bodyResolver(i + offset), + }), + })); + + const result = await wallet.sendBatch( + keyPair.secretKey, + actions, + SUBWALLET_ID, + queryId, + DEFAULT_TIMEOUT, + blockchain.now, + ); + queryId = queryId.getNext(); + + const externalTx = result.transactions.find( + (tx: any) => tx.inMessage?.info.type === 'external-in', + ); + if (!externalTx) throw new Error('No external-in transaction'); + + const externalFees = extractTransactionFees(externalTx, blockchain); + const externalFee = + externalFees.import_fee + + externalFees.storage_fee + + externalFees.gas_fees + + externalFees.out_fwd_fees; + + const internalTx = result.transactions.find( + (tx: any) => + tx.inMessage?.info.type === 'internal' && + tx.inMessage?.info.src?.equals?.(wallet.address) && + tx.inMessage?.info.dest?.equals?.(wallet.address), + ); + if (!internalTx) throw new Error('No internal self-call transaction'); + + const internalFees = extractTransactionFees(internalTx, blockchain); + const internalFee = internalFees.storage_fee + internalFees.gas_fees; + + totalGas += extractGasUsed(externalTx) + extractGasUsed(internalTx); + totalFee += externalFee + internalFee; + requests++; + } + + const balanceAfter = (await blockchain.getContract(wallet.address)).balance; + const balanceDiff = balanceBefore - balanceAfter; + + if (balanceDiff !== totalMessageValue + totalFee) { + throw new Error( + `Balance mismatch: expected ${totalMessageValue + totalFee}, got ${balanceDiff}`, + ); + } + + return { + walletName, + requests, + totalGas, + totalFee, + messageCount, + bodyName, + }; +} + +function createStandardWalletMeasureFunction( + walletName: string, + createWallet: (blockchain: Blockchain, keyPair: KeyPair) => any, + batchSize: number, +): MeasureWalletFunction { + return async (messageCount, bodyResolver, bodyName, constants) => + measureStandardWallet( + walletName, + createWallet, + batchSize, + messageCount, + bodyResolver, + bodyName, + constants, + ); +} + +export const WALLET_CONFIGS: WalletConfig[] = [ + { + key: 'v2r1', + name: 'Wallet V2R1', + measureFunction: createStandardWalletMeasureFunction( + 'Wallet V2R1', + (blockchain, kp) => + blockchain.openContract( + WalletContractV2R1.create({ workchain: 0, publicKey: kp.publicKey }), + ), + 4, + ), + }, + { + key: 'v2r2', + name: 'Wallet V2R2', + measureFunction: createStandardWalletMeasureFunction( + 'Wallet V2R2', + (blockchain, kp) => + blockchain.openContract( + WalletContractV2R2.create({ workchain: 0, publicKey: kp.publicKey }), + ), + 4, + ), + }, + { + key: 'v3r1', + name: 'Wallet V3R1', + measureFunction: createStandardWalletMeasureFunction( + 'Wallet V3R1', + (blockchain, kp) => + blockchain.openContract( + WalletContractV3R1.create({ workchain: 0, publicKey: kp.publicKey }), + ), + 4, + ), + }, + { + key: 'v3r2', + name: 'Wallet V3R2', + measureFunction: createStandardWalletMeasureFunction( + 'Wallet V3R2', + (blockchain, kp) => + blockchain.openContract( + WalletContractV3R2.create({ workchain: 0, publicKey: kp.publicKey }), + ), + 4, + ), + }, + { + key: 'v4r2', + name: 'Wallet V4R2', + measureFunction: createStandardWalletMeasureFunction( + 'Wallet V4R2', + (blockchain, kp) => + blockchain.openContract(WalletContractV4.create({ workchain: 0, publicKey: kp.publicKey })), + 4, + ), + }, + { + key: 'v5r1', + name: 'Wallet V5R1', + measureFunction: createStandardWalletMeasureFunction( + 'Wallet V5R1', + (blockchain, kp) => + blockchain.openContract( + WalletContractV5R1.create({ workchain: 0, publicKey: kp.publicKey }), + ), + 255, + ), + }, + { + key: 'preprocessedV2', + name: 'Preprocessed Wallet V2', + measureFunction: measurePreprocessedWalletV2, + }, + { + key: 'highloadV3', + name: 'Highload Wallet V3', + measureFunction: measureHighloadV3, + }, +]; + +export const DEFAULT_CONSTANTS: TestConstants = { + messageValue: toNano('0.01'), + deployValue: toNano('1000'), +}; + +export async function runAllMeasurements(config: { + enabledWallets: Record; + testRuns: Array<{ + messageCount: number; + bodyResolver: MessageBodyResolver; + bodyName: string; + }>; + constants: TestConstants; +}): Promise { + const keyPair = keyPairFromSeed(await getSecureRandomBytes(32)); + const allResults: WalletTestResult[][] = []; + + const enabledWalletConfigs = WALLET_CONFIGS.filter( + (walletConfig) => config.enabledWallets[walletConfig.key], + ); + + for (const testRun of config.testRuns) { + const results: WalletTestResult[] = []; + + for (const walletConfig of enabledWalletConfigs) { + const result = await walletConfig.measureFunction( + testRun.messageCount, + testRun.bodyResolver, + testRun.bodyName, + config.constants, + ); + results.push(result); + } + + allResults.push(results); + } + + return allResults; +} diff --git a/standard/wallets/comparison/tests/imports/const.ts b/standard/wallets/comparison/tests/imports/const.ts index 95c2d77..41c3270 100644 --- a/standard/wallets/comparison/tests/imports/const.ts +++ b/standard/wallets/comparison/tests/imports/const.ts @@ -3,16 +3,16 @@ export const SUBWALLET_ID = 239; export const DEFAULT_TIMEOUT = 128; export enum OP { - InternalTransfer = 0xae42e5a4 + InternalTransfer = 0xae42e5a4, } export abstract class Errors { - static invalid_signature = 33; - static invalid_subwallet = 34; - static invalid_creation_time = 35; - static already_executed = 36; + static invalid_signature = 33; + static invalid_subwallet = 34; + static invalid_creation_time = 35; + static already_executed = 36; } -export const maxKeyCount = (1 << 13); //That is max key count not max key value -export const maxShift = maxKeyCount - 1; +export const maxKeyCount = 1 << 13; //That is max key count not max key value +export const maxShift = maxKeyCount - 1; export const maxQueryCount = maxKeyCount * 1023; // Therefore value count -export const maxQueryId = (maxShift << 10) + 1022; \ No newline at end of file +export const maxQueryId = (maxShift << 10) + 1022; diff --git a/standard/wallets/comparison/tests/print-tables.ts b/standard/wallets/comparison/tests/print-tables.ts new file mode 100644 index 0000000..d759e1e --- /dev/null +++ b/standard/wallets/comparison/tests/print-tables.ts @@ -0,0 +1,390 @@ +import { writeFileSync, mkdirSync } from 'fs'; +import path from 'path'; +import { WalletTestResult, toCoins } from './get-results'; + +type ColumnAlignment = 'left' | 'center' | 'right'; + +type ColumnConfig = { + key: string; + header: string; + alignment: ColumnAlignment; + enabled: boolean; +}; + +type RequestTimings = { + realSeconds: number; + theoreticalSeconds: number; +}; + +type TestRun = { + messageCount: number; + bodyName: string; +}; + +export type PrintTablesConfig = { + allResults: WalletTestResult[][]; + testRuns: TestRun[]; + columnOrder: ColumnConfig[]; + requestTimings: RequestTimings; + highloadWalletName: string; + preprocessedWalletName: string; + outputDirectory: string; +}; + +export const formatSeconds = (seconds: number): string => { + const totalSeconds = Math.round(seconds); + if (totalSeconds < 60) { + return `${totalSeconds}s`; + } + const minutes = Math.floor(totalSeconds / 60); + const secs = totalSeconds % 60; + return `${minutes}m ${secs}s`; +}; + +const trimCommonTrailingZeros = (values: string[]): string[] => { + if (values.length === 0) return values; + + const numericValues = values.filter((v) => /^\d+\.\d+$/.test(v)); + if (numericValues.length !== values.length) return values; + + let commonTrailingZeros = Infinity; + for (const value of values) { + const decimals = value.split('.')[1]; + let trailingZeros = 0; + for (let i = decimals.length - 1; i >= 0; i--) { + if (decimals[i] === '0') { + trailingZeros++; + } else { + break; + } + } + commonTrailingZeros = Math.min(commonTrailingZeros, trailingZeros); + } + + if (commonTrailingZeros >= 2) { + return values.map((v) => { + const [integer, decimals] = v.split('.'); + const trimmedDecimals = decimals.slice(0, decimals.length - commonTrailingZeros); + return trimmedDecimals ? `${integer}.${trimmedDecimals}` : integer; + }); + } + + return values; +}; + +const formatPercentDiffPlain = (value: bigint, baseline: bigint): string => { + if (baseline === 0n) { + return 'N/A'; + } + const diff = Number(((value - baseline) * 10000n) / baseline) / 100; + if (!Number.isFinite(diff)) { + return 'N/A'; + } + if (diff === 0) { + return '0.00%'; + } + const prefix = diff > 0 ? '+' : ''; + return `${prefix}${diff.toFixed(2)}%`; +}; + +const formatPercentDiffMarkdown = (value: bigint, baseline: bigint): string => { + if (baseline === 0n) { + return 'N/A'; + } + if (value === baseline) { + return '**Best**'; + } + return formatPercentDiffPlain(value, baseline); +}; + +const getAlignmentMarker = (alignment: ColumnAlignment): string => { + switch (alignment) { + case 'left': + return ':---'; + case 'center': + return ':---:'; + case 'right': + return '---:'; + } +}; + +export async function printTables(config: PrintTablesConfig) { + const { + allResults, + testRuns, + columnOrder, + requestTimings, + highloadWalletName, + preprocessedWalletName, + outputDirectory, + } = config; + + if (allResults.length === 0) return; + + const markdownLines: string[] = ['# Wallet Fee Comparison Results', '']; + const numberFormatter = new Intl.NumberFormat('en-US'); + const tonFormatter = new Intl.NumberFormat('en-US', { + minimumFractionDigits: 9, + maximumFractionDigits: 9, + }); + + const resultsByBody = new Map>(); + + allResults.forEach((results, runIndex) => { + if (results.length === 0) return; + + const testRun = testRuns[runIndex]; + if (!resultsByBody.has(testRun.bodyName)) { + resultsByBody.set(testRun.bodyName, []); + } + resultsByBody.get(testRun.bodyName)!.push({ results, runIndex }); + }); + + const isHighloadResult = (result: WalletTestResult) => result.walletName === highloadWalletName; + + const formatRealTime = (result: WalletTestResult): string => { + const seconds = isHighloadResult(result) + ? requestTimings.realSeconds + : result.requests * requestTimings.realSeconds; + return formatSeconds(seconds); + }; + + const formatTheoreticalTime = (result: WalletTestResult): string => { + const seconds = isHighloadResult(result) + ? requestTimings.theoreticalSeconds + : result.requests * requestTimings.theoreticalSeconds; + return formatSeconds(seconds); + }; + + const createColumns = ( + gasPerMsgValues: bigint[], + feePerMsgValues: bigint[], + minGasPerMsg: bigint, + minFeePerMsg: bigint, + ) => { + const columnAccessors: Record< + string, + { + markdownAccessor: (result: WalletTestResult, index: number) => string; + consoleAccessor: (result: WalletTestResult, index: number) => string | number; + } + > = { + walletVersion: { + markdownAccessor: (result, idx) => { + const gasPerMsg = gasPerMsgValues[idx]; + const feePerMsg = feePerMsgValues[idx]; + const isGasBest = gasPerMsg === minGasPerMsg; + const isFeeBest = feePerMsg === minFeePerMsg; + const isBest = isGasBest || isFeeBest; + return isBest ? `**${result.walletName}** ${isFeeBest ? '✅' : ''}` : result.walletName; + }, + consoleAccessor: (result) => result.walletName, + }, + gasDelta: { + markdownAccessor: (_result, idx) => + formatPercentDiffMarkdown(gasPerMsgValues[idx], minGasPerMsg), + consoleAccessor: (_result, idx) => + formatPercentDiffPlain(gasPerMsgValues[idx], minGasPerMsg), + }, + feeDelta: { + markdownAccessor: (_result, idx) => + formatPercentDiffMarkdown(feePerMsgValues[idx], minFeePerMsg), + consoleAccessor: (_result, idx) => + formatPercentDiffPlain(feePerMsgValues[idx], minFeePerMsg), + }, + requests: { + markdownAccessor: (result) => numberFormatter.format(result.requests), + consoleAccessor: (result) => result.requests, + }, + totalGas: { + markdownAccessor: (result) => numberFormatter.format(Number(result.totalGas)), + consoleAccessor: (result) => Number(result.totalGas), + }, + gasPerMsg: { + markdownAccessor: (_result, idx) => numberFormatter.format(Number(gasPerMsgValues[idx])), + consoleAccessor: (_result, idx) => Number(gasPerMsgValues[idx]), + }, + totalFee: { + markdownAccessor: (result) => tonFormatter.format(toCoins(result.totalFee)), + consoleAccessor: (result) => toCoins(result.totalFee), + }, + feePerMsg: { + markdownAccessor: (_result, idx) => tonFormatter.format(toCoins(feePerMsgValues[idx])), + consoleAccessor: (_result, idx) => toCoins(feePerMsgValues[idx]), + }, + realTime: { + markdownAccessor: (result) => formatRealTime(result), + consoleAccessor: (result) => formatRealTime(result), + }, + theoryTime: { + markdownAccessor: (result) => formatTheoreticalTime(result), + consoleAccessor: (result) => formatTheoreticalTime(result), + }, + }; + + return columnOrder + .filter((col) => col.enabled) + .map((col) => ({ + header: col.header, + alignment: col.alignment, + markdownAccessor: columnAccessors[col.key].markdownAccessor, + consoleAccessor: columnAccessors[col.key].consoleAccessor, + })); + }; + + resultsByBody.forEach((runsData, bodyName) => { + markdownLines.push(`## ${bodyName}`, ''); + + runsData.forEach(({ results, runIndex }) => { + const testRun = testRuns[runIndex]; + markdownLines.push(`### ${testRun.messageCount} Messages`, ''); + + const mainResults = results.filter((r) => r.walletName !== preprocessedWalletName); + const preprocessedResults = results.filter((r) => r.walletName === preprocessedWalletName); + + const mainGasPerMsgValues = mainResults.map((r) => r.totalGas / BigInt(r.messageCount)); + const mainFeePerMsgValues = mainResults.map((r) => r.totalFee / BigInt(r.messageCount)); + const minGasPerMsg = + mainGasPerMsgValues.length > 0 + ? mainGasPerMsgValues.reduce( + (min, val) => (val < min ? val : min), + mainGasPerMsgValues[0], + ) + : 0n; + const minFeePerMsg = + mainFeePerMsgValues.length > 0 + ? mainFeePerMsgValues.reduce( + (min, val) => (val < min ? val : min), + mainFeePerMsgValues[0], + ) + : 0n; + + let mainColumns: ReturnType | null = null; + let mainAllRowCells: string[][] = []; + let preprocessedColumns: ReturnType | null = null; + let preprocessedAllRowCells: string[][] = []; + + if (mainResults.length > 0) { + mainColumns = createColumns( + mainGasPerMsgValues, + mainFeePerMsgValues, + minGasPerMsg, + minFeePerMsg, + ); + mainAllRowCells = mainResults.map((result, idx) => + mainColumns!.map((column) => column.markdownAccessor(result, idx)), + ); + } + + if (preprocessedResults.length > 0) { + const preprocessedGasPerMsgValues = preprocessedResults.map( + (r) => r.totalGas / BigInt(r.messageCount), + ); + const preprocessedFeePerMsgValues = preprocessedResults.map( + (r) => r.totalFee / BigInt(r.messageCount), + ); + preprocessedColumns = createColumns( + preprocessedGasPerMsgValues, + preprocessedFeePerMsgValues, + minGasPerMsg, + minFeePerMsg, + ); + preprocessedAllRowCells = preprocessedResults.map((result, idx) => + preprocessedColumns!.map((column) => column.markdownAccessor(result, idx)), + ); + } + + const columns = mainColumns || preprocessedColumns; + if (!columns) return; + + const columnIndices = { + totalFee: columns.findIndex((c) => c.header === 'Total Fee (TON)'), + feePerMsg: columns.findIndex((c) => c.header === 'Fee/Msg (TON)'), + }; + + if (columnIndices.totalFee >= 0) { + const allTotalFeeValues = [ + ...mainAllRowCells.map((row) => row[columnIndices.totalFee]), + ...preprocessedAllRowCells.map((row) => row[columnIndices.totalFee]), + ]; + const trimmedTotalFee = trimCommonTrailingZeros(allTotalFeeValues); + + mainAllRowCells.forEach((row, idx) => { + row[columnIndices.totalFee] = trimmedTotalFee[idx]; + }); + preprocessedAllRowCells.forEach((row, idx) => { + row[columnIndices.totalFee] = trimmedTotalFee[mainAllRowCells.length + idx]; + }); + } + + if (columnIndices.feePerMsg >= 0) { + const allFeePerMsgValues = [ + ...mainAllRowCells.map((row) => row[columnIndices.feePerMsg]), + ...preprocessedAllRowCells.map((row) => row[columnIndices.feePerMsg]), + ]; + const trimmedFeePerMsg = trimCommonTrailingZeros(allFeePerMsgValues); + + mainAllRowCells.forEach((row, idx) => { + row[columnIndices.feePerMsg] = trimmedFeePerMsg[idx]; + }); + preprocessedAllRowCells.forEach((row, idx) => { + row[columnIndices.feePerMsg] = trimmedFeePerMsg[mainAllRowCells.length + idx]; + }); + } + + if (mainResults.length > 0 && mainColumns) { + const headerRow = `| ${mainColumns.map((column) => column.header).join(' | ')} |`; + const separatorRow = `| ${mainColumns.map((column) => getAlignmentMarker(column.alignment)).join(' | ')} |`; + markdownLines.push(headerRow, separatorRow); + + mainAllRowCells.forEach((rowCells) => { + markdownLines.push(`| ${rowCells.join(' | ')} |`); + }); + + const consoleRows = mainResults.map((result, idx) => { + const row: Record = {}; + mainColumns.forEach((column) => { + row[column.header] = column.consoleAccessor(result, idx); + }); + return row; + }); + + console.log(`\n${testRun.bodyName} - ${testRun.messageCount} Messages`); + console.table(consoleRows); + + markdownLines.push(''); + } + + if (preprocessedResults.length > 0 && preprocessedColumns) { + markdownLines.push('**Preprocessed Wallet V2**', ''); + + const headerRow = `| ${preprocessedColumns.map((column) => column.header).join(' | ')} |`; + const separatorRow = `| ${preprocessedColumns.map((column) => getAlignmentMarker(column.alignment)).join(' | ')} |`; + markdownLines.push(headerRow, separatorRow); + + preprocessedAllRowCells.forEach((rowCells) => { + markdownLines.push(`| ${rowCells.join(' | ')} |`); + }); + + const consoleRows = preprocessedResults.map((result, idx) => { + const row: Record = {}; + preprocessedColumns.forEach((column) => { + row[column.header] = column.consoleAccessor(result, idx); + }); + return row; + }); + + console.log(`\nPreprocessed Wallet V2:`); + console.table(consoleRows); + + markdownLines.push(''); + } + }); + }); + + const outputDir = path.resolve(outputDirectory, 'results'); + const outputFile = path.join(outputDir, 'wallet-fee-comparison.md'); + mkdirSync(outputDir, { recursive: true }); + writeFileSync(outputFile, markdownLines.join('\n'), { encoding: 'utf-8' }); + console.log(`Markdown report saved to ${outputFile}`); +} diff --git a/standard/wallets/comparison/tests/results/wallet-fee-comparison.md b/standard/wallets/comparison/tests/results/wallet-fee-comparison.md index 9be55f3..f814a98 100644 --- a/standard/wallets/comparison/tests/results/wallet-fee-comparison.md +++ b/standard/wallets/comparison/tests/results/wallet-fee-comparison.md @@ -1,145 +1,223 @@ # Wallet Fee Comparison Results -## Run 1: 1 messages, Body: Empty - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| **Wallet V2R1** ✅ | 1 | 2,769 | 2,769 | 0.0017108 | 0.0017108 | **Best** | **Best** | 13s | 4s | -| Wallet V2R2 | 1 | 2,846 | 2,846 | 0.0017416 | 0.0017416 | +2.78% | +1.80% | 13s | 4s | -| Wallet V3R1 | 1 | 2,917 | 2,917 | 0.00177 | 0.00177 | +5.34% | +3.46% | 13s | 4s | -| Wallet V3R2 | 1 | 2,994 | 2,994 | 0.0018008 | 0.0018008 | +8.12% | +5.26% | 13s | 4s | -| Wallet V4R2 | 1 | 3,308 | 3,308 | 0.0019264 | 0.0019264 | +19.46% | +12.60% | 13s | 4s | -| Wallet V5R1 | 1 | 4,939 | 4,939 | 0.0026748 | 0.0026748 | +78.36% | +56.34% | 13s | 4s | -| Highload Wallet V3 | 1 | 7,956 | 7,956 | 0.0049124 | 0.0049124 | +187.32% | +187.14% | 13s | 4s | - -## Run 2: 4 messages, Body: Empty - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| **Wallet V2R1** ✅ | 1 | 4,695 | 1,173 | 0.0030908 | 0.0007727 | **Best** | **Best** | 13s | 4s | -| Wallet V2R2 | 1 | 4,772 | 1,193 | 0.0031216 | 0.0007804 | +1.70% | +0.99% | 13s | 4s | -| Wallet V3R1 | 1 | 4,843 | 1,210 | 0.00315 | 0.0007875 | +3.15% | +1.91% | 13s | 4s | -| Wallet V3R2 | 1 | 4,920 | 1,230 | 0.0031808 | 0.0007952 | +4.85% | +2.91% | 13s | 4s | -| Wallet V4R2 | 1 | 5,234 | 1,308 | 0.0033064 | 0.0008266 | +11.50% | +6.97% | 13s | 4s | -| Wallet V5R1 | 1 | 7,090 | 1,772 | 0.0043128 | 0.0010782 | +51.06% | +39.53% | 13s | 4s | -| Highload Wallet V3 | 1 | 7,956 | 1,989 | 0.0064676 | 0.0016169 | +69.56% | +109.25% | 13s | 4s | - -## Run 3: 200 messages, Body: Empty - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Wallet V2R1 | 50 | 234,750 | 1,173 | 0.15454 | 0.0007727 | +2907.69% | +42.99% | 10m 50s | 3m 20s | -| Wallet V2R2 | 50 | 238,600 | 1,193 | 0.15608 | 0.0007804 | +2958.97% | +44.41% | 10m 50s | 3m 20s | -| Wallet V3R1 | 50 | 242,150 | 1,210 | 0.1575 | 0.0007875 | +3002.56% | +45.73% | 10m 50s | 3m 20s | -| Wallet V3R2 | 50 | 246,000 | 1,230 | 0.15904 | 0.0007952 | +3053.84% | +47.15% | 10m 50s | 3m 20s | -| Wallet V4R2 | 50 | 261,700 | 1,308 | 0.16532 | 0.0008266 | +3253.84% | +52.96% | 10m 50s | 3m 20s | -| Wallet V5R1 | 1 | 147,622 | 738 | 0.1113288 | 0.000556644 | +1792.30% | +3.01% | 13s | 4s | -| **Highload Wallet V3** ✅ | 1 | 7,956 | 39 | 0.108074 | 0.00054037 | **Best** | **Best** | 13s | 4s | - -## Run 4: 1000 messages, Body: Empty - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Wallet V2R1 | 250 | 1,173,750 | 1,173 | 0.7727 | 0.0007727 | +3683.87% | +44.18% | 54m 10s | 16m 40s | -| Wallet V2R2 | 250 | 1,193,000 | 1,193 | 0.7804 | 0.0007804 | +3748.38% | +45.61% | 54m 10s | 16m 40s | -| Wallet V3R1 | 250 | 1,210,750 | 1,210 | 0.7875 | 0.0007875 | +3803.22% | +46.94% | 54m 10s | 16m 40s | -| Wallet V3R2 | 250 | 1,230,000 | 1,230 | 0.7952 | 0.0007952 | +3867.74% | +48.37% | 54m 10s | 16m 40s | -| Wallet V4R2 | 250 | 1,308,500 | 1,308 | 0.8266 | 0.0008266 | +4119.35% | +54.23% | 54m 10s | 16m 40s | -| Wallet V5R1 | 4 | 733,888 | 733 | 0.554515201 | 0.000554515 | +2264.51% | +3.46% | 52s | 16s | -| **Highload Wallet V3** ✅ | 4 | 31,689 | 31 | 0.535922 | 0.000535922 | **Best** | **Best** | 13s | 4s | - -## Run 5: 1 messages, Body: Comment - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| **Wallet V2R1** ✅ | 1 | 2,769 | 2,769 | 0.001762 | 0.001762 | **Best** | **Best** | 13s | 4s | -| Wallet V2R2 | 1 | 2,846 | 2,846 | 0.0017928 | 0.0017928 | +2.78% | +1.74% | 13s | 4s | -| Wallet V3R1 | 1 | 2,917 | 2,917 | 0.0018212 | 0.0018212 | +5.34% | +3.35% | 13s | 4s | -| Wallet V3R2 | 1 | 2,994 | 2,994 | 0.001852 | 0.001852 | +8.12% | +5.10% | 13s | 4s | -| Wallet V4R2 | 1 | 3,308 | 3,308 | 0.0019776 | 0.0019776 | +19.46% | +12.23% | 13s | 4s | -| Wallet V5R1 | 1 | 4,939 | 4,939 | 0.002726 | 0.002726 | +78.36% | +54.71% | 13s | 4s | -| Highload Wallet V3 | 1 | 7,956 | 7,956 | 0.0050148 | 0.0050148 | +187.32% | +184.60% | 13s | 4s | - -## Run 6: 4 messages, Body: Comment - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| **Wallet V2R1** ✅ | 1 | 4,695 | 1,173 | 0.0032956 | 0.0008239 | **Best** | **Best** | 13s | 4s | -| Wallet V2R2 | 1 | 4,772 | 1,193 | 0.0033264 | 0.0008316 | +1.70% | +0.93% | 13s | 4s | -| Wallet V3R1 | 1 | 4,843 | 1,210 | 0.0033548 | 0.0008387 | +3.15% | +1.79% | 13s | 4s | -| Wallet V3R2 | 1 | 4,920 | 1,230 | 0.0033856 | 0.0008464 | +4.85% | +2.73% | 13s | 4s | -| Wallet V4R2 | 1 | 5,234 | 1,308 | 0.0035112 | 0.0008778 | +11.50% | +6.54% | 13s | 4s | -| Wallet V5R1 | 1 | 7,090 | 1,772 | 0.0045176 | 0.0011294 | +51.06% | +37.07% | 13s | 4s | -| Highload Wallet V3 | 1 | 7,956 | 1,989 | 0.0068772 | 0.0017193 | +69.56% | +108.67% | 13s | 4s | - -## Run 7: 200 messages, Body: Comment - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Wallet V2R1 | 50 | 234,750 | 1,173 | 0.16478 | 0.0008239 | +2907.69% | +35.54% | 10m 50s | 3m 20s | -| Wallet V2R2 | 50 | 238,600 | 1,193 | 0.16632 | 0.0008316 | +2958.97% | +36.81% | 10m 50s | 3m 20s | -| Wallet V3R1 | 50 | 242,150 | 1,210 | 0.16774 | 0.0008387 | +3002.56% | +37.97% | 10m 50s | 3m 20s | -| Wallet V3R2 | 50 | 246,000 | 1,230 | 0.16928 | 0.0008464 | +3053.84% | +39.24% | 10m 50s | 3m 20s | -| Wallet V4R2 | 50 | 261,700 | 1,308 | 0.17556 | 0.0008778 | +3253.84% | +44.41% | 10m 50s | 3m 20s | -| **Wallet V5R1** ✅ | 1 | 147,622 | 738 | 0.1215688 | 0.000607844 | +1792.30% | **Best** | 13s | 4s | -| **Highload Wallet V3** | 1 | 7,956 | 39 | 0.128554 | 0.00064277 | **Best** | +5.74% | 13s | 4s | - -## Run 8: 1000 messages, Body: Comment - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Wallet V2R1 | 250 | 1,173,750 | 1,173 | 0.8239 | 0.0008239 | +3683.87% | +36.02% | 54m 10s | 16m 40s | -| Wallet V2R2 | 250 | 1,193,000 | 1,193 | 0.8316 | 0.0008316 | +3748.38% | +37.29% | 54m 10s | 16m 40s | -| Wallet V3R1 | 250 | 1,210,750 | 1,210 | 0.8387 | 0.0008387 | +3803.22% | +38.46% | 54m 10s | 16m 40s | -| Wallet V3R2 | 250 | 1,230,000 | 1,230 | 0.8464 | 0.0008464 | +3867.74% | +39.73% | 54m 10s | 16m 40s | -| Wallet V4R2 | 250 | 1,308,500 | 1,308 | 0.8778 | 0.0008778 | +4119.35% | +44.91% | 54m 10s | 16m 40s | -| **Wallet V5R1** ✅ | 4 | 733,888 | 733 | 0.6057152 | 0.000605715 | +2264.51% | **Best** | 52s | 16s | -| **Highload Wallet V3** | 4 | 31,689 | 31 | 0.638322 | 0.000638322 | **Best** | +5.38% | 13s | 4s | - -## Run 9: 1 messages, Body: Jetton - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| **Wallet V2R1** ✅ | 1 | 2,769 | 2,769 | 0.0021012 | 0.0021012 | **Best** | **Best** | 13s | 4s | -| Wallet V2R2 | 1 | 2,846 | 2,846 | 0.002132 | 0.002132 | +2.78% | +1.46% | 13s | 4s | -| Wallet V3R1 | 1 | 2,917 | 2,917 | 0.0021604 | 0.0021604 | +5.34% | +2.81% | 13s | 4s | -| Wallet V3R2 | 1 | 2,994 | 2,994 | 0.002191201 | 0.002191201 | +8.12% | +4.28% | 13s | 4s | -| Wallet V4R2 | 1 | 3,308 | 3,308 | 0.0023168 | 0.0023168 | +19.46% | +10.26% | 13s | 4s | -| Wallet V5R1 | 1 | 4,939 | 4,939 | 0.0030652 | 0.0030652 | +78.36% | +45.87% | 13s | 4s | -| Highload Wallet V3 | 1 | 7,956 | 7,956 | 0.0056932 | 0.0056932 | +187.32% | +170.94% | 13s | 4s | - -## Run 10: 4 messages, Body: Jetton - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| **Wallet V2R1** ✅ | 1 | 4,695 | 1,173 | 0.0046524 | 0.0011631 | **Best** | **Best** | 13s | 4s | -| Wallet V2R2 | 1 | 4,772 | 1,193 | 0.0046832 | 0.0011708 | +1.70% | +0.66% | 13s | 4s | -| Wallet V3R1 | 1 | 4,843 | 1,210 | 0.0047116 | 0.0011779 | +3.15% | +1.27% | 13s | 4s | -| Wallet V3R2 | 1 | 4,920 | 1,230 | 0.004742401 | 0.0011856 | +4.85% | +1.93% | 13s | 4s | -| Wallet V4R2 | 1 | 5,234 | 1,308 | 0.004868 | 0.001217 | +11.50% | +4.63% | 13s | 4s | -| Wallet V5R1 | 1 | 7,090 | 1,772 | 0.0058744 | 0.0014686 | +51.06% | +26.26% | 13s | 4s | -| Highload Wallet V3 | 1 | 7,956 | 1,989 | 0.0095908 | 0.0023977 | +69.56% | +106.14% | 13s | 4s | - -## Run 11: 200 messages, Body: Jetton - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Wallet V2R1 | 50 | 234,750 | 1,173 | 0.23262 | 0.0011631 | +2907.69% | +22.81% | 10m 50s | 3m 20s | -| Wallet V2R2 | 50 | 238,600 | 1,193 | 0.23416 | 0.0011708 | +2958.97% | +23.62% | 10m 50s | 3m 20s | -| Wallet V3R1 | 50 | 242,150 | 1,210 | 0.23558 | 0.0011779 | +3002.56% | +24.37% | 10m 50s | 3m 20s | -| Wallet V3R2 | 50 | 246,000 | 1,230 | 0.23712 | 0.0011856 | +3053.84% | +25.18% | 10m 50s | 3m 20s | -| Wallet V4R2 | 50 | 261,700 | 1,308 | 0.2434 | 0.001217 | +3253.84% | +28.50% | 10m 50s | 3m 20s | -| **Wallet V5R1** ✅ | 1 | 147,622 | 738 | 0.1894088 | 0.000947044 | +1792.30% | **Best** | 13s | 4s | -| **Highload Wallet V3** | 1 | 7,956 | 39 | 0.264234 | 0.00132117 | **Best** | +39.50% | 13s | 4s | - -## Run 12: 1000 messages, Body: Jetton - -| Wallet Version | Requests | Total Gas | Gas per Msg | Total Fee (TON) | Fee per Msg (TON) | Gas delta (%) | Fee delta (%) | Real Time (sec) | Theoretical Time (sec) | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Wallet V2R1 | 250 | 1,173,750 | 1,173 | 1.1631 | 0.0011631 | +3683.87% | +23.09% | 54m 10s | 16m 40s | -| Wallet V2R2 | 250 | 1,193,000 | 1,193 | 1.1708 | 0.0011708 | +3748.38% | +23.90% | 54m 10s | 16m 40s | -| Wallet V3R1 | 250 | 1,210,750 | 1,210 | 1.1779 | 0.0011779 | +3803.22% | +24.65% | 54m 10s | 16m 40s | -| Wallet V3R2 | 250 | 1,230,000 | 1,230 | 1.18560025 | 0.0011856 | +3867.74% | +25.47% | 54m 10s | 16m 40s | -| Wallet V4R2 | 250 | 1,308,500 | 1,308 | 1.217 | 0.001217 | +4119.35% | +28.79% | 54m 10s | 16m 40s | -| **Wallet V5R1** ✅ | 4 | 733,888 | 733 | 0.944915201 | 0.000944915 | +2264.51% | **Best** | 52s | 16s | -| **Highload Wallet V3** | 4 | 31,689 | 31 | 1.316722 | 0.001316722 | **Best** | +39.34% | 13s | 4s | +### Sending TONs + +#### 1 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :----------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| **Wallet V2R1** ✅ | **Best** | **Best** | 1 | 2,769 | 2,769 | 0.0017108 | 0.0017108 | 13s | 4s | +| Wallet V2R2 | +2.78% | +1.80% | 1 | 2,846 | 2,846 | 0.0017416 | 0.0017416 | 13s | 4s | +| Wallet V3R1 | +5.34% | +3.46% | 1 | 2,917 | 2,917 | 0.0017700 | 0.0017700 | 13s | 4s | +| Wallet V3R2 | +8.12% | +5.26% | 1 | 2,994 | 2,994 | 0.0018008 | 0.0018008 | 13s | 4s | +| Wallet V4R2 | +19.46% | +12.60% | 1 | 3,308 | 3,308 | 0.0019264 | 0.0019264 | 13s | 4s | +| Wallet V5R1 | +78.36% | +56.34% | 1 | 4,939 | 4,939 | 0.0026748 | 0.0026748 | 13s | 4s | +| Highload Wallet V3 | +187.32% | +187.14% | 1 | 7,956 | 7,956 | 0.0049124 | 0.0049124 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -44.49% | -18.98% | 1 | 1,537 | 1,537 | 0.0013860 | 0.0013860 | 13s | 4s | + +#### 4 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :----------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| **Wallet V2R1** ✅ | **Best** | **Best** | 1 | 4,695 | 1,173 | 0.0030908 | 0.0007727 | 13s | 4s | +| Wallet V2R2 | +1.70% | +0.99% | 1 | 4,772 | 1,193 | 0.0031216 | 0.0007804 | 13s | 4s | +| Wallet V3R1 | +3.15% | +1.91% | 1 | 4,843 | 1,210 | 0.0031500 | 0.0007875 | 13s | 4s | +| Wallet V3R2 | +4.85% | +2.91% | 1 | 4,920 | 1,230 | 0.0031808 | 0.0007952 | 13s | 4s | +| Wallet V4R2 | +11.50% | +6.97% | 1 | 5,234 | 1,308 | 0.0033064 | 0.0008266 | 13s | 4s | +| Wallet V5R1 | +51.06% | +39.53% | 1 | 7,090 | 1,772 | 0.0043128 | 0.0010782 | 13s | 4s | +| Highload Wallet V3 | +69.56% | +109.25% | 1 | 7,956 | 1,989 | 0.0064676 | 0.0016169 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -67.26% | -29.99% | 1 | 1,537 | 384 | 0.0021636 | 0.0005409 | 13s | 4s | + +#### 200 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :-----------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Wallet V2R1 | +2907.69% | +42.99% | 50 | 234,750 | 1,173 | 0.1545400 | 0.000772700 | 10m 50s | 3m 20s | +| Wallet V2R2 | +2958.97% | +44.41% | 50 | 238,600 | 1,193 | 0.1560800 | 0.000780400 | 10m 50s | 3m 20s | +| Wallet V3R1 | +3002.56% | +45.73% | 50 | 242,150 | 1,210 | 0.1575000 | 0.000787500 | 10m 50s | 3m 20s | +| Wallet V3R2 | +3053.84% | +47.15% | 50 | 246,000 | 1,230 | 0.1590400 | 0.000795200 | 10m 50s | 3m 20s | +| Wallet V4R2 | +3253.84% | +52.96% | 50 | 261,700 | 1,308 | 0.1653200 | 0.000826600 | 10m 50s | 3m 20s | +| Wallet V5R1 | +1792.30% | +3.01% | 1 | 147,622 | 738 | 0.1113288 | 0.000556644 | 13s | 4s | +| **Highload Wallet V3** ✅ | **Best** | **Best** | 1 | 7,956 | 39 | 0.1080740 | 0.000540370 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -82.05% | -50.99% | 1 | 1,537 | 7 | 0.0529668 | 0.000264834 | 13s | 4s | + +#### 1000 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :-----------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Wallet V2R1 | +3683.87% | +44.18% | 250 | 1,173,750 | 1,173 | 0.7727000 | 0.000772700 | 54m 10s | 16m 40s | +| Wallet V2R2 | +3748.38% | +45.61% | 250 | 1,193,000 | 1,193 | 0.7804000 | 0.000780400 | 54m 10s | 16m 40s | +| Wallet V3R1 | +3803.22% | +46.94% | 250 | 1,210,750 | 1,210 | 0.7875000 | 0.000787500 | 54m 10s | 16m 40s | +| Wallet V3R2 | +3867.74% | +48.37% | 250 | 1,230,000 | 1,230 | 0.7952000 | 0.000795200 | 54m 10s | 16m 40s | +| Wallet V4R2 | +4119.35% | +54.23% | 250 | 1,308,500 | 1,308 | 0.8266000 | 0.000826600 | 54m 10s | 16m 40s | +| Wallet V5R1 | +2264.51% | +3.46% | 4 | 733,888 | 733 | 0.5545152 | 0.000554515 | 52s | 16s | +| **Highload Wallet V3** ✅ | **Best** | **Best** | 4 | 31,689 | 31 | 0.5359220 | 0.000535922 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -80.64% | -50.79% | 4 | 6,148 | 6 | 0.2637072 | 0.000263707 | 52s | 16s | + +### Sending Comment + +#### 1 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :----------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| **Wallet V2R1** ✅ | **Best** | **Best** | 1 | 2,769 | 2,769 | 0.0017620 | 0.0017620 | 13s | 4s | +| Wallet V2R2 | +2.78% | +1.74% | 1 | 2,846 | 2,846 | 0.0017928 | 0.0017928 | 13s | 4s | +| Wallet V3R1 | +5.34% | +3.35% | 1 | 2,917 | 2,917 | 0.0018212 | 0.0018212 | 13s | 4s | +| Wallet V3R2 | +8.12% | +5.10% | 1 | 2,994 | 2,994 | 0.0018520 | 0.0018520 | 13s | 4s | +| Wallet V4R2 | +19.46% | +12.23% | 1 | 3,308 | 3,308 | 0.0019776 | 0.0019776 | 13s | 4s | +| Wallet V5R1 | +78.36% | +54.71% | 1 | 4,939 | 4,939 | 0.0027260 | 0.0027260 | 13s | 4s | +| Highload Wallet V3 | +187.32% | +184.60% | 1 | 7,956 | 7,956 | 0.0050148 | 0.0050148 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -44.49% | -18.43% | 1 | 1,537 | 1,537 | 0.0014372 | 0.0014372 | 13s | 4s | + +#### 4 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :----------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| **Wallet V2R1** ✅ | **Best** | **Best** | 1 | 4,695 | 1,173 | 0.0032956 | 0.0008239 | 13s | 4s | +| Wallet V2R2 | +1.70% | +0.93% | 1 | 4,772 | 1,193 | 0.0033264 | 0.0008316 | 13s | 4s | +| Wallet V3R1 | +3.15% | +1.79% | 1 | 4,843 | 1,210 | 0.0033548 | 0.0008387 | 13s | 4s | +| Wallet V3R2 | +4.85% | +2.73% | 1 | 4,920 | 1,230 | 0.0033856 | 0.0008464 | 13s | 4s | +| Wallet V4R2 | +11.50% | +6.54% | 1 | 5,234 | 1,308 | 0.0035112 | 0.0008778 | 13s | 4s | +| Wallet V5R1 | +51.06% | +37.07% | 1 | 7,090 | 1,772 | 0.0045176 | 0.0011294 | 13s | 4s | +| Highload Wallet V3 | +69.56% | +108.67% | 1 | 7,956 | 1,989 | 0.0068772 | 0.0017193 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -67.26% | -28.13% | 1 | 1,537 | 384 | 0.0023684 | 0.0005921 | 13s | 4s | + +#### 200 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Wallet V2R1 | +2907.69% | +35.54% | 50 | 234,750 | 1,173 | 0.1647800 | 0.000823900 | 10m 50s | 3m 20s | +| Wallet V2R2 | +2958.97% | +36.81% | 50 | 238,600 | 1,193 | 0.1663200 | 0.000831600 | 10m 50s | 3m 20s | +| Wallet V3R1 | +3002.56% | +37.97% | 50 | 242,150 | 1,210 | 0.1677400 | 0.000838700 | 10m 50s | 3m 20s | +| Wallet V3R2 | +3053.84% | +39.24% | 50 | 246,000 | 1,230 | 0.1692800 | 0.000846400 | 10m 50s | 3m 20s | +| Wallet V4R2 | +3253.84% | +44.41% | 50 | 261,700 | 1,308 | 0.1755600 | 0.000877800 | 10m 50s | 3m 20s | +| **Wallet V5R1** ✅ | +1792.30% | **Best** | 1 | 147,622 | 738 | 0.1215688 | 0.000607844 | 13s | 4s | +| **Highload Wallet V3** | **Best** | +5.74% | 1 | 7,956 | 39 | 0.1285540 | 0.000642770 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -82.05% | -48.00% | 1 | 1,537 | 7 | 0.0632068 | 0.000316034 | 13s | 4s | + +#### 1000 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Wallet V2R1 | +3683.87% | +36.02% | 250 | 1,173,750 | 1,173 | 0.8239000 | 0.000823900 | 54m 10s | 16m 40s | +| Wallet V2R2 | +3748.38% | +37.29% | 250 | 1,193,000 | 1,193 | 0.8316000 | 0.000831600 | 54m 10s | 16m 40s | +| Wallet V3R1 | +3803.22% | +38.46% | 250 | 1,210,750 | 1,210 | 0.8387000 | 0.000838700 | 54m 10s | 16m 40s | +| Wallet V3R2 | +3867.74% | +39.73% | 250 | 1,230,000 | 1,230 | 0.8464000 | 0.000846400 | 54m 10s | 16m 40s | +| Wallet V4R2 | +4119.35% | +44.91% | 250 | 1,308,500 | 1,308 | 0.8778000 | 0.000877800 | 54m 10s | 16m 40s | +| **Wallet V5R1** ✅ | +2264.51% | **Best** | 4 | 733,888 | 733 | 0.6057152 | 0.000605715 | 52s | 16s | +| **Highload Wallet V3** | **Best** | +5.38% | 4 | 31,689 | 31 | 0.6383220 | 0.000638322 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -80.64% | -48.01% | 4 | 6,148 | 6 | 0.3149072 | 0.000314907 | 52s | 16s | + +### Sending Jettons + +#### 1 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :----------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| **Wallet V2R1** ✅ | **Best** | **Best** | 1 | 2,769 | 2,769 | 0.0021012 | 0.0021012 | 13s | 4s | +| Wallet V2R2 | +2.78% | +1.46% | 1 | 2,846 | 2,846 | 0.0021320 | 0.0021320 | 13s | 4s | +| Wallet V3R1 | +5.34% | +2.81% | 1 | 2,917 | 2,917 | 0.0021604 | 0.0021604 | 13s | 4s | +| Wallet V3R2 | +8.12% | +4.28% | 1 | 2,994 | 2,994 | 0.0021912 | 0.0021912 | 13s | 4s | +| Wallet V4R2 | +19.46% | +10.26% | 1 | 3,308 | 3,308 | 0.0023168 | 0.0023168 | 13s | 4s | +| Wallet V5R1 | +78.36% | +45.87% | 1 | 4,939 | 4,939 | 0.0030652 | 0.0030652 | 13s | 4s | +| Highload Wallet V3 | +187.32% | +170.94% | 1 | 7,956 | 7,956 | 0.0056932 | 0.0056932 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -44.49% | -15.45% | 1 | 1,537 | 1,537 | 0.0017764 | 0.0017764 | 13s | 4s | + +#### 4 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :----------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| **Wallet V2R1** ✅ | **Best** | **Best** | 1 | 4,695 | 1,173 | 0.0046524 | 0.0011631 | 13s | 4s | +| Wallet V2R2 | +1.70% | +0.66% | 1 | 4,772 | 1,193 | 0.0046832 | 0.0011708 | 13s | 4s | +| Wallet V3R1 | +3.15% | +1.27% | 1 | 4,843 | 1,210 | 0.0047116 | 0.0011779 | 13s | 4s | +| Wallet V3R2 | +4.85% | +1.93% | 1 | 4,920 | 1,230 | 0.0047424 | 0.0011856 | 13s | 4s | +| Wallet V4R2 | +11.50% | +4.63% | 1 | 5,234 | 1,308 | 0.0048680 | 0.0012170 | 13s | 4s | +| Wallet V5R1 | +51.06% | +26.26% | 1 | 7,090 | 1,772 | 0.0058744 | 0.0014686 | 13s | 4s | +| Highload Wallet V3 | +69.56% | +106.14% | 1 | 7,956 | 1,989 | 0.0095908 | 0.0023977 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -67.26% | -19.92% | 1 | 1,537 | 384 | 0.0037252 | 0.0009313 | 13s | 4s | + +#### 200 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Wallet V2R1 | +2907.69% | +22.81% | 50 | 234,750 | 1,173 | 0.2326200 | 0.001163100 | 10m 50s | 3m 20s | +| Wallet V2R2 | +2958.97% | +23.62% | 50 | 238,600 | 1,193 | 0.2341600 | 0.001170800 | 10m 50s | 3m 20s | +| Wallet V3R1 | +3002.56% | +24.37% | 50 | 242,150 | 1,210 | 0.2355800 | 0.001177900 | 10m 50s | 3m 20s | +| Wallet V3R2 | +3053.84% | +25.18% | 50 | 246,000 | 1,230 | 0.2371200 | 0.001185600 | 10m 50s | 3m 20s | +| Wallet V4R2 | +3253.84% | +28.50% | 50 | 261,700 | 1,308 | 0.2434000 | 0.001217000 | 10m 50s | 3m 20s | +| **Wallet V5R1** ✅ | +1792.30% | **Best** | 1 | 147,622 | 738 | 0.1894088 | 0.000947044 | 13s | 4s | +| **Highload Wallet V3** | **Best** | +39.50% | 1 | 7,956 | 39 | 0.2642340 | 0.001321170 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -82.05% | -30.81% | 1 | 1,537 | 7 | 0.1310468 | 0.000655234 | 13s | 4s | + +#### 1000 Messages + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Wallet V2R1 | +3683.87% | +23.09% | 250 | 1,173,750 | 1,173 | 1.1631000 | 0.001163100 | 54m 10s | 16m 40s | +| Wallet V2R2 | +3748.38% | +23.90% | 250 | 1,193,000 | 1,193 | 1.1708000 | 0.001170800 | 54m 10s | 16m 40s | +| Wallet V3R1 | +3803.22% | +24.65% | 250 | 1,210,750 | 1,210 | 1.1779000 | 0.001177900 | 54m 10s | 16m 40s | +| Wallet V3R2 | +3867.74% | +25.47% | 250 | 1,230,000 | 1,230 | 1.1856000 | 0.001185600 | 54m 10s | 16m 40s | +| Wallet V4R2 | +4119.35% | +28.79% | 250 | 1,308,500 | 1,308 | 1.2170000 | 0.001217000 | 54m 10s | 16m 40s | +| **Wallet V5R1** ✅ | +2264.51% | **Best** | 4 | 733,888 | 733 | 0.9449152 | 0.000944915 | 52s | 16s | +| **Highload Wallet V3** | **Best** | +39.34% | 4 | 31,689 | 31 | 1.3167220 | 0.001316722 | 13s | 4s | + +**Preprocessed Wallet V2** + +| Wallet Version | Gas delta % | Fee delta % | Requests | Total Gas | Gas/Msg | Total Fee (TON) | Fee/Msg (TON) | Real Time (s) | Theory Time (s) | +| :--------------------: | ----------: | ----------: | -------: | --------: | ------: | --------------: | ------------: | ------------: | --------------: | +| Preprocessed Wallet V2 | -80.64% | -30.77% | 4 | 6,148 | 6 | 0.6541072 | 0.000654107 | 52s | 16s | diff --git a/standard/wallets/comparison/tests/utils/fee-extraction.ts b/standard/wallets/comparison/tests/utils/fee-extraction.ts new file mode 100644 index 0000000..207fbcc --- /dev/null +++ b/standard/wallets/comparison/tests/utils/fee-extraction.ts @@ -0,0 +1,72 @@ +import { Blockchain } from '@ton/sandbox'; +import { beginCell, storeMessage } from '@ton/core'; +import { computeCellForwardFees, getMsgPrices } from './gas-utils'; + +/** + * Transaction fee components extracted from a transaction. + * + * Note: According to TON documentation, msg_fwd_fees already includes the action fee. + * For internal messages: msg_fwd_fees = action_fee + fwd_fee + * where action_fee ≈ msg_fwd_fees * first_frac / 2^16 + * + * Reference: https://docs.ton.org/develop/howto/fees-low-level#forward-fee + */ +export type TransactionFees = { + storage_fee: bigint; // Storage fees collected during storage phase + gas_fees: bigint; // Computation fees (gas) from compute phase + action_fees: bigint; // Action phase fees for sending messages + out_fwd_fees: bigint; // Total forward fees for outbound messages (includes action_fees) + import_fee: bigint; // Import fee for external-in messages (0 for internal) + in_fwd_fee: bigint; // Forward fee for inbound internal messages (0 for external) +}; + +/** + * Extracts fee components from a transaction. + * + * @param tx - Transaction object to analyze + * @param blockchain - Blockchain instance for config access (required for import_fee calculation) + * @returns TransactionFees object with detailed fee breakdown + */ +export function extractTransactionFees(tx: any, blockchain: Blockchain): TransactionFees { + const fees: TransactionFees = { + storage_fee: 0n, + gas_fees: 0n, + action_fees: 0n, + out_fwd_fees: 0n, + import_fee: 0n, + in_fwd_fee: 0n, + }; + + if (tx.description.type !== 'generic') { + return fees; + } + + // Storage fee + fees.storage_fee = (tx.description.storagePhase?.storageFeesCollected ?? 0n) as bigint; + + // Compute phase: gas fees + if (tx.description.computePhase.type === 'vm') { + fees.gas_fees = tx.description.computePhase.gasFees as bigint; + } + + // Action phase: fees for sending messages, setting code, etc. + fees.action_fees = (tx.description.actionPhase?.totalActionFees ?? 0n) as bigint; + + // Action phase: total forward fees for outbound messages + // Note: totalFwdFees includes action_fees (sender's share of msg_fwd_fees) + fees.out_fwd_fees = (tx.description.actionPhase?.totalFwdFees ?? 0n) as bigint; + + // Inbound message fees (depends on message type) + if (tx.inMessage?.info.type === 'external-in') { + // External messages: import fee + const msgPrices = getMsgPrices(blockchain.config, 0); + + const extMsgCell = beginCell().store(storeMessage(tx.inMessage)).endCell(); + fees.import_fee = computeCellForwardFees(msgPrices, extMsgCell); + } else if (tx.inMessage?.info.type === 'internal') { + // Internal messages: forward fee paid by sender + fees.in_fwd_fee = tx.inMessage.info.forwardFee as bigint; + } + + return fees; +} diff --git a/standard/wallets/comparison/tests/utils/feeExtraction.ts b/standard/wallets/comparison/tests/utils/feeExtraction.ts deleted file mode 100644 index 29923d4..0000000 --- a/standard/wallets/comparison/tests/utils/feeExtraction.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Blockchain } from '@ton/sandbox'; -import { beginCell, storeMessage } from '@ton/core'; -import { computeCellForwardFees, getMsgPrices } from './gasUtils'; - -/** - * Transaction fee components extracted from a transaction. - * - * Note: According to TON documentation, msg_fwd_fees already includes the action fee. - * For internal messages: msg_fwd_fees = action_fee + fwd_fee - * where action_fee ≈ msg_fwd_fees * first_frac / 2^16 - * - * Reference: https://docs.ton.org/develop/howto/fees-low-level#forward-fee - */ -export type TransactionFees = { - storage_fee: bigint; // Storage fees collected during storage phase - gas_fees: bigint; // Computation fees (gas) from compute phase - action_fees: bigint; // Action phase fees for sending messages - out_fwd_fees: bigint; // Total forward fees for outbound messages (includes action_fees) - import_fee: bigint; // Import fee for external-in messages (0 for internal) - in_fwd_fee: bigint; // Forward fee for inbound internal messages (0 for external) -}; - -/** - * Extracts fee components from a transaction. - * - * @param tx - Transaction object to analyze - * @param blockchain - Blockchain instance for config access (required for import_fee calculation) - * @returns TransactionFees object with detailed fee breakdown - */ -export function extractTransactionFees(tx: any, blockchain: Blockchain): TransactionFees { - const fees: TransactionFees = { - storage_fee: 0n, - gas_fees: 0n, - action_fees: 0n, - out_fwd_fees: 0n, - import_fee: 0n, - in_fwd_fee: 0n, - }; - - if (tx.description.type !== 'generic') { - return fees; - } - - // Storage fee - fees.storage_fee = (tx.description.storagePhase?.storageFeesCollected ?? 0n) as bigint; - - // Compute phase: gas fees - if (tx.description.computePhase.type === 'vm') { - fees.gas_fees = tx.description.computePhase.gasFees as bigint; - } - - // Action phase: fees for sending messages, setting code, etc. - fees.action_fees = (tx.description.actionPhase?.totalActionFees ?? 0n) as bigint; - - // Action phase: total forward fees for outbound messages - // Note: totalFwdFees includes action_fees (sender's share of msg_fwd_fees) - fees.out_fwd_fees = (tx.description.actionPhase?.totalFwdFees ?? 0n) as bigint; - - // Inbound message fees (depends on message type) - if (tx.inMessage?.info.type === 'external-in') { - // External messages: import fee - const msgPrices = getMsgPrices(blockchain.config, 0); - - const extMsgCell = beginCell().store(storeMessage(tx.inMessage)).endCell(); - fees.import_fee = computeCellForwardFees(msgPrices, extMsgCell); - } else if (tx.inMessage?.info.type === 'internal') { - // Internal messages: forward fee paid by sender - fees.in_fwd_fee = tx.inMessage.info.forwardFee as bigint; - } - - return fees; -} - diff --git a/standard/wallets/comparison/tests/utils/gas-utils.ts b/standard/wallets/comparison/tests/utils/gas-utils.ts new file mode 100644 index 0000000..e189bfb --- /dev/null +++ b/standard/wallets/comparison/tests/utils/gas-utils.ts @@ -0,0 +1,379 @@ +import { + Cell, + Slice, + toNano, + beginCell, + Address, + Dictionary, + Message, + DictionaryValue, + Transaction, +} from '@ton/core'; + +export type GasPrices = { + flat_gas_limit: bigint; + flat_gas_price: bigint; + gas_price: bigint; +}; +export type StoragePrices = { + utime_sice: number; + bit_price_ps: bigint; + cell_price_ps: bigint; + mc_bit_price_ps: bigint; + mc_cell_price_ps: bigint; +}; + +export type MsgPrices = ReturnType; +export type FullFees = ReturnType; + +export class StorageStats { + bits: bigint; + cells: bigint; + + constructor(bits?: number | bigint, cells?: number | bigint) { + this.bits = bits !== undefined ? BigInt(bits) : 0n; + this.cells = cells !== undefined ? BigInt(cells) : 0n; + } + add(...stats: StorageStats[]) { + let cells = this.cells, + bits = this.bits; + for (let stat of stats) { + bits += stat.bits; + cells += stat.cells; + } + return new StorageStats(bits, cells); + } + sub(...stats: StorageStats[]) { + let cells = this.cells, + bits = this.bits; + for (let stat of stats) { + bits -= stat.bits; + cells -= stat.cells; + } + return new StorageStats(bits, cells); + } + addBits(bits: number | bigint) { + return new StorageStats(this.bits + BigInt(bits), this.cells); + } + subBits(bits: number | bigint) { + return new StorageStats(this.bits - BigInt(bits), this.cells); + } + addCells(cells: number | bigint) { + return new StorageStats(this.bits, this.cells + BigInt(cells)); + } + subCells(cells: number | bigint) { + return new StorageStats(this.bits, this.cells - BigInt(cells)); + } + + toString(): string { + return JSON.stringify({ + bits: this.bits.toString(), + cells: this.cells.toString(), + }); + } +} + +export function computedGeneric(transaction: T) { + if (transaction.description.type !== 'generic') + throw new Error('Expected generic transactionaction'); + if (transaction.description.computePhase.type !== 'vm') throw new Error('Compute phase expected'); + return transaction.description.computePhase; +} + +export function storageGeneric(transaction: T) { + if (transaction.description.type !== 'generic') + throw new Error('Expected generic transactionaction'); + const storagePhase = transaction.description.storagePhase; + if (storagePhase === null || storagePhase === undefined) + throw new Error('Storage phase expected'); + return storagePhase; +} +export function getFwdStats(transaction: T) { + if (transaction.description.type !== 'generic') throw new Error('Expected generic transaction'); + if (transaction.description.actionPhase == undefined) throw new Error('Action phase expected'); + const actionMsgSize = transaction.description.actionPhase.totalMessageSize; + return new StorageStats(actionMsgSize.bits, actionMsgSize.cells); +} + +function shr16ceil(src: bigint) { + let rem = src % BigInt(65536); + let res = src / 65536n; // >> BigInt(16); + if (rem != BigInt(0)) { + res += BigInt(1); + } + return res; +} + +export function collectCellStats( + cell: Cell, + visited: Array, + skipRoot: boolean = false, +): StorageStats { + let bits = skipRoot ? 0n : BigInt(cell.bits.length); + let cells = skipRoot ? 0n : 1n; + let hash = cell.hash().toString(); + if (visited.includes(hash)) { + // We should not account for current cell data if visited + return new StorageStats(); + } else { + visited.push(hash); + } + for (let ref of cell.refs) { + let r = collectCellStats(ref, visited); + cells += r.cells; + bits += r.bits; + } + return new StorageStats(bits, cells); +} + +export function getGasPrices(configRaw: Cell, workchain: 0 | -1): GasPrices { + const config = configRaw + .beginParse() + .loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const ds = config.get(21 + workchain)!.beginParse(); + if (ds.loadUint(8) !== 0xd1) { + throw new Error('Invalid flat gas prices tag!'); + } + + const flat_gas_limit = ds.loadUintBig(64); + const flat_gas_price = ds.loadUintBig(64); + + if (ds.loadUint(8) !== 0xde) { + throw new Error('Invalid gas prices tag!'); + } + return { + flat_gas_limit, + flat_gas_price, + gas_price: ds.preloadUintBig(64), + }; +} + +export function setGasPrice(configRaw: Cell, prices: GasPrices, workchain: 0 | -1): Cell { + const config = configRaw + .beginParse() + .loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const idx = 21 + workchain; + const ds = config.get(idx)!; + const tail = ds.beginParse().skip(8 + 64 + 64 + 8 + 64); + + const newPrices = beginCell() + .storeUint(0xd1, 8) + .storeUint(prices.flat_gas_limit, 64) + .storeUint(prices.flat_gas_price, 64) + .storeUint(0xde, 8) + .storeUint(prices.gas_price, 64) + .storeSlice(tail) + .endCell(); + config.set(idx, newPrices); + + return beginCell().storeDictDirect(config).endCell(); +} + +export const storageValue: DictionaryValue = { + serialize: (src, builder) => { + builder + .storeUint(0xcc, 8) + .storeUint(src.utime_sice, 32) + .storeUint(src.bit_price_ps, 64) + .storeUint(src.cell_price_ps, 64) + .storeUint(src.mc_bit_price_ps, 64) + .storeUint(src.mc_cell_price_ps, 64); + }, + parse: (src) => { + return { + utime_sice: src.skip(8).loadUint(32), + bit_price_ps: src.loadUintBig(64), + cell_price_ps: src.loadUintBig(64), + mc_bit_price_ps: src.loadUintBig(64), + mc_cell_price_ps: src.loadUintBig(64), + }; + }, +}; + +export function getStoragePrices(configRaw: Cell) { + const config = configRaw + .beginParse() + .loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const storageData = Dictionary.loadDirect( + Dictionary.Keys.Uint(32), + storageValue, + config.get(18)!, + ); + const values = storageData.values(); + + return values[values.length - 1]; +} +export function calcStorageFee(prices: StoragePrices, stats: StorageStats, duration: bigint) { + return shr16ceil( + (stats.bits * prices.bit_price_ps + stats.cells * prices.cell_price_ps) * duration, + ); +} +export function setStoragePrices(configRaw: Cell, prices: StoragePrices) { + const config = configRaw + .beginParse() + .loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + const storageData = Dictionary.loadDirect( + Dictionary.Keys.Uint(32), + storageValue, + config.get(18)!, + ); + storageData.set(storageData.values().length - 1, prices); + config.set(18, beginCell().storeDictDirect(storageData).endCell()); + return beginCell().storeDictDirect(config).endCell(); +} + +export function computeGasFee(prices: GasPrices, gas: bigint): bigint { + if (gas <= prices.flat_gas_limit) { + return prices.flat_gas_price; + } + return prices.flat_gas_price + (prices.gas_price * (gas - prices.flat_gas_limit)) / 65536n; +} + +export function computeDefaultForwardFee(msgPrices: MsgPrices) { + return msgPrices.lumpPrice - ((msgPrices.lumpPrice * msgPrices.firstFrac) >> BigInt(16)); +} + +export function computeCellForwardFees(msgPrices: MsgPrices, msg: Cell) { + let storageStats = collectCellStats(msg, [], true); + return computeFwdFees(msgPrices, storageStats.cells, storageStats.bits); +} +export function computeMessageForwardFees(msgPrices: MsgPrices, msg: Message) { + // let msg = loadMessageRelaxed(cell.beginParse()); + let storageStats = new StorageStats(); + + if (msg.info.type !== 'internal') { + throw Error('Helper intended for internal messages'); + } + const defaultFwd = computeDefaultForwardFee(msgPrices); + // If message forward fee matches default than msg cell is flat + if (msg.info.forwardFee == defaultFwd) { + return { + fees: { + total: msgPrices.lumpPrice, + res: msgPrices.lumpPrice - defaultFwd, + remaining: defaultFwd, + }, + stats: storageStats, + }; + } + let visited: Array = []; + // Init + if (msg.init) { + let addBits = 5n; // Minimal additional bits + let refCount = 0; + if (msg.init.splitDepth) { + addBits += 5n; + } + if (msg.init.libraries) { + refCount++; + storageStats = storageStats.add( + collectCellStats(beginCell().storeDictDirect(msg.init.libraries).endCell(), visited, true), + ); + } + if (msg.init.code) { + refCount++; + storageStats = storageStats.add(collectCellStats(msg.init.code, visited)); + } + if (msg.init.data) { + refCount++; + storageStats = storageStats.add(collectCellStats(msg.init.data, visited)); + } + if (refCount >= 2) { + //https://github.com/ton-blockchain/ton/blob/51baec48a02e5ba0106b0565410d2c2fd4665157/crypto/block/transaction.cpp#L2079 + storageStats.cells++; + storageStats.bits += addBits; + } + } + const lumpBits = BigInt(msg.body.bits.length); + const bodyStats = collectCellStats(msg.body, visited, true); + storageStats = storageStats.add(bodyStats); + + // NOTE: Extra currencies are ignored for now + let fees = computeFwdFeesVerbose( + msgPrices, + BigInt(storageStats.cells), + BigInt(storageStats.bits), + ); + // Meeh + if (fees.remaining < msg.info.forwardFee) { + // console.log(`Remaining ${fees.remaining} < ${msg.info.forwardFee} lump bits:${lumpBits}`); + storageStats = storageStats.addCells(1).addBits(lumpBits); + fees = computeFwdFeesVerbose(msgPrices, storageStats.cells, storageStats.bits); + } + if (fees.remaining != msg.info.forwardFee) { + console.log('Result fees:', fees); + console.log(msg); + console.log(fees.remaining); + throw new Error('Something went wrong in fee calcuation!'); + } + return { fees, stats: storageStats }; +} + +export const configParseMsgPrices = (sc: Slice) => { + let magic = sc.loadUint(8); + + if (magic != 0xea) { + throw Error('Invalid message prices magic number!'); + } + return { + lumpPrice: sc.loadUintBig(64), + bitPrice: sc.loadUintBig(64), + cellPrice: sc.loadUintBig(64), + ihrPriceFactor: sc.loadUintBig(32), + firstFrac: sc.loadUintBig(16), + nextFrac: sc.loadUintBig(16), + }; +}; + +export const setMsgPrices = (configRaw: Cell, prices: MsgPrices, workchain: 0 | -1) => { + const config = configRaw + .beginParse() + .loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const priceCell = beginCell() + .storeUint(0xea, 8) + .storeUint(prices.lumpPrice, 64) + .storeUint(prices.bitPrice, 64) + .storeUint(prices.cellPrice, 64) + .storeUint(prices.ihrPriceFactor, 32) + .storeUint(prices.firstFrac, 16) + .storeUint(prices.nextFrac, 16) + .endCell(); + config.set(25 + workchain, priceCell); + + return beginCell().storeDictDirect(config).endCell(); +}; + +export const getMsgPrices = (configRaw: Cell, workchain: 0 | -1) => { + const config = configRaw + .beginParse() + .loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); + + const prices = config.get(25 + workchain); + + if (prices === undefined) { + throw Error('No prices defined in config'); + } + + return configParseMsgPrices(prices.beginParse()); +}; + +export function computeFwdFees(msgPrices: MsgPrices, cells: bigint, bits: bigint) { + return msgPrices.lumpPrice + shr16ceil(msgPrices.bitPrice * bits + msgPrices.cellPrice * cells); +} + +export function computeFwdFeesVerbose( + msgPrices: MsgPrices, + cells: bigint | number, + bits: bigint | number, +) { + const fees = computeFwdFees(msgPrices, BigInt(cells), BigInt(bits)); + + const res = (fees * msgPrices.firstFrac) >> 16n; + return { + total: fees, + res, + remaining: fees - res, + }; +} diff --git a/standard/wallets/comparison/tests/utils/gasUtils.ts b/standard/wallets/comparison/tests/utils/gasUtils.ts deleted file mode 100644 index 599ecc9..0000000 --- a/standard/wallets/comparison/tests/utils/gasUtils.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { Cell, Slice, toNano, beginCell, Address, Dictionary, Message, DictionaryValue, Transaction } from '@ton/core'; - -export type GasPrices = { - flat_gas_limit: bigint, - flat_gas_price: bigint, - gas_price: bigint; -}; -export type StoragePrices = { - utime_sice: number, - bit_price_ps: bigint, - cell_price_ps: bigint, - mc_bit_price_ps: bigint, - mc_cell_price_ps: bigint -}; - - -export type MsgPrices = ReturnType; -export type FullFees = ReturnType; - -export class StorageStats { - bits: bigint; - cells: bigint; - - constructor(bits?: number | bigint, cells?: number | bigint) { - this.bits = bits !== undefined ? BigInt(bits) : 0n; - this.cells = cells !== undefined ? BigInt(cells) : 0n; - } - add(...stats: StorageStats[]) { - let cells = this.cells, bits = this.bits; - for (let stat of stats) { - bits += stat.bits; - cells += stat.cells; - } - return new StorageStats(bits, cells); - } - sub(...stats: StorageStats[]) { - let cells = this.cells, bits = this.bits; - for (let stat of stats) { - bits -= stat.bits; - cells -= stat.cells; - } - return new StorageStats(bits, cells); - } - addBits(bits: number | bigint) { - return new StorageStats(this.bits + BigInt(bits), this.cells); - } - subBits(bits: number | bigint) { - return new StorageStats(this.bits - BigInt(bits), this.cells); - } - addCells(cells: number | bigint) { - return new StorageStats(this.bits, this.cells + BigInt(cells)); - } - subCells(cells: number | bigint) { - return new StorageStats(this.bits, this.cells - BigInt(cells)); - } - - toString(): string { - return JSON.stringify({ - bits: this.bits.toString(), - cells: this.cells.toString() - }); - } -} - -export function computedGeneric(transaction: T) { - if (transaction.description.type !== "generic") - throw new Error("Expected generic transactionaction"); - if (transaction.description.computePhase.type !== "vm") - throw new Error("Compute phase expected") - return transaction.description.computePhase; -} - -export function storageGeneric(transaction: T) { - if (transaction.description.type !== "generic") - throw new Error("Expected generic transactionaction"); - const storagePhase = transaction.description.storagePhase; - if (storagePhase === null || storagePhase === undefined) - throw new Error("Storage phase expected") - return storagePhase; -} -export function getFwdStats(transaction: T) { - if (transaction.description.type !== "generic") - throw new Error("Expected generic transaction"); - if (transaction.description.actionPhase == undefined) - throw new Error("Action phase expected"); - const actionMsgSize = transaction.description.actionPhase.totalMessageSize; - return new StorageStats(actionMsgSize.bits, actionMsgSize.cells); -} - -function shr16ceil(src: bigint) { - let rem = src % BigInt(65536); - let res = src / 65536n; // >> BigInt(16); - if (rem != BigInt(0)) { - res += BigInt(1); - } - return res; -} - -export function collectCellStats(cell: Cell, visited: Array, skipRoot: boolean = false): StorageStats { - let bits = skipRoot ? 0n : BigInt(cell.bits.length); - let cells = skipRoot ? 0n : 1n; - let hash = cell.hash().toString(); - if (visited.includes(hash)) { - // We should not account for current cell data if visited - return new StorageStats(); - } - else { - visited.push(hash); - } - for (let ref of cell.refs) { - let r = collectCellStats(ref, visited); - cells += r.cells; - bits += r.bits; - } - return new StorageStats(bits, cells); -} - -export function getGasPrices(configRaw: Cell, workchain: 0 | -1): GasPrices { - const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); - - const ds = config.get(21 + workchain)!.beginParse(); - if (ds.loadUint(8) !== 0xd1) { - throw new Error("Invalid flat gas prices tag!"); - } - - const flat_gas_limit = ds.loadUintBig(64); - const flat_gas_price = ds.loadUintBig(64); - - if (ds.loadUint(8) !== 0xde) { - throw new Error("Invalid gas prices tag!"); - } - return { - flat_gas_limit, - flat_gas_price, - gas_price: ds.preloadUintBig(64) - }; -} - -export function setGasPrice(configRaw: Cell, prices: GasPrices, workchain: 0 | -1): Cell { - const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); - const idx = 21 + workchain; - const ds = config.get(idx)!; - const tail = ds.beginParse().skip(8 + 64 + 64 + 8 + 64); - - const newPrices = beginCell().storeUint(0xd1, 8) - .storeUint(prices.flat_gas_limit, 64) - .storeUint(prices.flat_gas_price, 64) - .storeUint(0xde, 8) - .storeUint(prices.gas_price, 64) - .storeSlice(tail) - .endCell(); - config.set(idx, newPrices); - - return beginCell().storeDictDirect(config).endCell(); -} - -export const storageValue: DictionaryValue = { - serialize: (src, builder) => { - builder.storeUint(0xcc, 8) - .storeUint(src.utime_sice, 32) - .storeUint(src.bit_price_ps, 64) - .storeUint(src.cell_price_ps, 64) - .storeUint(src.mc_bit_price_ps, 64) - .storeUint(src.mc_cell_price_ps, 64) - }, - parse: (src) => { - return { - utime_sice: src.skip(8).loadUint(32), - bit_price_ps: src.loadUintBig(64), - cell_price_ps: src.loadUintBig(64), - mc_bit_price_ps: src.loadUintBig(64), - mc_cell_price_ps: src.loadUintBig(64) - }; - } -}; - -export function getStoragePrices(configRaw: Cell) { - const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); - const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32), storageValue, config.get(18)!); - const values = storageData.values(); - - return values[values.length - 1]; -} -export function calcStorageFee(prices: StoragePrices, stats: StorageStats, duration: bigint) { - return shr16ceil((stats.bits * prices.bit_price_ps + stats.cells * prices.cell_price_ps) * duration) -} -export function setStoragePrices(configRaw: Cell, prices: StoragePrices) { - const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); - const storageData = Dictionary.loadDirect(Dictionary.Keys.Uint(32), storageValue, config.get(18)!); - storageData.set(storageData.values().length - 1, prices); - config.set(18, beginCell().storeDictDirect(storageData).endCell()); - return beginCell().storeDictDirect(config).endCell(); -} - -export function computeGasFee(prices: GasPrices, gas: bigint): bigint { - if (gas <= prices.flat_gas_limit) { - return prices.flat_gas_price; - } - return prices.flat_gas_price + prices.gas_price * (gas - prices.flat_gas_limit) / 65536n -} - -export function computeDefaultForwardFee(msgPrices: MsgPrices) { - return msgPrices.lumpPrice - ((msgPrices.lumpPrice * msgPrices.firstFrac) >> BigInt(16)); -} - -export function computeCellForwardFees(msgPrices: MsgPrices, msg: Cell) { - let storageStats = collectCellStats(msg, [], true); - return computeFwdFees(msgPrices, storageStats.cells, storageStats.bits); -} -export function computeMessageForwardFees(msgPrices: MsgPrices, msg: Message) { - // let msg = loadMessageRelaxed(cell.beginParse()); - let storageStats = new StorageStats(); - - if (msg.info.type !== "internal") { - throw Error("Helper intended for internal messages"); - } - const defaultFwd = computeDefaultForwardFee(msgPrices); - // If message forward fee matches default than msg cell is flat - if (msg.info.forwardFee == defaultFwd) { - return { fees: { total: msgPrices.lumpPrice, res: msgPrices.lumpPrice - defaultFwd, remaining: defaultFwd }, stats: storageStats }; - } - let visited: Array = []; - // Init - if (msg.init) { - let addBits = 5n; // Minimal additional bits - let refCount = 0; - if (msg.init.splitDepth) { - addBits += 5n; - } - if (msg.init.libraries) { - refCount++; - storageStats = storageStats.add(collectCellStats(beginCell().storeDictDirect(msg.init.libraries).endCell(), visited, true)); - } - if (msg.init.code) { - refCount++; - storageStats = storageStats.add(collectCellStats(msg.init.code, visited)) - } - if (msg.init.data) { - refCount++; - storageStats = storageStats.add(collectCellStats(msg.init.data, visited)); - } - if (refCount >= 2) { //https://github.com/ton-blockchain/ton/blob/51baec48a02e5ba0106b0565410d2c2fd4665157/crypto/block/transaction.cpp#L2079 - storageStats.cells++; - storageStats.bits += addBits; - } - } - const lumpBits = BigInt(msg.body.bits.length); - const bodyStats = collectCellStats(msg.body, visited, true); - storageStats = storageStats.add(bodyStats); - - // NOTE: Extra currencies are ignored for now - let fees = computeFwdFeesVerbose(msgPrices, BigInt(storageStats.cells), BigInt(storageStats.bits)); - // Meeh - if (fees.remaining < msg.info.forwardFee) { - // console.log(`Remaining ${fees.remaining} < ${msg.info.forwardFee} lump bits:${lumpBits}`); - storageStats = storageStats.addCells(1).addBits(lumpBits); - fees = computeFwdFeesVerbose(msgPrices, storageStats.cells, storageStats.bits); - } - if (fees.remaining != msg.info.forwardFee) { - console.log("Result fees:", fees); - console.log(msg); - console.log(fees.remaining); - throw (new Error("Something went wrong in fee calcuation!")); - } - return { fees, stats: storageStats }; -} - -export const configParseMsgPrices = (sc: Slice) => { - - let magic = sc.loadUint(8); - - if (magic != 0xea) { - throw Error("Invalid message prices magic number!"); - } - return { - lumpPrice: sc.loadUintBig(64), - bitPrice: sc.loadUintBig(64), - cellPrice: sc.loadUintBig(64), - ihrPriceFactor: sc.loadUintBig(32), - firstFrac: sc.loadUintBig(16), - nextFrac: sc.loadUintBig(16) - }; -} - -export const setMsgPrices = (configRaw: Cell, prices: MsgPrices, workchain: 0 | -1) => { - const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); - - const priceCell = beginCell().storeUint(0xea, 8) - .storeUint(prices.lumpPrice, 64) - .storeUint(prices.bitPrice, 64) - .storeUint(prices.cellPrice, 64) - .storeUint(prices.ihrPriceFactor, 32) - .storeUint(prices.firstFrac, 16) - .storeUint(prices.nextFrac, 16) - .endCell(); - config.set(25 + workchain, priceCell); - - return beginCell().storeDictDirect(config).endCell(); -} - -export const getMsgPrices = (configRaw: Cell, workchain: 0 | -1) => { - - const config = configRaw.beginParse().loadDictDirect(Dictionary.Keys.Int(32), Dictionary.Values.Cell()); - - const prices = config.get(25 + workchain); - - if (prices === undefined) { - throw Error("No prices defined in config"); - } - - return configParseMsgPrices(prices.beginParse()); -} - -export function computeFwdFees(msgPrices: MsgPrices, cells: bigint, bits: bigint) { - return msgPrices.lumpPrice + (shr16ceil((msgPrices.bitPrice * bits) - + (msgPrices.cellPrice * cells)) - ); -} - -export function computeFwdFeesVerbose(msgPrices: MsgPrices, cells: bigint | number, bits: bigint | number) { - const fees = computeFwdFees(msgPrices, BigInt(cells), BigInt(bits)); - - const res = (fees * msgPrices.firstFrac) >> 16n; - return { - total: fees, - res, - remaining: fees - res - } -} \ No newline at end of file diff --git a/standard/wallets/comparison/tests/wallet-fee-comparison.spec.ts b/standard/wallets/comparison/tests/wallet-fee-comparison.spec.ts new file mode 100644 index 0000000..3692bbb --- /dev/null +++ b/standard/wallets/comparison/tests/wallet-fee-comparison.spec.ts @@ -0,0 +1,183 @@ +import { Cell, beginCell } from '@ton/core'; +import { randomAddress } from '@ton/test-utils'; +import { + MessageBodyResolver, + runAllMeasurements, + WALLET_CONFIGS, + DEFAULT_CONSTANTS, +} from './get-results'; +import { printTables } from './print-tables'; + +type MessageBodyConfig = { + name: string; + resolveBody: MessageBodyResolver; +}; + +type TestRunConfig = { + messageCount: number; + bodyResolver: MessageBodyResolver; + bodyName: string; +}; + +type WalletKey = + | 'v2r1' + | 'v2r2' + | 'v3r1' + | 'v3r2' + | 'v4r2' + | 'v5r1' + | 'preprocessedV2' + | 'highloadV3'; + +type EnabledWallets = Record; + +type ColumnAlignment = 'left' | 'center' | 'right'; + +type ColumnConfig = { + key: string; + header: string; + alignment: ColumnAlignment; + enabled: boolean; +}; + +type Config = { + requestTimings: { + realSeconds: number; + theoreticalSeconds: number; + }; + messageCounts: number[]; + messageBodyVariants: MessageBodyConfig[]; + enabledWallets: EnabledWallets; + columnOrder: ColumnConfig[]; + testRuns: TestRunConfig[]; + outputDirectory: string; +}; + +function buildTestRuns(bodyVariants: MessageBodyConfig[], counts: number[]): TestRunConfig[] { + return bodyVariants.flatMap((variant) => + counts.map((messageCount) => ({ + messageCount, + bodyResolver: variant.resolveBody, + bodyName: variant.name, + })), + ); +} + +function commentBodyResolver(messageIndex: number): Cell { + return beginCell().storeUint(0, 32).storeStringTail(randomString(12, messageIndex)).endCell(); +} + +function jettonBodyResolver(messageIndex: number): Cell { + return beginCell() + .storeUint(0xf8a7ea5, 32) + .storeUint(messageIndex, 64) + .storeCoins(1) + .storeAddress(randomAddress()) + .storeAddress(randomAddress()) + .storeMaybeRef(null) + .storeCoins(0) + .storeMaybeRef(commentBodyResolver(messageIndex)) + .endCell(); +} + +function randomString(size: number, seed: number): string { + return generateSeededString(seed, size); +} + +function generateSeededString( + seed: number, + size: number, + characterSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', +): string { + const randomFunc = mulberry32(seed); + + let result = ''; + for (let i = 0; i < size; i++) { + const randomIndex = Math.floor(randomFunc() * characterSet.length); + result += characterSet.charAt(randomIndex); + } + return result; +} + +function mulberry32(seed: number): () => number { + return function () { + let t = (seed += 0x6d2b79f5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +const CONFIG: Config = (() => { + const messageCounts = [1, 4, 200, 1000]; + const messageBodyVariants: MessageBodyConfig[] = [ + { name: 'Sending TONs', resolveBody: () => Cell.EMPTY }, + { name: 'Sending Comment', resolveBody: commentBodyResolver }, + { name: 'Sending Jettons', resolveBody: jettonBodyResolver }, + ]; + + const enabledWallets = { + v2r1: true, + v2r2: true, + v3r1: true, + v3r2: true, + v4r2: true, + v5r1: true, + preprocessedV2: true, + highloadV3: true, + } satisfies EnabledWallets; + + const columnOrder: ColumnConfig[] = [ + { key: 'walletVersion', header: 'Wallet Version', alignment: 'center', enabled: true }, + { key: 'gasDelta', header: 'Gas delta %', alignment: 'right', enabled: true }, + { key: 'feeDelta', header: 'Fee delta %', alignment: 'right', enabled: true }, + { key: 'requests', header: 'Requests', alignment: 'right', enabled: true }, + { key: 'totalGas', header: 'Total Gas', alignment: 'right', enabled: true }, + { key: 'gasPerMsg', header: 'Gas/Msg', alignment: 'right', enabled: true }, + { key: 'totalFee', header: 'Total Fee (TON)', alignment: 'right', enabled: true }, + { key: 'feePerMsg', header: 'Fee/Msg (TON)', alignment: 'right', enabled: true }, + { key: 'realTime', header: 'Real Time (s)', alignment: 'right', enabled: true }, + { key: 'theoryTime', header: 'Theory Time (s)', alignment: 'right', enabled: true }, + ]; + + return { + requestTimings: { + realSeconds: 13, + theoreticalSeconds: 4, + }, + messageCounts, + messageBodyVariants, + enabledWallets, + columnOrder, + testRuns: buildTestRuns(messageBodyVariants, messageCounts), + outputDirectory: __dirname, + }; +})(); + +describe('Wallet Fee Comparison', () => { + it('Run all wallet measurements', async () => { + const allResults = await runAllMeasurements({ + enabledWallets: CONFIG.enabledWallets, + testRuns: CONFIG.testRuns, + constants: DEFAULT_CONSTANTS, + }); + + const walletNames = WALLET_CONFIGS.reduce( + (acc, config) => { + acc[config.key] = config.name; + return acc; + }, + {} as Record, + ); + + await printTables({ + allResults, + testRuns: CONFIG.testRuns, + columnOrder: CONFIG.columnOrder, + requestTimings: CONFIG.requestTimings, + highloadWalletName: walletNames['highloadV3'], + preprocessedWalletName: walletNames['preprocessedV2'], + outputDirectory: CONFIG.outputDirectory, + }); + }); +}); diff --git a/standard/wallets/comparison/tsconfig.json b/standard/wallets/comparison/tsconfig.json index c6f6c22..f74721e 100644 --- a/standard/wallets/comparison/tsconfig.json +++ b/standard/wallets/comparison/tsconfig.json @@ -1,29 +1,20 @@ { - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": [ - "ES2020" - ], - "outDir": "./dist", - "rootDir": "./", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "moduleResolution": "node" - }, - "include": [ - "wrappers/**/*", - "tests/**/*", - "scripts/**/*" - ], - "exclude": [ - "node_modules", - "dist" - ] -} \ No newline at end of file + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node" + }, + "include": ["wrappers/**/*", "tests/**/*", "scripts/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/standard/wallets/comparison/utils.ts b/standard/wallets/comparison/utils.ts index bfaca13..c63606d 100644 --- a/standard/wallets/comparison/utils.ts +++ b/standard/wallets/comparison/utils.ts @@ -1,7 +1,7 @@ -const getRandom = (min:number, max:number) => { - return Math.random() * (max - min) + min; -} +const getRandom = (min: number, max: number) => { + return Math.random() * (max - min) + min; +}; export const getRandomInt = (min: number, max: number) => { - return Math.round(getRandom(min, max)); -} + return Math.round(getRandom(min, max)); +}; diff --git a/standard/wallets/comparison/wrappers/HighloadQueryId.ts b/standard/wallets/comparison/wrappers/HighloadQueryId.ts deleted file mode 100644 index 4c994c2..0000000 --- a/standard/wallets/comparison/wrappers/HighloadQueryId.ts +++ /dev/null @@ -1,81 +0,0 @@ -const BIT_NUMBER_SIZE = 10n; // 10 bit -const SHIFT_SIZE = 13n; // 13 bit -const MAX_BIT_NUMBER = 1022n; -const MAX_SHIFT = 8191n; // 2^13 = 8192 - -export class HighloadQueryId { - private shift: bigint; // [0 .. 8191] - private bitnumber: bigint; // [0 .. 1022] - - constructor() { - this.shift = 0n; - this.bitnumber = 0n; - } - - static fromShiftAndBitNumber(shift: bigint, bitnumber: bigint): HighloadQueryId { - const q = new HighloadQueryId(); - q.shift = shift; - if (q.shift < 0) throw new Error('invalid shift'); - if (q.shift > MAX_SHIFT) throw new Error('invalid shift'); - q.bitnumber = bitnumber; - if (q.bitnumber < 0) throw new Error('invalid bitnumber'); - if (q.bitnumber > MAX_BIT_NUMBER) throw new Error('invalid bitnumber'); - return q; - } - - - getNext() { - let newBitnumber = this.bitnumber + 1n; - let newShift = this.shift; - - if (newShift === MAX_SHIFT && newBitnumber > (MAX_BIT_NUMBER - 1n)) { - throw new Error('Overload'); // NOTE: we left one queryId for emergency withdraw - } - - if (newBitnumber > MAX_BIT_NUMBER) { - newBitnumber = 0n; - newShift += 1n; - if (newShift > MAX_SHIFT) { - throw new Error('Overload') - } - } - - return HighloadQueryId.fromShiftAndBitNumber(newShift, newBitnumber); - } - - hasNext() { - const isEnd = this.bitnumber >= (MAX_BIT_NUMBER - 1n) && this.shift === MAX_SHIFT; // NOTE: we left one queryId for emergency withdraw; - return !isEnd; - } - - getShift(): bigint { - return this.shift; - } - - getBitNumber(): bigint { - return this.bitnumber; - } - - getQueryId(): bigint { - return (this.shift << BIT_NUMBER_SIZE) + this.bitnumber; - } - - static fromQueryId(queryId: bigint): HighloadQueryId { - const shift = queryId >> BIT_NUMBER_SIZE; - const bitnumber = queryId & 1023n; - return this.fromShiftAndBitNumber(shift, bitnumber); - } - - static fromSeqno(i: bigint): HighloadQueryId { - const shift = i / 1023n; - const bitnumber = i % 1023n; - return this.fromShiftAndBitNumber(shift, bitnumber); - } - - /** - * @return {bigint} [0 .. 8380415] - */ - toSeqno(): bigint { - return this.bitnumber + this.shift * 1023n; - } -} \ No newline at end of file diff --git a/standard/wallets/comparison/wrappers/HighloadWalletV3.ts b/standard/wallets/comparison/wrappers/HighloadWalletV3.ts deleted file mode 100644 index 005fae6..0000000 --- a/standard/wallets/comparison/wrappers/HighloadWalletV3.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { - Address, - beginCell, - Cell, - Contract, - contractAddress, - ContractProvider, - internal as internal_relaxed, - MessageRelaxed, - OutAction, - OutActionSendMsg, - Sender, - SendMode, - storeMessageRelaxed, - storeOutList, - toNano -} from '@ton/core'; -import { sign } from "@ton/crypto"; -import { OP } from "../tests/imports/const"; -import { HighloadQueryId } from "./HighloadQueryId"; - -const HighloadWalletV3CodeHex = "b5ee9c7241021001000228000114ff00f4a413f4bcf2c80b01020120020d02014803040078d020d74bc00101c060b0915be101d0d3030171b0915be0fa4030f828c705b39130e0d31f018210ae42e5a4ba9d8040d721d74cf82a01ed55fb04e030020120050a02027306070011adce76a2686b85ffc00201200809001aabb6ed44d0810122d721d70b3f0018aa3bed44d08307d721d70b1f0201200b0c001bb9a6eed44d0810162d721d70b15800e5b8bf2eda2edfb21ab09028409b0ed44d0810120d721f404f404d33fd315d1058e1bf82325a15210b99f326df82305aa0015a112b992306dde923033e2923033e25230800df40f6fa19ed021d721d70a00955f037fdb31e09130e259800df40f6fa19cd001d721d70a00937fdb31e0915be270801f6f2d48308d718d121f900ed44d0d3ffd31ff404f404d33fd315d1f82321a15220b98e12336df82324aa00a112b9926d32de58f82301de541675f910f2a106d0d31fd4d307d30cd309d33fd315d15168baf2a2515abaf2a6f8232aa15250bcf2a304f823bbf2a35304800df40f6fa199d024d721d70a00f2649130e20e01fe5309800df40f6fa18e13d05004d718d20001f264c858cf16cf8301cf168e1030c824cf40cf8384095005a1a514cf40e2f800c94039800df41704c8cbff13cb1ff40012f40012cb3f12cb15c9ed54f80f21d0d30001f265d3020171b0925f03e0fa4001d70b01c000f2a5fa4031fa0031f401fa0031fa00318060d721d300010f0020f265d2000193d431d19130e272b1fb00b585bf03"; - -export const HighloadWalletV3Code = Cell.fromBoc(Buffer.from(HighloadWalletV3CodeHex, "hex"))[0] - -export type HighloadWalletV3Config = { - publicKey: Buffer, - subwalletId: number, - timeout: number -}; - - -export const TIMESTAMP_SIZE = 64; -export const TIMEOUT_SIZE = 22; - -export function highloadWalletV3ConfigToCell(config: HighloadWalletV3Config): Cell { - return beginCell() - .storeBuffer(config.publicKey) - .storeUint(config.subwalletId, 32) - .storeUint(0, 1 + 1 + TIMESTAMP_SIZE) - .storeUint(config.timeout, TIMEOUT_SIZE) - .endCell(); -} - -export class HighloadWalletV3 implements Contract { - - constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) { - } - - static createFromAddress(address: Address) { - return new HighloadWalletV3(address); - } - - static createFromConfig(config: HighloadWalletV3Config, code: Cell, workchain = 0) { - const data = highloadWalletV3ConfigToCell(config); - const init = { code, data }; - return new HighloadWalletV3(contractAddress(workchain, init), init); - } - - async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { - await provider.internal(via, { - value, - bounce: false, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: beginCell().endCell(), - }); - } - - async sendExternalMessage( - provider: ContractProvider, - secretKey: Buffer, - opts: { - message: MessageRelaxed | Cell, - mode: number, - query_id: bigint | HighloadQueryId, - createdAt: number, - subwalletId: number, - timeout: number, - } - ) { - let messageCell: Cell; - - if (opts.message instanceof Cell) { - messageCell = opts.message - } else { - const messageBuilder = beginCell(); - messageBuilder.store(storeMessageRelaxed(opts.message)) - messageCell = messageBuilder.endCell(); - } - - const queryId = (opts.query_id instanceof HighloadQueryId) ? opts.query_id.getQueryId() : opts.query_id; - - const messageInner = beginCell() - .storeUint(opts.subwalletId, 32) - .storeRef(messageCell) - .storeUint(opts.mode, 8) - .storeUint(queryId, 23) - .storeUint(opts.createdAt, TIMESTAMP_SIZE) - .storeUint(opts.timeout, TIMEOUT_SIZE) - .endCell(); - - await provider.external( - beginCell() - .storeBuffer(sign(messageInner.hash(), secretKey)) - .storeRef(messageInner) - .endCell() - ); - } - - async sendBatch(provider: ContractProvider, secretKey: Buffer, messages: OutActionSendMsg[], subwallet: number, query_id: HighloadQueryId, timeout: number, createdAt?: number, value: bigint = 0n) { - if (createdAt == undefined) { - createdAt = Math.floor(Date.now() / 1000); - } - return await this.sendExternalMessage(provider, secretKey, { - message: this.packActions(messages, value, query_id), - mode: value > 0n ? SendMode.PAY_GAS_SEPARATELY : SendMode.CARRY_ALL_REMAINING_BALANCE, - query_id: query_id, - createdAt: createdAt, - subwalletId: subwallet, - timeout: timeout - }); - } - - static createInternalTransferBody(opts: { - actions: OutAction[] | Cell, - queryId: HighloadQueryId, - }) { - let actionsCell: Cell; - if (opts.actions instanceof Cell) { - actionsCell = opts.actions; - } else { - if (opts.actions.length > 254) { - throw TypeError("Max allowed action count is 254. Use packActions instead."); - } - const actionsBuilder = beginCell(); - storeOutList(opts.actions)(actionsBuilder); - actionsCell = actionsBuilder.endCell(); - } - return beginCell().storeUint(OP.InternalTransfer, 32) - .storeUint(opts.queryId.getQueryId(), 64) - .storeRef(actionsCell) - .endCell(); - - - } - - createInternalTransfer(opts: { - actions: OutAction[] | Cell - queryId: HighloadQueryId, - value: bigint - }) { - - return internal_relaxed({ - to: this.address, - value: opts.value, - body: HighloadWalletV3.createInternalTransferBody(opts) - }); - /*beginCell() - .storeUint(0x10, 6) - .storeAddress(this.address) - .storeCoins(opts.value) - .storeUint(0, 107) - .storeSlice(body.asSlice()) - .endCell(); - */ - } - - packActions(messages: OutAction[], value: bigint = toNano('1'), query_id: HighloadQueryId) { - let batch: OutAction[]; - if (messages.length > 254) { - batch = messages.slice(0, 253); - batch.push({ - type: 'sendMsg', - mode: value > 0n ? SendMode.PAY_GAS_SEPARATELY : SendMode.CARRY_ALL_REMAINING_BALANCE, - outMsg: this.packActions(messages.slice(253), value, query_id) - }); - } else { - batch = messages; - } - return this.createInternalTransfer({ - actions: batch, - queryId: query_id, - value - }); - } - - - async getPublicKey(provider: ContractProvider): Promise { - const res = (await provider.get('get_public_key', [])).stack; - const pubKeyU = res.readBigNumber(); - return Buffer.from(pubKeyU.toString(16).padStart(32 * 2, '0'), 'hex'); - } - - async getSubwalletId(provider: ContractProvider): Promise { - const res = (await provider.get('get_subwallet_id', [])).stack; - return res.readNumber(); - } - - async getTimeout(provider: ContractProvider): Promise { - const res = (await provider.get('get_timeout', [])).stack; - return res.readNumber(); - } - - async getLastCleaned(provider: ContractProvider): Promise { - const res = (await provider.get('get_last_clean_time', [])).stack; - return res.readNumber(); - } - - async getProcessed(provider: ContractProvider, queryId: HighloadQueryId, needClean = true): Promise { - const res = (await provider.get('processed?', [{ 'type': 'int', 'value': queryId.getQueryId() }, { - 'type': 'int', - 'value': needClean ? -1n : 0n - }])).stack; - return res.readBoolean(); - } -} diff --git a/standard/wallets/comparison/wrappers/MsgGenerator.ts b/standard/wallets/comparison/wrappers/MsgGenerator.ts deleted file mode 100644 index 8618587..0000000 --- a/standard/wallets/comparison/wrappers/MsgGenerator.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Cell, CommonMessageInfoExternalIn, CommonMessageInfoExternalOut, ExternalAddress, Message, MessageRelaxed, StateInit, beginCell, external, storeMessage, storeMessageRelaxed } from '@ton/core'; -import { randomAddress } from '@ton/test-utils'; -export class MsgGenerator { - constructor(readonly wc: number){} - - generateExternalOutWithBadSource() { - const ssrcInvalid = beginCell() - .storeUint(2, 2) // addr_std$10 - .storeUint(0, 1) // anycast nothing - .storeInt(this.wc, 8) // workchain_id: -1 - .storeUint(1, 10) - .endCell() - - return beginCell() - .storeUint(3, 2) // ext_out_msg_info$11 - .storeBit(0) // src:INVALID - .storeSlice(ssrcInvalid.beginParse()) - .endCell(); - - } - generateExternalOutWithBadDst() { - const src = randomAddress(-1); - return beginCell() - .storeUint(3, 2) // ext_out_msg_info$11 - .storeAddress(src) // src:MsgAddressInt - .storeBit(0) // dest:INVALID - .endCell(); - } - generateExternalInWithBadSource() { - const ssrcInvalid = beginCell() - .storeUint(1, 2) // addrExtern$01 - .storeUint(128, 9) - .storeUint(0, 10) - .endCell() - - return beginCell() - .storeUint(2, 2) //ext_in_msg_info$11 - .storeSlice(ssrcInvalid.beginParse()) // src:INVALID - .endCell(); - } - generateExternalInWithBadDst() { - const src = new ExternalAddress(BigInt(Date.now()), 256); - return beginCell() - .storeUint(2, 2) //ext_in_msg_info$10 - .storeAddress(src) // src:MsgAddressExt - .storeBit(0) // dest:INVALID - .endCell(); - } - generateInternalMessageWithBadGrams() { - const src = randomAddress(this.wc); - const dst = randomAddress(this.wc); - return beginCell() - .storeUint(0, 1) // int_msg_info$0 - .storeUint(0, 1) // ihr_disabled:Bool - .storeUint(0, 1) // bounce:Bool - .storeUint(0, 1) // bounced:Bool - .storeAddress(src) // src:MsgAddress - .storeAddress(dst) // dest:MsgAddress - .storeUint(8, 4) // len of nanograms - .storeUint(1, 1) // INVALID GRAMS amount - .endCell(); - - } - generateInternalMessageWithBadInitStateData() { - const ssrc = randomAddress(this.wc); - const sdest = randomAddress(this.wc); - - const init_state_with_bad_data = beginCell().storeUint(0, 1) // maybe (##5) - .storeUint(1, 1) // Maybe TickTock - .storeUint(1, 1) // bool Tick - .storeUint(0, 1) // bool Tock - .storeUint(1, 1) // code: Maybe Cell^ - .storeUint(1, 1) // data: Maybe Cell^ - .storeUint(1, 1); // library: Maybe ^Cell - // bits for references but no data - - return beginCell() - .storeUint(0, 1) // int_msg_info$0 - .storeUint(0, 1) // ihr_disabled:Bool - .storeUint(0, 1) // bounce:Bool - .storeUint(0, 1) // bounced:Bool - .storeAddress(ssrc) // src:MsgAddress - .storeAddress(sdest) // dest:MsgAddress - .storeCoins(0) // - .storeMaybeRef(null) // extra currencies - .storeCoins(0) // ihr_fee - .storeCoins(0) // fwd_fee - .storeUint(1000, 64) // created_lt:uint64 - .storeUint(1000, 32) // created_at:uint32 - .storeUint(1, 1) // Maybe init_state - .storeUint(1, 1) // Either (X ^X) init state - .storeRef(init_state_with_bad_data.endCell()) - .storeUint(0, 1) // Either (X ^X) body - .endCell(); -} - - *generateBadMsg() { - // Meh - yield this.generateExternalInWithBadDst(); - yield this.generateExternalOutWithBadDst(); - yield this.generateExternalInWithBadSource(); - yield this.generateExternalOutWithBadSource(); - yield this.generateInternalMessageWithBadGrams(); - yield this.generateInternalMessageWithBadInitStateData(); - } - generateExternalInMsg(info?: Partial, body?: Cell, init?: StateInit) { - const msgInfo: CommonMessageInfoExternalIn = { - type: 'external-in', - dest: info?.dest || randomAddress(this.wc), - src: info?.src, - importFee: info?.importFee || 0n - } - const newMsg: Message = { - info: msgInfo, - body: body || Cell.EMPTY, - init - } - return beginCell().store(storeMessage(newMsg)).endCell(); - } - generateExternalOutMsg(info?: Partial, body?: Cell) { - const msgInfo: CommonMessageInfoExternalOut = { - type: 'external-out', - createdAt: info?.createdAt || 0, - createdLt: info?.createdLt || 0n, - src: info?.src || randomAddress(this.wc), - dest: info?.dest - } - const newMsg: MessageRelaxed = { - info: msgInfo, - body: body || Cell.EMPTY, - } - return beginCell().store(storeMessageRelaxed(newMsg)).endCell(); - } -} diff --git a/standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts b/standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts deleted file mode 100644 index d79365e..0000000 --- a/standard/wallets/comparison/wrappers/PreprocessedWalletV2.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { - Address, - beginCell, - Cell, - Contract, - contractAddress, - ContractProvider, - Sender, - SendMode, - Slice, - StateInit, - storeStateInit, - OutAction, - OutActionSendMsg, - OutActionSetCode, - storeOutList, -} from '@ton/core'; -import { KeyPair, sign } from '@ton/crypto'; - -const MAX_ACTIONS = 255; -const DEFAULT_VALID_UNTIL_OFFSET = 60; - - -export const walletCode = Cell.fromBoc( - Buffer.from( - 'B5EE9C7241010101003D000076FF00DDD40120F90001D0D33FD30FD74CED44D0D3FFD70B0F20A4830FA90822C8CBFFCB0FC9ED5444301046BAF2A1F823BEF2A2F910F2A3F800ED552E766412', - 'hex' - ) -)[0]; - -export type TransferMessage = { - to: Address; - value: bigint; - body?: Cell; - mode?: SendMode; - bounce?: boolean; - init?: StateInit; -}; - -export function createTransferAction(msg: TransferMessage): OutActionSendMsg { - const bounce = msg.bounce ?? true; - - return { - type: 'sendMsg', - mode: msg.mode ?? SendMode.PAY_GAS_SEPARATELY, - outMsg: { - info: { - type: 'internal', - ihrDisabled: true, - bounce: bounce, - bounced: false, - dest: msg.to, - value: { coins: msg.value }, - ihrFee: 0n, - forwardFee: 0n, - createdLt: 0n, - createdAt: 0 - }, - init: msg.init, - body: msg.body || Cell.EMPTY - } - }; -} - -export function createSetCodeAction(code: Cell): OutActionSetCode { - return { - type: 'setCode', - newCode: code - }; -} - - -export class Wallet implements Contract { - constructor( - readonly address: Address, - readonly init?: { code: Cell; data: Cell } - ) { } - - static createFromAddress(address: Address) { - return new Wallet(address); - } - - static createFromPublicKey(publicKey: Buffer, workchain = 0) { - const data = beginCell() - .storeBuffer(publicKey, 32) - .storeUint(0, 16) - .endCell(); - const init = { code: walletCode, data }; - return new Wallet(contractAddress(workchain, init), init); - } - - async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { - await provider.internal(via, { - value, - sendMode: SendMode.PAY_GAS_SEPARATELY, - body: Cell.EMPTY, - }); - } - - async sendExternalMessage( - provider: ContractProvider, - keypair: KeyPair, - actions: OutAction[], - seqno: number, - validUntil?: number - ) { - if (actions.length > MAX_ACTIONS) { - throw new Error(`Maximum ${MAX_ACTIONS} actions allowed`); - } - - if (actions.length === 0) { - throw new Error('At least one action is required'); - } - - if (validUntil === undefined) { - validUntil = Math.floor(Date.now() / 1000) + DEFAULT_VALID_UNTIL_OFFSET; - } - - const actionsCell = beginCell(); - storeOutList(actions)(actionsCell); - - const msgInner = beginCell() - .storeUint(validUntil, 64) - .storeUint(seqno & 0xFFFF, 16) - .storeRef(actionsCell.endCell()) - .endCell(); - const hash = msgInner.hash(); - const signature = sign(hash, keypair.secretKey); - await provider.external( - beginCell().storeBuffer(signature, 64).storeRef(msgInner).endCell() - ); - } - - async sendTransfers( - provider: ContractProvider, - keypair: KeyPair, - transfers: TransferMessage[], - seqno: number, - validUntil?: number - ) { - if (transfers.length === 0) { - throw new Error('At least one transfer is required'); - } - const actions = transfers.map(createTransferAction); - await this.sendExternalMessage(provider, keypair, actions, seqno, validUntil); - } - - async sendSetCode( - provider: ContractProvider, - keypair: KeyPair, - code: Cell, - seqno: number, - validUntil?: number - ) { - const action = createSetCodeAction(code); - await this.sendExternalMessage(provider, keypair, [action], seqno, validUntil); - } - - private async getStorageParams(provider: ContractProvider): Promise<{ publicKey: Buffer; seqno: bigint } | { publicKey: undefined; seqno: bigint }> { - const state = (await provider.getState()).state; - if (state.type == 'active') { - const data = Cell.fromBoc(state.data!)[0].beginParse(); - return { publicKey: data.loadBuffer(32), seqno: data.loadUintBig(16) }; - } - return { publicKey: undefined, seqno: BigInt(0) }; - } - - async getPublicKey(provider: ContractProvider): Promise { - const { publicKey } = await this.getStorageParams(provider); - return publicKey; - } - - async getSeqno(provider: ContractProvider): Promise { - const { seqno } = await this.getStorageParams(provider); - return seqno; - } -} diff --git a/standard/wallets/comparison/wrappers/highload-query-id.ts b/standard/wallets/comparison/wrappers/highload-query-id.ts new file mode 100644 index 0000000..31b6309 --- /dev/null +++ b/standard/wallets/comparison/wrappers/highload-query-id.ts @@ -0,0 +1,80 @@ +const BIT_NUMBER_SIZE = 10n; // 10 bit +const SHIFT_SIZE = 13n; // 13 bit +const MAX_BIT_NUMBER = 1022n; +const MAX_SHIFT = 8191n; // 2^13 = 8192 + +export class HighloadQueryId { + private shift: bigint; // [0 .. 8191] + private bitnumber: bigint; // [0 .. 1022] + + constructor() { + this.shift = 0n; + this.bitnumber = 0n; + } + + static fromShiftAndBitNumber(shift: bigint, bitnumber: bigint): HighloadQueryId { + const q = new HighloadQueryId(); + q.shift = shift; + if (q.shift < 0) throw new Error('invalid shift'); + if (q.shift > MAX_SHIFT) throw new Error('invalid shift'); + q.bitnumber = bitnumber; + if (q.bitnumber < 0) throw new Error('invalid bitnumber'); + if (q.bitnumber > MAX_BIT_NUMBER) throw new Error('invalid bitnumber'); + return q; + } + + getNext() { + let newBitnumber = this.bitnumber + 1n; + let newShift = this.shift; + + if (newShift === MAX_SHIFT && newBitnumber > MAX_BIT_NUMBER - 1n) { + throw new Error('Overload'); // NOTE: we left one queryId for emergency withdraw + } + + if (newBitnumber > MAX_BIT_NUMBER) { + newBitnumber = 0n; + newShift += 1n; + if (newShift > MAX_SHIFT) { + throw new Error('Overload'); + } + } + + return HighloadQueryId.fromShiftAndBitNumber(newShift, newBitnumber); + } + + hasNext() { + const isEnd = this.bitnumber >= MAX_BIT_NUMBER - 1n && this.shift === MAX_SHIFT; // NOTE: we left one queryId for emergency withdraw; + return !isEnd; + } + + getShift(): bigint { + return this.shift; + } + + getBitNumber(): bigint { + return this.bitnumber; + } + + getQueryId(): bigint { + return (this.shift << BIT_NUMBER_SIZE) + this.bitnumber; + } + + static fromQueryId(queryId: bigint): HighloadQueryId { + const shift = queryId >> BIT_NUMBER_SIZE; + const bitnumber = queryId & 1023n; + return this.fromShiftAndBitNumber(shift, bitnumber); + } + + static fromSeqno(i: bigint): HighloadQueryId { + const shift = i / 1023n; + const bitnumber = i % 1023n; + return this.fromShiftAndBitNumber(shift, bitnumber); + } + + /** + * @return {bigint} [0 .. 8380415] + */ + toSeqno(): bigint { + return this.bitnumber + this.shift * 1023n; + } +} diff --git a/standard/wallets/comparison/wrappers/highload-wallet-v3.ts b/standard/wallets/comparison/wrappers/highload-wallet-v3.ts new file mode 100644 index 0000000..a110440 --- /dev/null +++ b/standard/wallets/comparison/wrappers/highload-wallet-v3.ts @@ -0,0 +1,233 @@ +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + internal as internal_relaxed, + MessageRelaxed, + OutAction, + OutActionSendMsg, + Sender, + SendMode, + storeMessageRelaxed, + storeOutList, + toNano, +} from '@ton/core'; +import { sign } from '@ton/crypto'; +import { OP } from '../tests/imports/const'; +import { HighloadQueryId } from './highload-query-id'; + +const HighloadWalletV3CodeHex = + 'b5ee9c7241021001000228000114ff00f4a413f4bcf2c80b01020120020d02014803040078d020d74bc00101c060b0915be101d0d3030171b0915be0fa4030f828c705b39130e0d31f018210ae42e5a4ba9d8040d721d74cf82a01ed55fb04e030020120050a02027306070011adce76a2686b85ffc00201200809001aabb6ed44d0810122d721d70b3f0018aa3bed44d08307d721d70b1f0201200b0c001bb9a6eed44d0810162d721d70b15800e5b8bf2eda2edfb21ab09028409b0ed44d0810120d721f404f404d33fd315d1058e1bf82325a15210b99f326df82305aa0015a112b992306dde923033e2923033e25230800df40f6fa19ed021d721d70a00955f037fdb31e09130e259800df40f6fa19cd001d721d70a00937fdb31e0915be270801f6f2d48308d718d121f900ed44d0d3ffd31ff404f404d33fd315d1f82321a15220b98e12336df82324aa00a112b9926d32de58f82301de541675f910f2a106d0d31fd4d307d30cd309d33fd315d15168baf2a2515abaf2a6f8232aa15250bcf2a304f823bbf2a35304800df40f6fa199d024d721d70a00f2649130e20e01fe5309800df40f6fa18e13d05004d718d20001f264c858cf16cf8301cf168e1030c824cf40cf8384095005a1a514cf40e2f800c94039800df41704c8cbff13cb1ff40012f40012cb3f12cb15c9ed54f80f21d0d30001f265d3020171b0925f03e0fa4001d70b01c000f2a5fa4031fa0031f401fa0031fa00318060d721d300010f0020f265d2000193d431d19130e272b1fb00b585bf03'; + +export const HighloadWalletV3Code = Cell.fromBoc(Buffer.from(HighloadWalletV3CodeHex, 'hex'))[0]; + +export type HighloadWalletV3Config = { + publicKey: Buffer; + subwalletId: number; + timeout: number; +}; + +export const TIMESTAMP_SIZE = 64; +export const TIMEOUT_SIZE = 22; + +export function highloadWalletV3ConfigToCell(config: HighloadWalletV3Config): Cell { + return beginCell() + .storeBuffer(config.publicKey) + .storeUint(config.subwalletId, 32) + .storeUint(0, 1 + 1 + TIMESTAMP_SIZE) + .storeUint(config.timeout, TIMEOUT_SIZE) + .endCell(); +} + +export class HighloadWalletV3 implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} + + static createFromAddress(address: Address) { + return new HighloadWalletV3(address); + } + + static createFromConfig(config: HighloadWalletV3Config, code: Cell, workchain = 0) { + const data = highloadWalletV3ConfigToCell(config); + const init = { code, data }; + return new HighloadWalletV3(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + bounce: false, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell(), + }); + } + + async sendExternalMessage( + provider: ContractProvider, + secretKey: Buffer, + opts: { + message: MessageRelaxed | Cell; + mode: number; + query_id: bigint | HighloadQueryId; + createdAt: number; + subwalletId: number; + timeout: number; + }, + ) { + let messageCell: Cell; + + if (opts.message instanceof Cell) { + messageCell = opts.message; + } else { + const messageBuilder = beginCell(); + messageBuilder.store(storeMessageRelaxed(opts.message)); + messageCell = messageBuilder.endCell(); + } + + const queryId = + opts.query_id instanceof HighloadQueryId ? opts.query_id.getQueryId() : opts.query_id; + + const messageInner = beginCell() + .storeUint(opts.subwalletId, 32) + .storeRef(messageCell) + .storeUint(opts.mode, 8) + .storeUint(queryId, 23) + .storeUint(opts.createdAt, TIMESTAMP_SIZE) + .storeUint(opts.timeout, TIMEOUT_SIZE) + .endCell(); + + await provider.external( + beginCell() + .storeBuffer(sign(messageInner.hash(), secretKey)) + .storeRef(messageInner) + .endCell(), + ); + } + + async sendBatch( + provider: ContractProvider, + secretKey: Buffer, + messages: OutActionSendMsg[], + subwallet: number, + query_id: HighloadQueryId, + timeout: number, + createdAt?: number, + value: bigint = 0n, + ) { + if (createdAt == undefined) { + createdAt = Math.floor(Date.now() / 1000); + } + return await this.sendExternalMessage(provider, secretKey, { + message: this.packActions(messages, value, query_id), + mode: value > 0n ? SendMode.PAY_GAS_SEPARATELY : SendMode.CARRY_ALL_REMAINING_BALANCE, + query_id: query_id, + createdAt: createdAt, + subwalletId: subwallet, + timeout: timeout, + }); + } + + static createInternalTransferBody(opts: { + actions: OutAction[] | Cell; + queryId: HighloadQueryId; + }) { + let actionsCell: Cell; + if (opts.actions instanceof Cell) { + actionsCell = opts.actions; + } else { + if (opts.actions.length > 254) { + throw TypeError('Max allowed action count is 254. Use packActions instead.'); + } + const actionsBuilder = beginCell(); + storeOutList(opts.actions)(actionsBuilder); + actionsCell = actionsBuilder.endCell(); + } + return beginCell() + .storeUint(OP.InternalTransfer, 32) + .storeUint(opts.queryId.getQueryId(), 64) + .storeRef(actionsCell) + .endCell(); + } + + createInternalTransfer(opts: { + actions: OutAction[] | Cell; + queryId: HighloadQueryId; + value: bigint; + }) { + return internal_relaxed({ + to: this.address, + value: opts.value, + body: HighloadWalletV3.createInternalTransferBody(opts), + }); + /*beginCell() + .storeUint(0x10, 6) + .storeAddress(this.address) + .storeCoins(opts.value) + .storeUint(0, 107) + .storeSlice(body.asSlice()) + .endCell(); + */ + } + + packActions(messages: OutAction[], value: bigint = toNano('1'), query_id: HighloadQueryId) { + let batch: OutAction[]; + if (messages.length > 254) { + batch = messages.slice(0, 253); + batch.push({ + type: 'sendMsg', + mode: value > 0n ? SendMode.PAY_GAS_SEPARATELY : SendMode.CARRY_ALL_REMAINING_BALANCE, + outMsg: this.packActions(messages.slice(253), value, query_id), + }); + } else { + batch = messages; + } + return this.createInternalTransfer({ + actions: batch, + queryId: query_id, + value, + }); + } + + async getPublicKey(provider: ContractProvider): Promise { + const res = (await provider.get('get_public_key', [])).stack; + const pubKeyU = res.readBigNumber(); + return Buffer.from(pubKeyU.toString(16).padStart(32 * 2, '0'), 'hex'); + } + + async getSubwalletId(provider: ContractProvider): Promise { + const res = (await provider.get('get_subwallet_id', [])).stack; + return res.readNumber(); + } + + async getTimeout(provider: ContractProvider): Promise { + const res = (await provider.get('get_timeout', [])).stack; + return res.readNumber(); + } + + async getLastCleaned(provider: ContractProvider): Promise { + const res = (await provider.get('get_last_clean_time', [])).stack; + return res.readNumber(); + } + + async getProcessed( + provider: ContractProvider, + queryId: HighloadQueryId, + needClean = true, + ): Promise { + const res = ( + await provider.get('processed?', [ + { type: 'int', value: queryId.getQueryId() }, + { + type: 'int', + value: needClean ? -1n : 0n, + }, + ]) + ).stack; + return res.readBoolean(); + } +} diff --git a/standard/wallets/comparison/wrappers/msg-generator.ts b/standard/wallets/comparison/wrappers/msg-generator.ts new file mode 100644 index 0000000..7d6acb1 --- /dev/null +++ b/standard/wallets/comparison/wrappers/msg-generator.ts @@ -0,0 +1,149 @@ +import { + Cell, + CommonMessageInfoExternalIn, + CommonMessageInfoExternalOut, + ExternalAddress, + Message, + MessageRelaxed, + StateInit, + beginCell, + external, + storeMessage, + storeMessageRelaxed, +} from '@ton/core'; +import { randomAddress } from '@ton/test-utils'; +export class MsgGenerator { + constructor(readonly wc: number) {} + + generateExternalOutWithBadSource() { + const ssrcInvalid = beginCell() + .storeUint(2, 2) // addr_std$10 + .storeUint(0, 1) // anycast nothing + .storeInt(this.wc, 8) // workchain_id: -1 + .storeUint(1, 10) + .endCell(); + + return beginCell() + .storeUint(3, 2) // ext_out_msg_info$11 + .storeBit(0) // src:INVALID + .storeSlice(ssrcInvalid.beginParse()) + .endCell(); + } + generateExternalOutWithBadDst() { + const src = randomAddress(-1); + return beginCell() + .storeUint(3, 2) // ext_out_msg_info$11 + .storeAddress(src) // src:MsgAddressInt + .storeBit(0) // dest:INVALID + .endCell(); + } + generateExternalInWithBadSource() { + const ssrcInvalid = beginCell() + .storeUint(1, 2) // addrExtern$01 + .storeUint(128, 9) + .storeUint(0, 10) + .endCell(); + + return beginCell() + .storeUint(2, 2) //ext_in_msg_info$11 + .storeSlice(ssrcInvalid.beginParse()) // src:INVALID + .endCell(); + } + generateExternalInWithBadDst() { + const src = new ExternalAddress(BigInt(Date.now()), 256); + return beginCell() + .storeUint(2, 2) //ext_in_msg_info$10 + .storeAddress(src) // src:MsgAddressExt + .storeBit(0) // dest:INVALID + .endCell(); + } + generateInternalMessageWithBadGrams() { + const src = randomAddress(this.wc); + const dst = randomAddress(this.wc); + return beginCell() + .storeUint(0, 1) // int_msg_info$0 + .storeUint(0, 1) // ihr_disabled:Bool + .storeUint(0, 1) // bounce:Bool + .storeUint(0, 1) // bounced:Bool + .storeAddress(src) // src:MsgAddress + .storeAddress(dst) // dest:MsgAddress + .storeUint(8, 4) // len of nanograms + .storeUint(1, 1) // INVALID GRAMS amount + .endCell(); + } + generateInternalMessageWithBadInitStateData() { + const ssrc = randomAddress(this.wc); + const sdest = randomAddress(this.wc); + + const init_state_with_bad_data = beginCell() + .storeUint(0, 1) // maybe (##5) + .storeUint(1, 1) // Maybe TickTock + .storeUint(1, 1) // bool Tick + .storeUint(0, 1) // bool Tock + .storeUint(1, 1) // code: Maybe Cell^ + .storeUint(1, 1) // data: Maybe Cell^ + .storeUint(1, 1); // library: Maybe ^Cell + // bits for references but no data + + return beginCell() + .storeUint(0, 1) // int_msg_info$0 + .storeUint(0, 1) // ihr_disabled:Bool + .storeUint(0, 1) // bounce:Bool + .storeUint(0, 1) // bounced:Bool + .storeAddress(ssrc) // src:MsgAddress + .storeAddress(sdest) // dest:MsgAddress + .storeCoins(0) // + .storeMaybeRef(null) // extra currencies + .storeCoins(0) // ihr_fee + .storeCoins(0) // fwd_fee + .storeUint(1000, 64) // created_lt:uint64 + .storeUint(1000, 32) // created_at:uint32 + .storeUint(1, 1) // Maybe init_state + .storeUint(1, 1) // Either (X ^X) init state + .storeRef(init_state_with_bad_data.endCell()) + .storeUint(0, 1) // Either (X ^X) body + .endCell(); + } + + *generateBadMsg() { + // Meh + yield this.generateExternalInWithBadDst(); + yield this.generateExternalOutWithBadDst(); + yield this.generateExternalInWithBadSource(); + yield this.generateExternalOutWithBadSource(); + yield this.generateInternalMessageWithBadGrams(); + yield this.generateInternalMessageWithBadInitStateData(); + } + generateExternalInMsg( + info?: Partial, + body?: Cell, + init?: StateInit, + ) { + const msgInfo: CommonMessageInfoExternalIn = { + type: 'external-in', + dest: info?.dest || randomAddress(this.wc), + src: info?.src, + importFee: info?.importFee || 0n, + }; + const newMsg: Message = { + info: msgInfo, + body: body || Cell.EMPTY, + init, + }; + return beginCell().store(storeMessage(newMsg)).endCell(); + } + generateExternalOutMsg(info?: Partial, body?: Cell) { + const msgInfo: CommonMessageInfoExternalOut = { + type: 'external-out', + createdAt: info?.createdAt || 0, + createdLt: info?.createdLt || 0n, + src: info?.src || randomAddress(this.wc), + dest: info?.dest, + }; + const newMsg: MessageRelaxed = { + info: msgInfo, + body: body || Cell.EMPTY, + }; + return beginCell().store(storeMessageRelaxed(newMsg)).endCell(); + } +} diff --git a/standard/wallets/comparison/wrappers/preprocessed-wallet-v2.ts b/standard/wallets/comparison/wrappers/preprocessed-wallet-v2.ts new file mode 100644 index 0000000..76fa572 --- /dev/null +++ b/standard/wallets/comparison/wrappers/preprocessed-wallet-v2.ts @@ -0,0 +1,172 @@ +import { + Address, + beginCell, + Cell, + Contract, + contractAddress, + ContractProvider, + Sender, + SendMode, + Slice, + StateInit, + storeStateInit, + OutAction, + OutActionSendMsg, + OutActionSetCode, + storeOutList, +} from '@ton/core'; +import { KeyPair, sign } from '@ton/crypto'; + +const MAX_ACTIONS = 255; +const DEFAULT_VALID_UNTIL_OFFSET = 60; + +export const walletCode = Cell.fromBoc( + Buffer.from( + 'B5EE9C7241010101003D000076FF00DDD40120F90001D0D33FD30FD74CED44D0D3FFD70B0F20A4830FA90822C8CBFFCB0FC9ED5444301046BAF2A1F823BEF2A2F910F2A3F800ED552E766412', + 'hex', + ), +)[0]; + +export type TransferMessage = { + to: Address; + value: bigint; + body?: Cell; + mode?: SendMode; + bounce?: boolean; + init?: StateInit; +}; + +export function createTransferAction(msg: TransferMessage): OutActionSendMsg { + const bounce = msg.bounce ?? true; + + return { + type: 'sendMsg', + mode: msg.mode ?? SendMode.PAY_GAS_SEPARATELY, + outMsg: { + info: { + type: 'internal', + ihrDisabled: true, + bounce: bounce, + bounced: false, + dest: msg.to, + value: { coins: msg.value }, + ihrFee: 0n, + forwardFee: 0n, + createdLt: 0n, + createdAt: 0, + }, + init: msg.init, + body: msg.body || Cell.EMPTY, + }, + }; +} + +export function createSetCodeAction(code: Cell): OutActionSetCode { + return { + type: 'setCode', + newCode: code, + }; +} + +export class Wallet implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} + + static createFromAddress(address: Address) { + return new Wallet(address); + } + + static createFromPublicKey(publicKey: Buffer, workchain = 0) { + const data = beginCell().storeBuffer(publicKey, 32).storeUint(0, 16).endCell(); + const init = { code: walletCode, data }; + return new Wallet(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: Cell.EMPTY, + }); + } + + async sendExternalMessage( + provider: ContractProvider, + keypair: KeyPair, + actions: OutAction[], + seqno: number, + validUntil?: number, + ) { + if (actions.length > MAX_ACTIONS) { + throw new Error(`Maximum ${MAX_ACTIONS} actions allowed`); + } + + if (actions.length === 0) { + throw new Error('At least one action is required'); + } + + if (validUntil === undefined) { + validUntil = Math.floor(Date.now() / 1000) + DEFAULT_VALID_UNTIL_OFFSET; + } + + const actionsCell = beginCell(); + storeOutList(actions)(actionsCell); + + const msgInner = beginCell() + .storeUint(validUntil, 64) + .storeUint(seqno & 0xffff, 16) + .storeRef(actionsCell.endCell()) + .endCell(); + const hash = msgInner.hash(); + const signature = sign(hash, keypair.secretKey); + await provider.external(beginCell().storeBuffer(signature, 64).storeRef(msgInner).endCell()); + } + + async sendTransfers( + provider: ContractProvider, + keypair: KeyPair, + transfers: TransferMessage[], + seqno: number, + validUntil?: number, + ) { + if (transfers.length === 0) { + throw new Error('At least one transfer is required'); + } + const actions = transfers.map(createTransferAction); + await this.sendExternalMessage(provider, keypair, actions, seqno, validUntil); + } + + async sendSetCode( + provider: ContractProvider, + keypair: KeyPair, + code: Cell, + seqno: number, + validUntil?: number, + ) { + const action = createSetCodeAction(code); + await this.sendExternalMessage(provider, keypair, [action], seqno, validUntil); + } + + private async getStorageParams( + provider: ContractProvider, + ): Promise<{ publicKey: Buffer; seqno: bigint } | { publicKey: undefined; seqno: bigint }> { + const state = (await provider.getState()).state; + if (state.type == 'active') { + const data = Cell.fromBoc(state.data!)[0].beginParse(); + return { publicKey: data.loadBuffer(32), seqno: data.loadUintBig(16) }; + } + return { publicKey: undefined, seqno: BigInt(0) }; + } + + async getPublicKey(provider: ContractProvider): Promise { + const { publicKey } = await this.getStorageParams(provider); + return publicKey; + } + + async getSeqno(provider: ContractProvider): Promise { + const { seqno } = await this.getStorageParams(provider); + return seqno; + } +} From 7771e6b39b1d1bb1fcf86c7bc50e88d7fca3d939 Mon Sep 17 00:00:00 2001 From: skywardboundd Date: Fri, 28 Nov 2025 02:17:15 +0300 Subject: [PATCH 4/4] feat: add exit codes --- tvm/exit-codes/.gitignore | 23 + tvm/exit-codes/.prettierignore | 1 + tvm/exit-codes/.prettierrc | 7 + tvm/exit-codes/README.md | 31 + tvm/exit-codes/contracts/-14.tolk | 6 + tvm/exit-codes/contracts/10.tolk | 25 + tvm/exit-codes/contracts/11.tolk | 12 + tvm/exit-codes/contracts/2.tolk | 11 + tvm/exit-codes/contracts/3.tolk | 19 + tvm/exit-codes/contracts/33.tolk | 10 + tvm/exit-codes/contracts/34.tolk | 6 + tvm/exit-codes/contracts/4.tolk | 29 + tvm/exit-codes/contracts/5.tolk | 55 + tvm/exit-codes/contracts/6.tolk | 11 + tvm/exit-codes/contracts/7.tolk | 13 + tvm/exit-codes/contracts/8.tolk | 34 + tvm/exit-codes/contracts/9.tolk | 20 + tvm/exit-codes/jest.config.ts | 12 + tvm/exit-codes/jest.setup.ts | 5 + tvm/exit-codes/package-lock.json | 9250 ++++++++++++++++++++++++ tvm/exit-codes/package.json | 31 + tvm/exit-codes/tests/main.spec.ts | 44 + tvm/exit-codes/tsconfig.json | 12 + tvm/exit-codes/wrappers/-14.compile.ts | 9 + tvm/exit-codes/wrappers/10.compile.ts | 9 + tvm/exit-codes/wrappers/11.compile.ts | 9 + tvm/exit-codes/wrappers/2.compile.ts | 9 + tvm/exit-codes/wrappers/3.compile.ts | 9 + tvm/exit-codes/wrappers/33.compile.ts | 9 + tvm/exit-codes/wrappers/34.compile.ts | 9 + tvm/exit-codes/wrappers/4.compile.ts | 9 + tvm/exit-codes/wrappers/5.compile.ts | 9 + tvm/exit-codes/wrappers/6.compile.ts | 9 + tvm/exit-codes/wrappers/7.compile.ts | 9 + tvm/exit-codes/wrappers/8.compile.ts | 9 + tvm/exit-codes/wrappers/9.compile.ts | 9 + tvm/exit-codes/wrappers/main.ts | 36 + 37 files changed, 9820 insertions(+) create mode 100644 tvm/exit-codes/.gitignore create mode 100644 tvm/exit-codes/.prettierignore create mode 100644 tvm/exit-codes/.prettierrc create mode 100644 tvm/exit-codes/README.md create mode 100644 tvm/exit-codes/contracts/-14.tolk create mode 100644 tvm/exit-codes/contracts/10.tolk create mode 100644 tvm/exit-codes/contracts/11.tolk create mode 100644 tvm/exit-codes/contracts/2.tolk create mode 100644 tvm/exit-codes/contracts/3.tolk create mode 100644 tvm/exit-codes/contracts/33.tolk create mode 100644 tvm/exit-codes/contracts/34.tolk create mode 100644 tvm/exit-codes/contracts/4.tolk create mode 100644 tvm/exit-codes/contracts/5.tolk create mode 100644 tvm/exit-codes/contracts/6.tolk create mode 100644 tvm/exit-codes/contracts/7.tolk create mode 100644 tvm/exit-codes/contracts/8.tolk create mode 100644 tvm/exit-codes/contracts/9.tolk create mode 100644 tvm/exit-codes/jest.config.ts create mode 100644 tvm/exit-codes/jest.setup.ts create mode 100644 tvm/exit-codes/package-lock.json create mode 100644 tvm/exit-codes/package.json create mode 100644 tvm/exit-codes/tests/main.spec.ts create mode 100644 tvm/exit-codes/tsconfig.json create mode 100644 tvm/exit-codes/wrappers/-14.compile.ts create mode 100644 tvm/exit-codes/wrappers/10.compile.ts create mode 100644 tvm/exit-codes/wrappers/11.compile.ts create mode 100644 tvm/exit-codes/wrappers/2.compile.ts create mode 100644 tvm/exit-codes/wrappers/3.compile.ts create mode 100644 tvm/exit-codes/wrappers/33.compile.ts create mode 100644 tvm/exit-codes/wrappers/34.compile.ts create mode 100644 tvm/exit-codes/wrappers/4.compile.ts create mode 100644 tvm/exit-codes/wrappers/5.compile.ts create mode 100644 tvm/exit-codes/wrappers/6.compile.ts create mode 100644 tvm/exit-codes/wrappers/7.compile.ts create mode 100644 tvm/exit-codes/wrappers/8.compile.ts create mode 100644 tvm/exit-codes/wrappers/9.compile.ts create mode 100644 tvm/exit-codes/wrappers/main.ts diff --git a/tvm/exit-codes/.gitignore b/tvm/exit-codes/.gitignore new file mode 100644 index 0000000..cf94e93 --- /dev/null +++ b/tvm/exit-codes/.gitignore @@ -0,0 +1,23 @@ +node_modules +temp +build +dist +.DS_Store +package.ts + +# VS Code +.vscode/* +.history/ +*.vsix + +# IDEA files +.idea + +# VIM +Session.vim +.vim/ + +# Other private editor folders +.nvim/ +.emacs/ +.helix/ diff --git a/tvm/exit-codes/.prettierignore b/tvm/exit-codes/.prettierignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/tvm/exit-codes/.prettierignore @@ -0,0 +1 @@ +build diff --git a/tvm/exit-codes/.prettierrc b/tvm/exit-codes/.prettierrc new file mode 100644 index 0000000..24a6660 --- /dev/null +++ b/tvm/exit-codes/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "singleQuote": true, + "bracketSpacing": true, + "semi": true +} diff --git a/tvm/exit-codes/README.md b/tvm/exit-codes/README.md new file mode 100644 index 0000000..c57e3f0 --- /dev/null +++ b/tvm/exit-codes/README.md @@ -0,0 +1,31 @@ +# TVM Exit Codes Examples + +This repository contains examples of different exit codes for TON blockchain documentation. + +## Project structure + +- `contracts` - source code of all the smart contracts of the project, each demonstrating a specific exit code. +- `wrappers` - wrapper classes (implementing `Contract` from ton-core) for the contracts, including any [de]serialization primitives and compilation functions. +- `tests` - tests for the contracts that verify the correct exit codes are returned. +- `build` - compiled contracts. + +## Exit Codes + +This project demonstrates various exit codes that can occur during smart contract execution in TVM: + +- **Compute Phase Exit Codes**: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, -14 +- **Action Phase Exit Codes**: 33, 34 + +Each contract file (e.g., `2.tolk`, `3.tolk`) demonstrates how to trigger and handle a specific exit code. + +## How to use + +### Build + +`npx blueprint build` or `yarn blueprint build` + +### Test + +`npx blueprint test` or `yarn blueprint test` + +Tests verify that each contract correctly returns the corresponding exit code when executed. diff --git a/tvm/exit-codes/contracts/-14.tolk b/tvm/exit-codes/contracts/-14.tolk new file mode 100644 index 0000000..92e4e54 --- /dev/null +++ b/tvm/exit-codes/contracts/-14.tolk @@ -0,0 +1,6 @@ +import "@stdlib/gas-payments"; + +fun onInternalMessage() { + setGasLimit(100); +} + diff --git a/tvm/exit-codes/contracts/10.tolk b/tvm/exit-codes/contracts/10.tolk new file mode 100644 index 0000000..880e808 --- /dev/null +++ b/tvm/exit-codes/contracts/10.tolk @@ -0,0 +1,25 @@ +import "@stdlib/tvm-dicts"; + +fun touch(y: T): void asm "NOP" // so that the compiler doesn't remove instructions +fun cast(y: T): U asm "NOP" +fun cell?.addIntToIDict(mutate self, key: int, number: int): void { + return self.iDictSetBuilder(32, key, beginCell().storeInt(number, 32)); +} + +fun onInternalMessage() { + var dict = createEmptyDict(); + dict.addIntToIDict(0, 0); + dict.addIntToIDict(1, 1); + + // The Int to Int dictionary is being misinterpreted as a map + val m: map = cast(dict); + + try { + // And the error happens only when we touch it + touch(m.get(0).isFound); + } catch (exitCode) { + // exitCode is 10 + assert (exitCode == 10) throw 1111; + } +} + diff --git a/tvm/exit-codes/contracts/11.tolk b/tvm/exit-codes/contracts/11.tolk new file mode 100644 index 0000000..d30ff82 --- /dev/null +++ b/tvm/exit-codes/contracts/11.tolk @@ -0,0 +1,12 @@ +fun sendMessage(msg: cell, mode: int): void asm "SENDMSG" + +fun onInternalMessage() { + try { + // fails in the Compute phase when the message is ill-formed + sendMessage(beginCell().endCell(), 0); + } catch (exitCode) { + // exitCode is 11 + assert (exitCode == 11) throw 1111; + } +} + diff --git a/tvm/exit-codes/contracts/2.tolk b/tvm/exit-codes/contracts/2.tolk new file mode 100644 index 0000000..1688a21 --- /dev/null +++ b/tvm/exit-codes/contracts/2.tolk @@ -0,0 +1,11 @@ +fun drop(): void asm "DROP" + +fun onInternalMessage() { + try { + repeat(100){ + drop(); + } + } catch(exitCode) { + assert (exitCode == 2) throw 1111; + } +} diff --git a/tvm/exit-codes/contracts/3.tolk b/tvm/exit-codes/contracts/3.tolk new file mode 100644 index 0000000..f05cb29 --- /dev/null +++ b/tvm/exit-codes/contracts/3.tolk @@ -0,0 +1,19 @@ +// Remember kids, don't try to overflow the stack at home! +fun stackOverflow(): void asm +""" + x{} SLICE // s + BLESS // c + 0 SETNUMARGS // c' + 2 PUSHINT // c' 2 + SWAP // 2 c' + 1 -1 SETCONTARGS // ← this blows up +""" + + +fun onInternalMessage() { + try { + stackOverflow(); + } catch(exitCode) { + assert (exitCode == 3) throw 1111; + } +} diff --git a/tvm/exit-codes/contracts/33.tolk b/tvm/exit-codes/contracts/33.tolk new file mode 100644 index 0000000..da056c4 --- /dev/null +++ b/tvm/exit-codes/contracts/33.tolk @@ -0,0 +1,10 @@ +import "@stdlib/gas-payments"; + +fun onInternalMessage() { + // For example, let's attempt to queue the reservation of a specific amount of nanoToncoins + // This won't fail in the compute phase, but will result in exit code 33 in the action phase + repeat (256) { + reserveToncoinsOnBalance(1000000, RESERVE_MODE_AT_MOST); + } +} + diff --git a/tvm/exit-codes/contracts/34.tolk b/tvm/exit-codes/contracts/34.tolk new file mode 100644 index 0000000..5930298 --- /dev/null +++ b/tvm/exit-codes/contracts/34.tolk @@ -0,0 +1,6 @@ +fun onInternalMessage() { + // For example, let's try to send an ill-formed message: + // won't fail in the compute phase, but will result in exit code 34 in the Action phase + sendRawMessage(beginCell().endCell(), 0); +} + diff --git a/tvm/exit-codes/contracts/4.tolk b/tvm/exit-codes/contracts/4.tolk new file mode 100644 index 0000000..fb6d383 --- /dev/null +++ b/tvm/exit-codes/contracts/4.tolk @@ -0,0 +1,29 @@ +fun touch(y: T): void asm "NOP" // so that the compiler doesn't remove instructions +fun pow2(y: int): int asm "POW2" + +fun onInternalMessage() { + var x = -pow2(255) - pow2(255); + var zero = x - x; + + try { + touch(-x); // integer overflow by negation + // since the max positive value is 2^{256} - 1 + } catch(exitCode) { + // exitCode is 4 + assert (exitCode == 4) throw 1111; + } + + try { + touch(x / zero); // division by zero! + } catch (exitCode) { + // exitCode is 4 + assert (exitCode == 4) throw 1111; + } + + try { + touch(x * x * x); // integer overflow! + } catch (exitCode) { + // exitCode is 4 + assert (exitCode == 4) throw 1111; + } +} diff --git a/tvm/exit-codes/contracts/5.tolk b/tvm/exit-codes/contracts/5.tolk new file mode 100644 index 0000000..8afdca2 --- /dev/null +++ b/tvm/exit-codes/contracts/5.tolk @@ -0,0 +1,55 @@ +fun touch(y: T): void asm "NOP" // so that the compiler doesn't remove instructions +fun pow2(y: int): int asm "POW2" + +fun onInternalMessage() { + try { + // Repeat only operates on an inclusive range from 1 to 2^{31} - 1 + // Any valid integer value greater than that causes an error with exit code 5 + repeat (pow2(55)) { + touch("smash. I. must."); + } + } catch(exitCode) { + // exitCode is 5 + assert (exitCode == 5) throw 1111; + } + + try { + // Builder.storeUint() function can only use up to 256 bits, thus 512 is too much: + touch(beginCell().storeUint(-1, 512).toCell()); + } catch (exitCode) { + // exitCode is 5 + assert (exitCode == 5) throw 1111; + } + + try { + touch(beginCell().storeUint(100, 2).toCell()); // maximum value is 2^{2} - 1 = 3 < 100 + } + catch(exitCode) { + // exitCode is 5 + assert (exitCode == 5) throw 1111; + } + + try { + val deployMsg = createMessage({ + bounce: false, + dest: { + workchain: 0, + stateInit: { + code: beginCell().endCell(), + data: beginCell().endCell(), + }, + toShard: { + fixedPrefixLength: pow2(52), // but fixedPrefixLength is uint5 + closeTo: contract.getAddress() + }, + }, + value: 1, + body: beginCell().endCell(), + }); + + deployMsg.send(SEND_MODE_PAY_FEES_SEPARATELY); + } catch (exitCode) { + // exitCode is 5 + assert (exitCode == 5) throw 1111; + } +} diff --git a/tvm/exit-codes/contracts/6.tolk b/tvm/exit-codes/contracts/6.tolk new file mode 100644 index 0000000..229bec2 --- /dev/null +++ b/tvm/exit-codes/contracts/6.tolk @@ -0,0 +1,11 @@ +// There's no such code page, and an attempt to set it fails +fun invalidOpcode(): void asm "42 SETCP" + +fun onInternalMessage() { + try { + invalidOpcode(); + } catch (exitCode) { + // exitCode is 6 + assert (exitCode == 6) throw 1111; + } +} diff --git a/tvm/exit-codes/contracts/7.tolk b/tvm/exit-codes/contracts/7.tolk new file mode 100644 index 0000000..4802c35 --- /dev/null +++ b/tvm/exit-codes/contracts/7.tolk @@ -0,0 +1,13 @@ +fun touch(y: T): void asm "NOP" // so that the compiler doesn't remove instructions +// The actual returned value type doesn't match the declared one +fun typeCheckError(): cell asm "42 PUSHINT"; + +fun onInternalMessage() { + try { + // it isn't cell + touch(typeCheckError().beginParse()); + } catch (exitCode) { + // exitCode is 7 + assert (exitCode == 7) throw 1111; + } +} \ No newline at end of file diff --git a/tvm/exit-codes/contracts/8.tolk b/tvm/exit-codes/contracts/8.tolk new file mode 100644 index 0000000..b190c5c --- /dev/null +++ b/tvm/exit-codes/contracts/8.tolk @@ -0,0 +1,34 @@ +fun touch(y: T): void asm "NOP" // so that the compiler doesn't remove instructions + +fun onInternalMessage() { + // Too many bits + try { + val data = beginCell() + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 24) // 1024 bits! + .toCell(); + touch(data); + } catch (exitCode) { + // exitCode is 8 + assert (exitCode == 8) throw 1111; + } + + // Too many refs + try { + val data = beginCell() + .storeRef(beginCell().endCell()) + .storeRef(beginCell().endCell()) + .storeRef(beginCell().endCell()) + .storeRef(beginCell().endCell()) + .storeRef(beginCell().endCell()) // 5 refs! + .toCell(); + touch(data); + } catch (exitCode) { + // exitCode is 8 + assert (exitCode == 8) throw 1111; + } +} + diff --git a/tvm/exit-codes/contracts/9.tolk b/tvm/exit-codes/contracts/9.tolk new file mode 100644 index 0000000..492588d --- /dev/null +++ b/tvm/exit-codes/contracts/9.tolk @@ -0,0 +1,20 @@ +fun touch(y: T): void asm "NOP" // so that the compiler doesn't remove instructions + +fun onInternalMessage() { + // Too few bits + try { + touch(beginCell().endCell().beginParse().loadInt(1)); // 0 bits! + } catch (exitCode) { + // exitCode is 9 + assert (exitCode == 9) throw 1111; + } + + // Too few refs + try { + touch(beginCell().endCell().beginParse().loadRef()); // 0 refs! + } catch (exitCode) { + // exitCode is 9 + assert (exitCode == 9) throw 1111; + } +} + diff --git a/tvm/exit-codes/jest.config.ts b/tvm/exit-codes/jest.config.ts new file mode 100644 index 0000000..b4ae0bf --- /dev/null +++ b/tvm/exit-codes/jest.config.ts @@ -0,0 +1,12 @@ +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + globalSetup: './jest.setup.ts', + cache: false, // disabled caching to prevent old Tact files from being used after a rebuild + testEnvironment: '@ton/sandbox/jest-environment', + testPathIgnorePatterns: ['/node_modules/', '/dist/'], + reporters: ['default', ['@ton/sandbox/jest-reporter', {}]], +}; + +export default config; diff --git a/tvm/exit-codes/jest.setup.ts b/tvm/exit-codes/jest.setup.ts new file mode 100644 index 0000000..6359ede --- /dev/null +++ b/tvm/exit-codes/jest.setup.ts @@ -0,0 +1,5 @@ +import { buildAllTact } from '@ton/blueprint'; + +export default async function () { + await buildAllTact(); +} diff --git a/tvm/exit-codes/package-lock.json b/tvm/exit-codes/package-lock.json new file mode 100644 index 0000000..173494f --- /dev/null +++ b/tvm/exit-codes/package-lock.json @@ -0,0 +1,9250 @@ +{ + "name": "Example", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "Example", + "version": "0.0.1", + "dependencies": { + "@ton/core": "~0" + }, + "devDependencies": { + "@tact-lang/compiler": ">=1.6.13 <2.0.0", + "@ton-community/func-js": ">=0.10.0", + "@ton/blueprint": ">=0.40.0", + "@ton/crypto": "^3.3.0", + "@ton/sandbox": ">=0.37.0", + "@ton/test-utils": ">=0.11.0", + "@ton/tolk-js": ">=1.0.0", + "@ton/ton": ">=15.3.1 <16.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.17.2", + "jest": "^30.0.5", + "prettier": "^3.6.2", + "ts-jest": "^29.4.1", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", + "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@emnapi/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", + "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", + "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", + "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.0", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@ipld/dag-pb": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@ipld/dag-pb/-/dag-pb-2.1.18.tgz", + "integrity": "sha512-ZBnf2fuX9y3KccADURG5vb9FaOeMjFkCrNysB0PtftME/4iCTjxfaLoNq/IAh5fTqUOMXvryN6Jyka4ZGuMLIg==", + "dev": true, + "license": "(Apache-2.0 AND MIT)", + "dependencies": { + "multiformats": "^9.5.4" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "30.2.0", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", + "@types/node": "*", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@multiformats/murmur3": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@multiformats/murmur3/-/murmur3-1.1.3.tgz", + "integrity": "sha512-wAPLUErGR8g6Lt+bAZn6218k9YQPym+sjszsXL6o4zfxbA22P+gxWZuuD9wDbwL55xrKO5idpcuQUX7/E3oHcw==", + "dev": true, + "license": "(Apache-2.0 AND MIT)", + "dependencies": { + "multiformats": "^9.5.4", + "murmurhash3js-revisited": "^3.0.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@noble/ed25519": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-1.7.5.tgz", + "integrity": "sha512-xuS0nwRMQBvSxDa7UxMb61xTiH3MxTgUfhyPUALVIe0FlOAz4sjELwyDRyUvqeEYfRSG9qNjFIycqLZppg4RSA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@tact-lang/compiler": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@tact-lang/compiler/-/compiler-1.6.13.tgz", + "integrity": "sha512-lrgT/kCgC+nuppB4zPSDCAcLQ6EauTJ3NEBX4prEBBRmJ8aexYAfFUfVayskZw96JrDjReNIhnD8dG/yU0Fk+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tact-lang/opcode": "^0.3.0", + "@ton/core": "0.60.1", + "@ton/crypto": "^3.2.0", + "@tonstudio/parser-runtime": "0.0.1", + "blockstore-core": "1.0.5", + "glob": "^8.1.0", + "ipfs-unixfs-importer": "9.0.10", + "path-normalize": "^6.0.13", + "yaml": "^2.7.1", + "zod": "^3.22.4" + }, + "bin": { + "tact": "bin/tact.js", + "tact-fmt": "bin/tact-fmt.js", + "unboc": "bin/unboc.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@tact-lang/compiler/node_modules/@ton/core": { + "version": "0.60.1", + "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.60.1.tgz", + "integrity": "sha512-8FwybYbfkk57C3l9gvnlRhRBHbLYmeu0LbB1z9N+dhDz0Z+FJW8w0TJlks8CgHrAFxsT3FlR2LsqFnsauMp38w==", + "dev": true, + "license": "MIT", + "dependencies": { + "symbol.inspect": "1.0.1" + }, + "peerDependencies": { + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@tact-lang/opcode": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@tact-lang/opcode/-/opcode-0.3.2.tgz", + "integrity": "sha512-ZFsgOBTCxsKkYYOKomdaHMc8VSOFQKTbjLR1mYq6NFYyTdaz69gHEqgsIEZ0URSNWGg2er5H+LXlv8+8Tlt7sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ton/core": "^0.61.0", + "@ton/crypto": "^3.3.0", + "@ton/sandbox": "^0.32.2" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@tact-lang/opcode/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@ton/core": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.61.0.tgz", + "integrity": "sha512-0qyVfP2dDue2bq80ydXggo2MlufcmzuFk6G94qRrZxvyQ3NSe4UeBTeRf1gQmN7tywgTsX2gS61e4yvJrlUu4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "symbol.inspect": "1.0.1" + }, + "peerDependencies": { + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/@ton/sandbox": { + "version": "0.32.2", + "resolved": "https://registry.npmjs.org/@ton/sandbox/-/sandbox-0.32.2.tgz", + "integrity": "sha512-D+Yuyka3pMuoD1KPufRGzE3iFZ0QLyba/xC5mfrXoLtV111ubKxc7RscndOsggeru0bdDYm0i/iaWO5YQWqUfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "table": "^6.9.0" + }, + "peerDependencies": { + "@ton/core": ">=0.61.0", + "@ton/crypto": ">=3.3.0", + "@ton/test-utils": ">=0.7.0", + "jest": "^29.5.0" + }, + "peerDependenciesMeta": { + "@ton/test-utils": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/@tact-lang/opcode/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@tact-lang/opcode/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tact-lang/opcode/node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tact-lang/opcode/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@tact-lang/opcode/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tact-lang/opcode/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@tact-lang/opcode/node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@tact-lang/opcode/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tact-lang/opcode/node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@tact-lang/opcode/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@tact-lang/opcode/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/@tact-lang/opcode/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@tact-lang/opcode/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@ton-api/client": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@ton-api/client/-/client-0.2.0.tgz", + "integrity": "sha512-m/T8Nroq4rghUcg72Bbt2He9x5g9RlrP4F9rs7J4DVYLMHnKQTlKDo9JMD/feLXtHxOMh/YGJNcb+BGBkNbNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.38.0" + }, + "peerDependencies": { + "@ton/core": ">=0.59.0" + } + }, + "node_modules/@ton-api/ton-adapter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@ton-api/ton-adapter/-/ton-adapter-0.2.0.tgz", + "integrity": "sha512-0l1Y7pgi6/N6HOqRAdgOemDssYB4sXKtHWSKgm+cDL754ZMP3gsj/6pEmgOo/H7+itsKf4t0UqOU0q2JuqG/zw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@ton-api/client": "^0.2.0", + "@ton/core": ">=0.59.0" + } + }, + "node_modules/@ton-community/func-js": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@ton-community/func-js/-/func-js-0.11.0.tgz", + "integrity": "sha512-pajDH+Tvspz/OQZoB+tHGwfs7hl11FnF+jnbi2oyEpdnyUXPeg9iZQhN2qkJgVsHn4HZptLYl70MTDKlp+hZ8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ton-community/func-js-bin": "0.4.6-wasmfix.debugger.1", + "arg": "^5.0.2", + "fflate": "^0.8.2" + }, + "bin": { + "func-js": "dist/cli.js" + } + }, + "node_modules/@ton-community/func-js-bin": { + "version": "0.4.6-wasmfix.debugger.1", + "resolved": "https://registry.npmjs.org/@ton-community/func-js-bin/-/func-js-bin-0.4.6-wasmfix.debugger.1.tgz", + "integrity": "sha512-j3phTMdPkjCrfY9BZjeXRxqdcNwdHUzA/FyuImyTvae7tPymttT4Mmkhc/ChrIx69a4d2Q06jEgI+THDSrRPfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ton/blueprint": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@ton/blueprint/-/blueprint-0.41.0.tgz", + "integrity": "sha512-sBdaCoioXfiqXchxjUiHpz3b5RjN6aV+3MiHA8Oiujiuo6HAQDnDz/VsOe00MYMFppcyaHWx1JTPcBzKwNwvpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ton-api/client": "^0.2.0", + "@ton-api/ton-adapter": "^0.2.0", + "@tonconnect/sdk": "^2.2.0", + "arg": "^5.0.2", + "axios": "^1.7.7", + "chalk": "^4.1.0", + "dotenv": "^16.1.4", + "inquirer": "^8.2.5", + "qrcode-terminal": "^0.12.0", + "ton-lite-client": "^3.1.1", + "ts-node": "^10.9.1" + }, + "bin": { + "blueprint": "dist/cli/cli.js" + }, + "peerDependencies": { + "@tact-lang/compiler": ">=1.6.5", + "@ton-community/func-js": ">=0.10.0", + "@ton/core": ">=0.61.0", + "@ton/crypto": ">=3.3.0", + "@ton/sandbox": "^0.37.1", + "@ton/tolk-js": ">=0.13.0", + "@ton/ton": ">=15.2.1" + }, + "peerDependenciesMeta": { + "@ton/sandbox": { + "optional": true + } + } + }, + "node_modules/@ton/core": { + "version": "0.62.0", + "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.62.0.tgz", + "integrity": "sha512-GCYlzzx11rSESKkiHvNy9tL8zWth+ZtUbvV29WH478FvBp8xTw24AyoigwXWNV+OLCAcnwlGhZpTpxjD3wzCwA==", + "license": "MIT", + "dependencies": { + "symbol.inspect": "1.0.1" + }, + "peerDependencies": { + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@ton/crypto": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz", + "integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==", + "license": "MIT", + "dependencies": { + "@ton/crypto-primitives": "2.1.0", + "jssha": "3.2.0", + "tweetnacl": "1.0.3" + } + }, + "node_modules/@ton/crypto-primitives": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz", + "integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==", + "license": "MIT", + "dependencies": { + "jssha": "3.2.0" + } + }, + "node_modules/@ton/sandbox": { + "version": "0.37.2", + "resolved": "https://registry.npmjs.org/@ton/sandbox/-/sandbox-0.37.2.tgz", + "integrity": "sha512-6GB9vkAmFeRRNWJucfNuyBdLFXa5VKhbNk6nsTi6mTm42sPSh7q9Tsjx37DQQVIVToflnhDF32VFryWwP4DOgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/debugadapter": "^1.68.0", + "chalk": "^4.1.2", + "fflate": "^0.8.2", + "table": "^6.9.0", + "ton-assembly": "0.1.2" + }, + "peerDependencies": { + "@ton-community/func-js": ">=0.10.0", + "@ton/core": ">=0.61.0", + "@ton/crypto": ">=3.3.0", + "@ton/test-utils": ">=0.7.0", + "jest": "^29.5.0 || ^30.0.5" + }, + "peerDependenciesMeta": { + "@ton/test-utils": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/@ton/test-utils": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@ton/test-utils/-/test-utils-0.12.0.tgz", + "integrity": "sha512-SJ6y+213M8C8vDc64JYIu07nEjaYMMlm3pU/64UFINackGkxFFpgmHdmRH/IzFJ2zclC0Wnv7LKNlBIAReT4sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-inspect-extracted": "^2.0.0" + }, + "peerDependencies": { + "@jest/globals": "*", + "@ton/core": ">=0.49.2", + "@types/bun": ">=1.0.0", + "chai": "*" + }, + "peerDependenciesMeta": { + "@jest/globals": { + "optional": true + }, + "@types/bun": { + "optional": true + }, + "chai": { + "optional": true + } + } + }, + "node_modules/@ton/tolk-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ton/tolk-js/-/tolk-js-1.1.0.tgz", + "integrity": "sha512-ZlyUlmQy0G4bfiz2k6dT8OEvP0p8Fs0MUTVKgN1HG3QOmzQF5BX3sNwnROAG/Zwe+poAj3VSlm4Kpt7Zjkalcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arg": "^5.0.2" + }, + "bin": { + "tolk-js": "dist/cli.js" + } + }, + "node_modules/@ton/ton": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@ton/ton/-/ton-15.4.0.tgz", + "integrity": "sha512-f19y2Rez88KZK+lv3CT3ghXi07LcToJtJhlgRSfK+3GzjdIcoW/wbmXG1ffi6fkc8W2LO5z6Q3gZaIEvNGnz6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.7", + "dataloader": "^2.0.0", + "symbol.inspect": "1.0.1", + "teslabot": "^1.3.0", + "zod": "^3.21.4" + }, + "peerDependencies": { + "@ton/core": ">=0.62.0 <1.0.0", + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@tonconnect/isomorphic-eventsource": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.1.tgz", + "integrity": "sha512-ODk48pMlqLSOvu3fM0R1sdlz/Cv2y4hSfwtXmLq9ky9+H7ZQfw/16ElpIJ69B4lUvHycxrueNgrRtF9PJHoGMw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "eventsource": "^2.0.2" + } + }, + "node_modules/@tonconnect/isomorphic-fetch": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.2.tgz", + "integrity": "sha512-DAyA4oL7MqbBo9k8+8E+YiWsGCYi6UMhDTcsZjhgzhESkBNG6b+NBkpb1KH4oi0xDZQoknFtY9XogJLuQtSMQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-fetch": "^2.6.9" + } + }, + "node_modules/@tonconnect/protocol": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.3.0.tgz", + "integrity": "sha512-OxrmcXF/EsSdGeASP9VpTRojuMtTV87DKYFLRq4ZJvF/Hirfm2cgcxzzj2uksEGm5IIR010UWo6b38RuokNwFQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.1" + } + }, + "node_modules/@tonconnect/sdk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-2.2.0.tgz", + "integrity": "sha512-8plnAXzaLhapUnt47ZqAOQSIQ8NHSvgTSR74QVJdPWqg8128smgGM4cDYewKdBfTD6Lup0odT1WMMrJu+rE4NQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@tonconnect/isomorphic-eventsource": "^0.0.1", + "@tonconnect/isomorphic-fetch": "^0.0.2", + "@tonconnect/protocol": "^2.2.5" + } + }, + "node_modules/@tonstudio/parser-runtime": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@tonstudio/parser-runtime/-/parser-runtime-0.0.1.tgz", + "integrity": "sha512-5s4fLkXWxa4SAd7QGGvJXe13GakEo0J3VF5dUI/i3A//bGZxMwCp1FcnbErpNs3y0LcAZoXE5FCUnDowDQptqw==", + "dev": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz", + "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pegjs": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz", + "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.34", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz", + "integrity": "sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/debugadapter": { + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@vscode/debugadapter/-/debugadapter-1.68.0.tgz", + "integrity": "sha512-D6gk5Fw2y4FV8oYmltoXpj+VAZexxJFopN/mcZ6YcgzQE9dgq2L45Aj3GLxScJOD6GeLILcxJIaA8l3v11esGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vscode/debugprotocol": "1.68.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@vscode/debugprotocol": { + "version": "1.68.0", + "resolved": "https://registry.npmjs.org/@vscode/debugprotocol/-/debugprotocol-1.68.0.tgz", + "integrity": "sha512-2J27dysaXmvnfuhFGhfeuxfHRXunqNPxtBoR3koiTOA9rdxWNDTa1zIFLCFMSHJ9MPTPKFcBeblsyaCJCIlQxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adnl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/adnl/-/adnl-1.0.3.tgz", + "integrity": "sha512-P+jGWhWTp0f4EskKie5rUA+EnQINzq0qvu1N3UkNSjynIOWCzl0wOPGUo3IAJ2r9/MI9DnebRYO0e4wyX7qbBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/ed25519": "^1.6.1", + "@noble/hashes": "^1.2.0", + "aes-js": "^3.1.2", + "buffer": "^6.0.3", + "events": "^3.3.0", + "isomorphic-ws": "^5.0.0", + "ws": "^8.8.1" + } + }, + "node_modules/aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.0.tgz", + "integrity": "sha512-zt40Pz4zcRXra9CVV31KeyofwiNvAbJ5B6YPz9pMJ+yOSLikvPT4Yi5LjfgjRa9CawVYBaD1JQzIVcIvBejKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "dev": true, + "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", + "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blockstore-core": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/blockstore-core/-/blockstore-core-1.0.5.tgz", + "integrity": "sha512-i/9CUMMvBALVbtSqUIuiWB3tk//a4Q2I2CEWiBuYNnhJvk/DWplXjLt8Sqc5VGkRVXVPSsEuH8fUtqJt5UFYcA==", + "dev": true, + "license": "(Apache-2.0 OR MIT)", + "dependencies": { + "err-code": "^3.0.1", + "interface-blockstore": "^2.0.2", + "interface-store": "^2.0.1", + "it-all": "^1.0.4", + "it-drain": "^1.0.4", + "it-filter": "^1.0.2", + "it-take": "^1.0.1", + "multiformats": "^9.4.7" + } + }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001751", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", + "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/case-shift": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/case-shift/-/case-shift-2.5.3.tgz", + "integrity": "sha512-6SdS9W5xu82Kj1Z6f14h0zsbWTdXGtD0RftPNnqbAFFqqlzX1SMFi1E1NDIBg5LC2m9EYWWPUV80nTb3iu2e6Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", + "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", + "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-js-pure": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.46.0.tgz", + "integrity": "sha512-NMCW30bHNofuhwLhYPt66OLOKTMbOhgTTatKVbaQC3KRHpTCiRIBYvtshr+NBYSnBxwAFhjW/RfJ0XbIjS16rw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/create-jest/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/create-jest/node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/create-jest/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/create-jest/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/create-jest/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/create-jest/node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/create-jest/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/create-jest/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/create-jest/node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-jest/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/create-jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/create-jest/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.241", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.241.tgz", + "integrity": "sha512-ILMvKX/ZV5WIJzzdtuHg8xquk2y0BOGlFOxBVwTpbiXqWIH0hamG45ddU4R3PQ0gYu+xgo0vdHXHli9sHIGb4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hamt-sharding": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-2.0.1.tgz", + "integrity": "sha512-vnjrmdXG9dDs1m/H4iJ6z0JFI2NtgsW5keRkTcM85NGak69Mkf5PHUqBz+Xs0T4sg0ppvj9O5EGAJo40FTxmmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sparse-array": "^1.3.1", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "8.2.7", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.7.tgz", + "integrity": "sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.0", + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/interface-blockstore": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/interface-blockstore/-/interface-blockstore-2.0.3.tgz", + "integrity": "sha512-OwVUnlNcx7H5HloK0Myv6c/C1q9cNG11HX6afdeU6q6kbuNj8jKCwVnmJHhC94LZaJ+9hvVOk4IUstb3Esg81w==", + "dev": true, + "license": "(Apache-2.0 OR MIT)", + "dependencies": { + "interface-store": "^2.0.2", + "multiformats": "^9.0.4" + } + }, + "node_modules/interface-store": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/interface-store/-/interface-store-2.0.2.tgz", + "integrity": "sha512-rScRlhDcz6k199EkHqT8NpM87ebN89ICOzILoBHgaG36/WX50N32BnU/kpZgCGPLhARRAWUUX5/cyaIjt7Kipg==", + "dev": true, + "license": "(Apache-2.0 OR MIT)" + }, + "node_modules/ipfs-unixfs": { + "version": "6.0.9", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-6.0.9.tgz", + "integrity": "sha512-0DQ7p0/9dRB6XCb0mVCTli33GzIzSVx5udpJuVM47tGcD+W+Bl4LsnoLswd3ggNnNEakMv1FdoFITiEnchXDqQ==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "dependencies": { + "err-code": "^3.0.1", + "protobufjs": "^6.10.2" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-unixfs-importer": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-9.0.10.tgz", + "integrity": "sha512-W+tQTVcSmXtFh7FWYWwPBGXJ1xDgREbIyI1E5JzDcimZLIyT5gGMfxR3oKPxxWj+GKMpP5ilvMQrbsPzWcm3Fw==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@ipld/dag-pb": "^2.0.2", + "@multiformats/murmur3": "^1.0.3", + "bl": "^5.0.0", + "err-code": "^3.0.1", + "hamt-sharding": "^2.0.0", + "interface-blockstore": "^2.0.3", + "ipfs-unixfs": "^6.0.0", + "it-all": "^1.0.5", + "it-batch": "^1.0.8", + "it-first": "^1.0.6", + "it-parallel-batch": "^1.0.9", + "merge-options": "^3.0.4", + "multiformats": "^9.4.2", + "rabin-wasm": "^0.1.4", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/it-all": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", + "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==", + "dev": true, + "license": "ISC" + }, + "node_modules/it-batch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-1.0.9.tgz", + "integrity": "sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA==", + "dev": true, + "license": "ISC" + }, + "node_modules/it-drain": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/it-drain/-/it-drain-1.0.5.tgz", + "integrity": "sha512-r/GjkiW1bZswC04TNmUnLxa6uovme7KKwPhc+cb1hHU65E3AByypHH6Pm91WHuvqfFsm+9ws0kPtDBV3/8vmIg==", + "dev": true, + "license": "ISC" + }, + "node_modules/it-filter": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/it-filter/-/it-filter-1.0.3.tgz", + "integrity": "sha512-EI3HpzUrKjTH01miLHWmhNWy3Xpbx4OXMXltgrNprL5lDpF3giVpHIouFpr5l+evXw6aOfxhnt01BIB+4VQA+w==", + "dev": true, + "license": "ISC" + }, + "node_modules/it-first": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", + "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==", + "dev": true, + "license": "ISC" + }, + "node_modules/it-parallel-batch": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-1.0.11.tgz", + "integrity": "sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "it-batch": "^1.0.9" + } + }, + "node_modules/it-take": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/it-take/-/it-take-1.0.2.tgz", + "integrity": "sha512-u7I6qhhxH7pSevcYNaMECtkvZW365ARqAIt9K+xjdK1B2WUDEjQSfETkOCT8bxFq/59LqrN3cMLUtTgmDBaygw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "jest-util": "30.2.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "p-limit": "^3.1.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", + "parse-json": "^5.2.0", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "esbuild-register": ">=3.4.0", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "esbuild-register": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-diff": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "jest-util": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.2.0", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jssha": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz", + "integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/lru_map": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", + "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "dev": true, + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/murmurhash3js-revisited": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", + "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-inspect-extracted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-inspect-extracted/-/node-inspect-extracted-2.0.2.tgz", + "integrity": "sha512-8qm9+tu/GmbA/uWQRs6ad8KstyhH94a0pXpRVGHaJ9wHlJbetCYoCwXbKILaaMcE+wgbgpOpzcCnipkL8vTqxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.26", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", + "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/ora/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-normalize": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/path-normalize/-/path-normalize-6.0.13.tgz", + "integrity": "sha512-PfC1Pc+IEhI77UEN731pj2nMs9gHAV36IA6IW6VdXWjoQesf+jtO9hdMUqTRS6mwR0T5rmyUrQzd5vw0VwL1Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==", + "dev": true, + "license": "MIT", + "bin": { + "pegjs": "bin/pegjs" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "dev": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/rabin-wasm": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", + "integrity": "sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@assemblyscript/loader": "^0.9.4", + "bl": "^5.0.0", + "debug": "^4.3.1", + "minimist": "^1.2.5", + "node-fetch": "^2.6.1", + "readable-stream": "^3.6.0" + }, + "bin": { + "rabin-wasm": "cli/bin.js" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparse-array": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", + "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==", + "dev": true, + "license": "ISC" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol.inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol.inspect/-/symbol.inspect-1.0.1.tgz", + "integrity": "sha512-YQSL4duoHmLhsTD1Pw8RW6TZ5MaTX5rXJnqacJottr2P2LZBF/Yvrc3ku4NUpMOm8aM0KOCqM+UAkMA5HWQCzQ==", + "license": "ISC" + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/teslabot": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/teslabot/-/teslabot-1.5.0.tgz", + "integrity": "sha512-e2MmELhCgrgZEGo7PQu/6bmYG36IDH+YrBI1iGm6jovXkeDIGa3pZ2WSqRjzkuw2vt1EqfkZoV5GpXgqL8QJVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ton-assembly": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ton-assembly/-/ton-assembly-0.1.2.tgz", + "integrity": "sha512-9EH2kEQ8x0Kc3yfU1UksDxNElvKGbb9KEIuEd1UZaNXP/Pfz0rHwc53IWK73/psTwEay8K/ikqEBMcTmSeYRxQ==", + "dev": true, + "dependencies": { + "@tonstudio/parser-runtime": "^0.0.1", + "cac": "^6.7.14" + }, + "bin": { + "tasm": "dist/cli/assembler.js", + "tdisasm": "dist/cli/disassembler.js" + }, + "peerDependencies": { + "@ton/core": ">=0.61.0" + } + }, + "node_modules/ton-lite-client": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/ton-lite-client/-/ton-lite-client-3.1.1.tgz", + "integrity": "sha512-jhgwRC0txsekBact1rFwgGE3DdgRnMDk2htHZjzLgO9PupdVLAkoFJJ3K9LvtXgY7bKFRcTI+GZWCZk2xRJ0Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "adnl": "^1.0.3", + "dataloader": "^2.1.0", + "lru_map": "^0.4.1", + "teslabot": "^1.5.0", + "ton-tl": "^1.0.1", + "tweetnacl": "^1.0.3" + }, + "peerDependencies": { + "@ton/core": ">=0.56.0" + } + }, + "node_modules/ton-tl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ton-tl/-/ton-tl-1.0.1.tgz", + "integrity": "sha512-dAHJSWEW0CRNm/sdWVhola9/OZc/yHmLOXlSNr9I6l0WaVZmGhwkmDuzvMm1ZJ3Dvhf5tYN+iAUSSgmf8Q+P0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/pegjs": "^0.10.3", + "bn.js": "^5.2.0", + "case-shift": "^2.5.3", + "crc-32": "^1.2.2", + "pegjs": "^0.10.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-jest": { + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/tvm/exit-codes/package.json b/tvm/exit-codes/package.json new file mode 100644 index 0000000..813958d --- /dev/null +++ b/tvm/exit-codes/package.json @@ -0,0 +1,31 @@ +{ + "name": "Example", + "version": "0.0.1", + "scripts": { + "bp": "blueprint", + "start": "blueprint run", + "build": "blueprint build", + "test": "jest --verbose", + "release": "blueprint pack && npm publish --access public" + }, + "dependencies": { + "@ton/core": "~0" + }, + "devDependencies": { + "@tact-lang/compiler": ">=1.6.13 <2.0.0", + "@ton-community/func-js": ">=0.10.0", + "@ton/blueprint": ">=0.40.0", + "@ton/crypto": "^3.3.0", + "@ton/sandbox": ">=0.37.0", + "@ton/test-utils": ">=0.11.0", + "@ton/tolk-js": "^1.2.0", + "@ton/ton": ">=15.3.1 <16.0.0", + "@types/jest": "^30.0.0", + "@types/node": "^22.17.2", + "jest": "^30.0.5", + "prettier": "^3.6.2", + "ts-jest": "^29.4.1", + "ts-node": "^10.9.2", + "typescript": "^5.9.2" + } +} diff --git a/tvm/exit-codes/tests/main.spec.ts b/tvm/exit-codes/tests/main.spec.ts new file mode 100644 index 0000000..dc7650e --- /dev/null +++ b/tvm/exit-codes/tests/main.spec.ts @@ -0,0 +1,44 @@ +import { Blockchain } from '@ton/sandbox'; +import { toNano } from '@ton/core'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; +import { SimpleContract, simpleContractConfigToCell } from '../wrappers/main'; + +describe('Exit codes', () => { + const computeExitCodes = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, -14]; + + const actionExitCodes = [33, 34]; + + it.each([...computeExitCodes, ...actionExitCodes])(`should return exit code %s`, async (exitCode) => { + const blockchain = await Blockchain.create(); + const deployer = await blockchain.treasury('deployer'); + const code = await compile(`${exitCode}`); + const contract = blockchain.openContract( + SimpleContract.createFromInit({ code, data: simpleContractConfigToCell({}) }, 0), + ); + + const result = await contract.sendDeploy(deployer.getSender(), toNano('10')); + + if (actionExitCodes.includes(exitCode)) { + expect(result.transactions).toHaveTransaction({ + to: contract.address, + deploy: true, + success: false, + actionResultCode: exitCode, + }); + } else if (exitCode === -14) { + expect(result.transactions).toHaveTransaction({ + to: contract.address, + deploy: true, + success: false, + exitCode: -14, + }); + } else { + expect(result.transactions).toHaveTransaction({ + to: contract.address, + deploy: true, + success: true, + }); + } + }); +}); diff --git a/tvm/exit-codes/tsconfig.json b/tvm/exit-codes/tsconfig.json new file mode 100644 index 0000000..9ca3cc9 --- /dev/null +++ b/tvm/exit-codes/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "outDir": "dist", + "module": "commonjs", + "declaration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/tvm/exit-codes/wrappers/-14.compile.ts b/tvm/exit-codes/wrappers/-14.compile.ts new file mode 100644 index 0000000..4867a87 --- /dev/null +++ b/tvm/exit-codes/wrappers/-14.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/-14.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/10.compile.ts b/tvm/exit-codes/wrappers/10.compile.ts new file mode 100644 index 0000000..befa5bc --- /dev/null +++ b/tvm/exit-codes/wrappers/10.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/10.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/11.compile.ts b/tvm/exit-codes/wrappers/11.compile.ts new file mode 100644 index 0000000..b0f1cf3 --- /dev/null +++ b/tvm/exit-codes/wrappers/11.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/11.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/2.compile.ts b/tvm/exit-codes/wrappers/2.compile.ts new file mode 100644 index 0000000..bee6274 --- /dev/null +++ b/tvm/exit-codes/wrappers/2.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/2.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/3.compile.ts b/tvm/exit-codes/wrappers/3.compile.ts new file mode 100644 index 0000000..88011dd --- /dev/null +++ b/tvm/exit-codes/wrappers/3.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/3.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/33.compile.ts b/tvm/exit-codes/wrappers/33.compile.ts new file mode 100644 index 0000000..17993c4 --- /dev/null +++ b/tvm/exit-codes/wrappers/33.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/33.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/34.compile.ts b/tvm/exit-codes/wrappers/34.compile.ts new file mode 100644 index 0000000..b34ce9b --- /dev/null +++ b/tvm/exit-codes/wrappers/34.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/34.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/4.compile.ts b/tvm/exit-codes/wrappers/4.compile.ts new file mode 100644 index 0000000..b5b1b83 --- /dev/null +++ b/tvm/exit-codes/wrappers/4.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/4.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/5.compile.ts b/tvm/exit-codes/wrappers/5.compile.ts new file mode 100644 index 0000000..d105115 --- /dev/null +++ b/tvm/exit-codes/wrappers/5.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/5.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/6.compile.ts b/tvm/exit-codes/wrappers/6.compile.ts new file mode 100644 index 0000000..f08ce69 --- /dev/null +++ b/tvm/exit-codes/wrappers/6.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/6.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/7.compile.ts b/tvm/exit-codes/wrappers/7.compile.ts new file mode 100644 index 0000000..c8a70d0 --- /dev/null +++ b/tvm/exit-codes/wrappers/7.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/7.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/8.compile.ts b/tvm/exit-codes/wrappers/8.compile.ts new file mode 100644 index 0000000..63b681d --- /dev/null +++ b/tvm/exit-codes/wrappers/8.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/8.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/9.compile.ts b/tvm/exit-codes/wrappers/9.compile.ts new file mode 100644 index 0000000..d8c1da6 --- /dev/null +++ b/tvm/exit-codes/wrappers/9.compile.ts @@ -0,0 +1,9 @@ +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: 'contracts/9.tolk', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + withSrcLineComments: true, // Fift output will contain .tolk lines as comments + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/tvm/exit-codes/wrappers/main.ts b/tvm/exit-codes/wrappers/main.ts new file mode 100644 index 0000000..3d411c8 --- /dev/null +++ b/tvm/exit-codes/wrappers/main.ts @@ -0,0 +1,36 @@ +import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; + +export type SimpleContractConfig = {}; + +export function simpleContractConfigToCell(config: SimpleContractConfig): Cell { + return beginCell().endCell(); +} + +export class SimpleContract implements Contract { + constructor( + readonly address: Address, + readonly init?: { code: Cell; data: Cell }, + ) {} + + static createFromAddress(address: Address) { + return new SimpleContract(address); + } + + static createFromInit(init: { code: Cell; data: Cell }, workchain = 0) { + return new SimpleContract(contractAddress(workchain, init), init); + } + + static createFromConfig(config: SimpleContractConfig, code: Cell, workchain = 0) { + const data = simpleContractConfigToCell(config); + const init = { code, data }; + return new SimpleContract(contractAddress(workchain, init), init); + } + + async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { + await provider.internal(via, { + value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell().endCell(), + }); + } +}