From 3733558b0630c3816888ba1f7816cbdb1a167067 Mon Sep 17 00:00:00 2001 From: tolk-vm Date: Sat, 2 Nov 2024 05:15:15 +0400 Subject: [PATCH] Support for Tolk Language: next-generation FunC * the user is able to create contracts in three languages: FunC, Tolk, Tact (both from UI and command-line) * templates 'empty' and 'counter', like for FunC and Tact * contracts are compiled with tolk-js, a WASM wrapper, like func-js * no file like 'stdlib.fc' exists, stdlib is embedded into Tolk distribution Co-authored-by: krigga --- CHANGELOG.md | 6 ++ README.md | 21 +++-- package.json | 4 +- src/cli/constants.ts | 12 ++- src/cli/create.ts | 23 +----- src/compile/CompilerConfig.ts | 10 ++- src/compile/compile.ts | 47 ++++++++++- src/index.ts | 2 +- .../common/compilables/compile.ts.template | 9 ++ .../counter/contracts/contract.tolk.template | 71 ++++++++++++++++ .../tolk/counter/scripts/deploy.ts.template | 22 +++++ .../counter/scripts/increment.ts.template | 38 +++++++++ .../tolk/counter/tests/spec.ts.template | 82 +++++++++++++++++++ .../tolk/counter/wrappers/wrapper.ts.template | 67 +++++++++++++++ .../empty/contracts/contract.tolk.template | 4 + .../tolk/empty/scripts/deploy.ts.template | 14 ++++ .../tolk/empty/tests/spec.ts.template | 40 +++++++++ .../tolk/empty/wrappers/wrapper.ts.template | 30 +++++++ .../wrappers/compile.ts.template | 9 ++ yarn.lock | 15 +++- 20 files changed, 489 insertions(+), 37 deletions(-) create mode 100644 src/templates/tolk/common/compilables/compile.ts.template create mode 100644 src/templates/tolk/counter/contracts/contract.tolk.template create mode 100644 src/templates/tolk/counter/scripts/deploy.ts.template create mode 100644 src/templates/tolk/counter/scripts/increment.ts.template create mode 100644 src/templates/tolk/counter/tests/spec.ts.template create mode 100644 src/templates/tolk/counter/wrappers/wrapper.ts.template create mode 100644 src/templates/tolk/empty/contracts/contract.tolk.template create mode 100644 src/templates/tolk/empty/scripts/deploy.ts.template create mode 100644 src/templates/tolk/empty/tests/spec.ts.template create mode 100644 src/templates/tolk/empty/wrappers/wrapper.ts.template create mode 100644 src/templates/tolk/not-separated-common/wrappers/compile.ts.template diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e1099..c3e6b7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.25.0] - 2024-11-02 + +### Added + +- Support for Tolk, "next-generation FunC", a new language for writing smart contracts in TON. [Tolk overview](https://docs.ton.org/develop/tolk/overview) + ## [0.24.0] - 2024-09-16 ### Added diff --git a/README.md b/README.md index a0f4c60..b29ea4f 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ npm create ton@latest ## Overview -Blueprint is an all-in-one development environment designed to enhance the process of creating, testing, and deploying smart contracts on TON blockchain using [FunC](https://docs.ton.org/develop/func/overview) and [Tact](https://docs.tact-lang.org/) languages +Blueprint is an all-in-one development environment designed to enhance the process of creating, testing, and deploying smart contracts on TON blockchain using [FunC](https://docs.ton.org/develop/func/overview), [Tolk](https://docs.ton.org/develop/tolk/overview), and [Tact](https://docs.tact-lang.org/) languages. ### Core features @@ -51,15 +51,16 @@ Blueprint is an all-in-one development environment designed to enhance the proce ### Tech stack 1. Compiling FunC with https://github.com/ton-community/func-js -2. Compiling Tact with https://github.com/tact-lang/tact -3. Testing smart contracts with https://github.com/ton-org/sandbox -4. Deploying smart contracts with [TON Connect 2](https://github.com/ton-connect) or a `ton://` deeplink +2. Compiling Tolk with https://github.com/ton-blockchain/tolk-js +3. Compiling Tact with https://github.com/tact-lang/tact +4. Testing smart contracts with https://github.com/ton-org/sandbox +5. Deploying smart contracts with [TON Connect 2](https://github.com/ton-connect) or a `ton://` deeplink ### Requirements * [Node.js](https://nodejs.org) with a recent version like v18. Version can be verified with `node -v` * IDE with TON support: - * [Visual Studio Code](https://code.visualstudio.com/) with the [FunC plugin](https://marketplace.visualstudio.com/items?itemName=tonwhales.func-vscode) + * [Visual Studio Code](https://code.visualstudio.com/) with the [FunC plugin](https://marketplace.visualstudio.com/items?itemName=tonwhales.func-vscode) or [Tolk plugin](https://marketplace.visualstudio.com/items?itemName=ton-core.tolk-vscode) * [IntelliJ IDEA](https://www.jetbrains.com/idea/) with the [TON Development plugin](https://plugins.jetbrains.com/plugin/23382-ton) ## Features overview @@ -71,7 +72,7 @@ Blueprint is an all-in-one development environment designed to enhance the proce ### Directory structure -* `contracts/` - Source code in [FunC](https://docs.ton.org/develop/func/overview) or [Tact](https://tact-lang.org/) for all smart contracts and their imports +* `contracts/` - Source code for all smart contracts and their imports * `wrappers/` - TypeScript interface classes for all contracts (implementing `Contract` from [@ton/core](https://www.npmjs.com/package/@ton/core)) * include message [de]serialization primitives, getter wrappers and compilation functions * used by the test suite and client code to interact with the contracts from TypeScript @@ -131,7 +132,7 @@ Before developing, make sure that your current working directory is located in t ### Creating contracts 1. Run interactive:    `npx blueprint create`   or   `yarn blueprint create` -2. Non-interactive:   `npx/yarn blueprint create --type ` (type can be `func-empty`, `func-counter`, `tact-empty`, `tact-counter`) +2. Non-interactive:   `npx/yarn blueprint create --type ` (type can be `func-empty`, `tolk-empty`, `tact-empty`, `func-counter`, `tolk-counter`, `tact-counter`) * Example: `yarn blueprint create MyNewContract --type func-empty` ### Writing contract code @@ -141,8 +142,12 @@ Before developing, make sure that your current working directory is located in t 2. Implement shared FunC imports (if breaking code to multiple files) in `contracts/imports/*.fc` 3. Implement wrapper TypeScript class in `wrappers/.ts` to encode messages and decode getters +#### Tolk +1. Implement the contract in `contracts/.tolk`; if you wish, split into multiple files +2. Implement wrapper TypeScript class in `wrappers/.ts` to encode messages and decode getters + #### Tact -1. Implement tact contract in `contracts/.tact` +1. Implement the contract in `contracts/.tact` 2. Wrappers will be automatically generated in `build//tact_.ts` ### Testing contracts diff --git a/package.json b/package.json index 5f9ba3e..ec47f9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ton/blueprint", - "version": "0.24.0", + "version": "0.25.0", "description": "Framework for development of TON smart contracts", "main": "dist/index.js", "bin": "./dist/cli/cli.js", @@ -36,8 +36,10 @@ "dependencies": { "@tact-lang/compiler": "^1.4.0", "@ton-community/func-js": "^0.7.0", + "@ton/tolk-js": "^0.6.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", diff --git a/src/cli/constants.ts b/src/cli/constants.ts index fba9b88..59c6b35 100644 --- a/src/cli/constants.ts +++ b/src/cli/constants.ts @@ -4,13 +4,21 @@ export const templateTypes: { name: string; value: string }[] = [ value: 'func-empty', }, { - name: 'A simple counter contract (FunC)', - value: 'func-counter', + name: 'An empty contract (Tolk)', + value: 'tolk-empty', }, { name: 'An empty contract (TACT)', value: 'tact-empty', }, + { + name: 'A simple counter contract (FunC)', + value: 'func-counter', + }, + { + name: 'A simple counter contract (Tolk)', + value: 'tolk-counter', + }, { name: 'A simple counter contract (TACT)', value: 'tact-counter', diff --git a/src/cli/create.ts b/src/cli/create.ts index b77197a..2d32d4f 100644 --- a/src/cli/create.ts +++ b/src/cli/create.ts @@ -7,7 +7,7 @@ import arg from 'arg'; import { UIProvider } from '../ui/UIProvider'; import { buildOne } from '../build'; import { getConfig } from '../config/utils'; -import { helpArgs, helpMessages } from './constants'; +import { helpArgs, helpMessages, templateTypes } from './constants'; function toSnakeCase(v: string): string { const r = v.replace(/[A-Z]/g, (sub) => '_' + sub.toLowerCase()); @@ -49,25 +49,6 @@ async function createFiles(templatePath: string, realPath: string, replaces: { [ } } -export const templateTypes: { name: string; value: string }[] = [ - { - name: 'An empty contract (FunC)', - value: 'func-empty', - }, - { - name: 'A simple counter contract (FunC)', - value: 'func-counter', - }, - { - name: 'An empty contract (TACT)', - value: 'tact-empty', - }, - { - name: 'A simple counter contract (TACT)', - value: 'tact-counter', - }, -]; - export const create: Runner = async (args: Args, ui: UIProvider) => { const localArgs = arg({ '--type': String, @@ -104,7 +85,7 @@ export const create: Runner = async (args: Args, ui: UIProvider) => { name, loweredName: name.substring(0, 1).toLowerCase() + name.substring(1), snakeName, - contractPath: 'contracts/' + snakeName + '.' + (lang === 'func' ? 'fc' : 'tact'), + contractPath: 'contracts/' + snakeName + '.' + (lang === 'func' ? 'fc' : (lang === 'tolk' ? 'tolk' : 'tact')), }; const config = await getConfig(); diff --git a/src/compile/CompilerConfig.ts b/src/compile/CompilerConfig.ts index 93650de..8bb1fec 100644 --- a/src/compile/CompilerConfig.ts +++ b/src/compile/CompilerConfig.ts @@ -31,4 +31,12 @@ export type FuncCompilerConfig = { } ); -export type CompilerConfig = (TactCompilerConfig | FuncCompilerConfig) & CommonCompilerConfig; +export type TolkCompilerConfig = { + lang: 'tolk'; + entrypoint: string; + optimizationLevel?: number; + withStackComments?: boolean; + experimentalOptions?: string; +}; + +export type CompilerConfig = (TactCompilerConfig | FuncCompilerConfig | TolkCompilerConfig) & CommonCompilerConfig; diff --git a/src/compile/compile.ts b/src/compile/compile.ts index bbcdb75..ce1ca62 100644 --- a/src/compile/compile.ts +++ b/src/compile/compile.ts @@ -12,6 +12,7 @@ import { CompilerConfig, TactCompilerConfig } from './CompilerConfig'; import * as Tact from '@tact-lang/compiler'; import { OverwritableVirtualFileSystem } from './OverwritableVirtualFileSystem'; import { getConfig } from '../config/utils'; +import { TolkCompilerConfig, runTolkCompiler, getTolkCompilerVersion } from '@ton/tolk-js'; export async function getCompilablesDirectory(): Promise { const config = await getConfig(); @@ -35,11 +36,43 @@ async function getCompilerConfigForContract(name: string): Promise { + const res = await runTolkCompiler(config); + + if (res.status === 'error') { + throw new Error(res.message); + } + + return { + lang: 'tolk', + stderr: res.stderr, + code: Cell.fromBase64(res.codeBoc64), + snapshot: res.sourcesSnapshot.map((e) => ({ + filename: e.filename, + content: e.contents, + })), + version: await getTolkCompilerVersion(), + }; +} + export type FuncCompileResult = { lang: 'func'; code: Cell; targets: string[]; - snapshot: SourcesArray; + snapshot: SourceSnapshot[]; version: string; }; @@ -132,13 +165,23 @@ async function doCompileTact(config: TactCompilerConfig, name: string): Promise< }; } -export type CompileResult = TactCompileResult | FuncCompileResult; +export type CompileResult = TactCompileResult | FuncCompileResult | TolkCompileResult; async function doCompileInner(name: string, config: CompilerConfig): Promise { if (config.lang === 'tact') { return await doCompileTact(config, name); } + if (config.lang === 'tolk') { + return await doCompileTolk({ + entrypointFileName: config.entrypoint, + fsReadCallback: (path) => readFileSync(path).toString(), + optimizationLevel: config.optimizationLevel, + withStackComments: config.withStackComments, + experimentalOptions: config.experimentalOptions, + }); + } + return await doCompileFunc({ targets: config.targets, sources: config.sources ?? ((path: string) => readFileSync(path).toString()), diff --git a/src/index.ts b/src/index.ts index 791ca2d..174bf67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export { NetworkProvider } from './network/NetworkProvider'; export { createNetworkProvider } from './network/createNetworkProvider'; -export { compile, CompileOpts } from './compile/compile'; +export { compile, CompileOpts, SourceSnapshot, TolkCompileResult, FuncCompileResult, TactCompileResult, CompileResult } from './compile/compile'; export { CompilerConfig, HookParams } from './compile/CompilerConfig'; diff --git a/src/templates/tolk/common/compilables/compile.ts.template b/src/templates/tolk/common/compilables/compile.ts.template new file mode 100644 index 0000000..d083bfc --- /dev/null +++ b/src/templates/tolk/common/compilables/compile.ts.template @@ -0,0 +1,9 @@ +{{name}}.compile.ts +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: '{{contractPath}}', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/src/templates/tolk/counter/contracts/contract.tolk.template b/src/templates/tolk/counter/contracts/contract.tolk.template new file mode 100644 index 0000000..116800c --- /dev/null +++ b/src/templates/tolk/counter/contracts/contract.tolk.template @@ -0,0 +1,71 @@ +{{snakeName}}.tolk +// simple counter contract in Tolk language + +const OP_INCREASE = 0x7e8764ef; // arbitrary 32-bit number, equal to OP_INCREASE in wrappers/CounterContract.ts + +// storage variables + +// id is required to be able to create different instances of counters +// since addresses in TON depend on the initial state of the contract +global ctxID: int; +global ctxCounter: int; + +// loadData populates storage variables from persistent storage +fun loadData() { + var ds = getContractData().beginParse(); + + ctxID = ds.loadUint(32); + ctxCounter = ds.loadUint(32); + + ds.assertEndOfSlice(); +} + +// saveData stores storage variables as a cell into persistent storage +fun saveData() { + setContractData( + beginCell() + .storeUint(ctxID, 32) + .storeUint(ctxCounter, 32) + .endCell() + ); +} + +// onInternalMessage is the main entrypoint; it's called when a contract receives an internal message from other contracts +fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) { + if (msgBody.isEndOfSlice()) { // ignore all empty messages + return; + } + + var cs: slice = msgFull.beginParse(); + val flags = cs.loadMessageFlags(); + if (isMessageBounced(flags)) { // ignore all bounced messages + return; + } + + loadData(); // here we populate the storage variables + + val op = msgBody.loadMessageOp(); // by convention, the first 32 bits of incoming message is the op + val queryID = msgBody.loadMessageQueryId(); // also by convention, the next 64 bits contain the "query id", although this is not always the case + + if (op == OP_INCREASE) { + val increaseBy = msgBody.loadUint(32); + ctxCounter += increaseBy; + saveData(); + return; + } + + throw 0xffff; // if the message contains an op that is not known to this contract, we throw +} + +// get methods are a means to conveniently read contract data using, for example, HTTP APIs +// note that unlike in many other smart contract VMs, get methods cannot be called by other contracts + +get currentCounter(): int { + loadData(); + return ctxCounter; +} + +get initialId(): int { + loadData(); + return ctxID; +} diff --git a/src/templates/tolk/counter/scripts/deploy.ts.template b/src/templates/tolk/counter/scripts/deploy.ts.template new file mode 100644 index 0000000..9be0630 --- /dev/null +++ b/src/templates/tolk/counter/scripts/deploy.ts.template @@ -0,0 +1,22 @@ +deploy{{name}}.ts +import { toNano } from '@ton/core'; +import { {{name}} } from '../wrappers/{{name}}'; +import { compile, NetworkProvider } from '@ton/blueprint'; + +export async function run(provider: NetworkProvider) { + const {{loweredName}} = provider.open( + {{name}}.createFromConfig( + { + id: Math.floor(Math.random() * 10000), + counter: 0, + }, + await compile('{{name}}') + ) + ); + + await {{loweredName}}.sendDeploy(provider.sender(), toNano('0.05')); + + await provider.waitForDeploy({{loweredName}}.address); + + console.log('ID', await {{loweredName}}.getID()); +} diff --git a/src/templates/tolk/counter/scripts/increment.ts.template b/src/templates/tolk/counter/scripts/increment.ts.template new file mode 100644 index 0000000..2c84b27 --- /dev/null +++ b/src/templates/tolk/counter/scripts/increment.ts.template @@ -0,0 +1,38 @@ +increment{{name}}.ts +import { Address, toNano } from '@ton/core'; +import { {{name}} } from '../wrappers/{{name}}'; +import { NetworkProvider, sleep } from '@ton/blueprint'; + +export async function run(provider: NetworkProvider, args: string[]) { + const ui = provider.ui(); + + const address = Address.parse(args.length > 0 ? args[0] : await ui.input('{{name}} address')); + + if (!(await provider.isContractDeployed(address))) { + ui.write(`Error: Contract at address ${address} is not deployed!`); + return; + } + + const {{loweredName}} = provider.open({{name}}.createFromAddress(address)); + + const counterBefore = await {{loweredName}}.getCounter(); + + await {{loweredName}}.sendIncrease(provider.sender(), { + increaseBy: 1, + value: toNano('0.05'), + }); + + ui.write('Waiting for counter to increase...'); + + let counterAfter = await {{loweredName}}.getCounter(); + let attempt = 1; + while (counterAfter === counterBefore) { + ui.setActionPrompt(`Attempt ${attempt}`); + await sleep(2000); + counterAfter = await {{loweredName}}.getCounter(); + attempt++; + } + + ui.clearActionPrompt(); + ui.write('Counter increased successfully!'); +} diff --git a/src/templates/tolk/counter/tests/spec.ts.template b/src/templates/tolk/counter/tests/spec.ts.template new file mode 100644 index 0000000..0bd77f1 --- /dev/null +++ b/src/templates/tolk/counter/tests/spec.ts.template @@ -0,0 +1,82 @@ +{{name}}.spec.ts +import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'; +import { Cell, toNano } from '@ton/core'; +import { {{name}} } from '../wrappers/{{name}}'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; + +describe('{{name}}', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('{{name}}'); + }); + + let blockchain: Blockchain; + let deployer: SandboxContract; + let {{loweredName}}: SandboxContract<{{name}}>; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + + {{loweredName}} = blockchain.openContract( + {{name}}.createFromConfig( + { + id: 0, + counter: 0, + }, + code + ) + ); + + deployer = await blockchain.treasury('deployer'); + + const deployResult = await {{loweredName}}.sendDeploy(deployer.getSender(), toNano('0.05')); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: {{loweredName}}.address, + deploy: true, + success: true, + }); + }); + + it('should deploy', async () => { + // the check is done inside beforeEach + // blockchain and {{loweredName}} are ready to use + }); + + it('should increase counter', async () => { + const increaseTimes = 3; + for (let i = 0; i < increaseTimes; i++) { + console.log(`increase ${i + 1}/${increaseTimes}`); + + const increaser = await blockchain.treasury('increaser' + i); + + const counterBefore = await {{loweredName}}.getCounter(); + + console.log('counter before increasing', counterBefore); + + const increaseBy = Math.floor(Math.random() * 100); + + console.log('increasing by', increaseBy); + + const increaseResult = await {{loweredName}}.sendIncrease(increaser.getSender(), { + increaseBy, + value: toNano('0.05'), + }); + + expect(increaseResult.transactions).toHaveTransaction({ + from: increaser.address, + to: {{loweredName}}.address, + success: true, + }); + + const counterAfter = await {{loweredName}}.getCounter(); + + console.log('counter after increasing', counterAfter); + + expect(counterAfter).toBe(counterBefore + increaseBy); + } + }); +}); diff --git a/src/templates/tolk/counter/wrappers/wrapper.ts.template b/src/templates/tolk/counter/wrappers/wrapper.ts.template new file mode 100644 index 0000000..c3ad3c7 --- /dev/null +++ b/src/templates/tolk/counter/wrappers/wrapper.ts.template @@ -0,0 +1,67 @@ +{{name}}.ts +import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; + +export type {{name}}Config = { + id: number; + counter: number; +}; + +export function {{loweredName}}ConfigToCell(config: {{name}}Config): Cell { + return beginCell().storeUint(config.id, 32).storeUint(config.counter, 32).endCell(); +} + +export const Opcodes = { + OP_INCREASE: 0x7e8764ef, +}; + +export class {{name}} implements Contract { + constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + + static createFromAddress(address: Address) { + return new {{name}}(address); + } + + static createFromConfig(config: {{name}}Config, code: Cell, workchain = 0) { + const data = {{loweredName}}ConfigToCell(config); + const init = { code, data }; + return new {{name}}(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(), + }); + } + + async sendIncrease( + provider: ContractProvider, + via: Sender, + opts: { + increaseBy: number; + value: bigint; + queryID?: number; + } + ) { + await provider.internal(via, { + value: opts.value, + sendMode: SendMode.PAY_GAS_SEPARATELY, + body: beginCell() + .storeUint(Opcodes.OP_INCREASE, 32) + .storeUint(opts.queryID ?? 0, 64) + .storeUint(opts.increaseBy, 32) + .endCell(), + }); + } + + async getCounter(provider: ContractProvider) { + const result = await provider.get('currentCounter', []); + return result.stack.readNumber(); + } + + async getID(provider: ContractProvider) { + const result = await provider.get('initialId', []); + return result.stack.readNumber(); + } +} diff --git a/src/templates/tolk/empty/contracts/contract.tolk.template b/src/templates/tolk/empty/contracts/contract.tolk.template new file mode 100644 index 0000000..5a7b186 --- /dev/null +++ b/src/templates/tolk/empty/contracts/contract.tolk.template @@ -0,0 +1,4 @@ +{{snakeName}}.tolk +fun onInternalMessage(myBalance: int, msgValue: int, msgFull: cell, msgBody: slice) { + +} diff --git a/src/templates/tolk/empty/scripts/deploy.ts.template b/src/templates/tolk/empty/scripts/deploy.ts.template new file mode 100644 index 0000000..cfc7281 --- /dev/null +++ b/src/templates/tolk/empty/scripts/deploy.ts.template @@ -0,0 +1,14 @@ +deploy{{name}}.ts +import { toNano } from '@ton/core'; +import { {{name}} } from '../wrappers/{{name}}'; +import { compile, NetworkProvider } from '@ton/blueprint'; + +export async function run(provider: NetworkProvider) { + const {{loweredName}} = provider.open({{name}}.createFromConfig({}, await compile('{{name}}'))); + + await {{loweredName}}.sendDeploy(provider.sender(), toNano('0.05')); + + await provider.waitForDeploy({{loweredName}}.address); + + // run methods on `{{loweredName}}` +} diff --git a/src/templates/tolk/empty/tests/spec.ts.template b/src/templates/tolk/empty/tests/spec.ts.template new file mode 100644 index 0000000..3e9ae32 --- /dev/null +++ b/src/templates/tolk/empty/tests/spec.ts.template @@ -0,0 +1,40 @@ +{{name}}.spec.ts +import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'; +import { Cell, toNano } from '@ton/core'; +import { {{name}} } from '../wrappers/{{name}}'; +import '@ton/test-utils'; +import { compile } from '@ton/blueprint'; + +describe('{{name}}', () => { + let code: Cell; + + beforeAll(async () => { + code = await compile('{{name}}'); + }); + + let blockchain: Blockchain; + let deployer: SandboxContract; + let {{loweredName}}: SandboxContract<{{name}}>; + + beforeEach(async () => { + blockchain = await Blockchain.create(); + + {{loweredName}} = blockchain.openContract({{name}}.createFromConfig({}, code)); + + deployer = await blockchain.treasury('deployer'); + + const deployResult = await {{loweredName}}.sendDeploy(deployer.getSender(), toNano('0.05')); + + expect(deployResult.transactions).toHaveTransaction({ + from: deployer.address, + to: {{loweredName}}.address, + deploy: true, + success: true, + }); + }); + + it('should deploy', async () => { + // the check is done inside beforeEach + // blockchain and {{loweredName}} are ready to use + }); +}); diff --git a/src/templates/tolk/empty/wrappers/wrapper.ts.template b/src/templates/tolk/empty/wrappers/wrapper.ts.template new file mode 100644 index 0000000..b779838 --- /dev/null +++ b/src/templates/tolk/empty/wrappers/wrapper.ts.template @@ -0,0 +1,30 @@ +{{name}}.ts +import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core'; + +export type {{name}}Config = {}; + +export function {{loweredName}}ConfigToCell(config: {{name}}Config): Cell { + return beginCell().endCell(); +} + +export class {{name}} implements Contract { + constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {} + + static createFromAddress(address: Address) { + return new {{name}}(address); + } + + static createFromConfig(config: {{name}}Config, code: Cell, workchain = 0) { + const data = {{loweredName}}ConfigToCell(config); + const init = { code, data }; + return new {{name}}(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(), + }); + } +} diff --git a/src/templates/tolk/not-separated-common/wrappers/compile.ts.template b/src/templates/tolk/not-separated-common/wrappers/compile.ts.template new file mode 100644 index 0000000..d083bfc --- /dev/null +++ b/src/templates/tolk/not-separated-common/wrappers/compile.ts.template @@ -0,0 +1,9 @@ +{{name}}.compile.ts +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tolk', + entrypoint: '{{contractPath}}', + withStackComments: true, // Fift output will contain comments, if you wish to debug its output + experimentalOptions: '', // you can pass experimental compiler options here +}; diff --git a/yarn.lock b/yarn.lock index 436f9f0..e40cfe1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -199,12 +199,14 @@ __metadata: "@ton-community/func-js": "npm:^0.7.0" "@ton/core": "npm:^0.58.1" "@ton/crypto": "npm:^3.3.0" + "@ton/tolk-js": "npm:^0.6.0" "@ton/ton": "npm:^15.0.0" "@tonconnect/sdk": "npm:^2.2.0" "@types/inquirer": "npm:^8.2.6" "@types/node": "npm:^20.2.5" "@types/qrcode-terminal": "npm:^0.12.0" arg: "npm:^5.0.2" + axios: "npm:^1.7.7" chalk: "npm:^4.1.0" dotenv: "npm:^16.1.4" inquirer: "npm:^8.2.5" @@ -283,6 +285,17 @@ __metadata: languageName: node linkType: hard +"@ton/tolk-js@npm:^0.6.0": + version: 0.6.0 + resolution: "@ton/tolk-js@npm:0.6.0" + dependencies: + arg: "npm:^5.0.2" + bin: + tolk-js: dist/cli.js + checksum: 10/4e595849fd473cbaeaefa262529c0000ce58a68d1a1b04e8902f449f0032eba792b48cbce141194fe9e728ab2e84d52da98262d066fec1e93246b398b1176448 + languageName: node + linkType: hard + "@ton/ton@npm:^15.0.0": version: 15.0.0 resolution: "@ton/ton@npm:15.0.0" @@ -468,7 +481,7 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.6.7": +"axios@npm:^1.6.7, axios@npm:^1.7.7": version: 1.7.7 resolution: "axios@npm:1.7.7" dependencies: