Skip to content

Commit

Permalink
Support for Tolk Language: next-generation FunC
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
tolk-vm and krigga committed Nov 2, 2024
1 parent ca4dcdd commit 3733558
Show file tree
Hide file tree
Showing 20 changed files with 489 additions and 37 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -131,7 +132,7 @@ Before developing, make sure that your current working directory is located in t
### Creating contracts

1. Run interactive: &nbsp;&nbsp; `npx blueprint create` &nbsp; or &nbsp; `yarn blueprint create`
2. Non-interactive: &nbsp; `npx/yarn blueprint create <CONTRACT> --type <TYPE>` (type can be `func-empty`, `func-counter`, `tact-empty`, `tact-counter`)
2. Non-interactive: &nbsp; `npx/yarn blueprint create <CONTRACT> --type <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
Expand All @@ -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/<CONTRACT>.ts` to encode messages and decode getters

#### Tolk
1. Implement the contract in `contracts/<CONTRACT>.tolk`; if you wish, split into multiple files
2. Implement wrapper TypeScript class in `wrappers/<CONTRACT>.ts` to encode messages and decode getters

#### Tact
1. Implement tact contract in `contracts/<CONTRACT>.tact`
1. Implement the contract in `contracts/<CONTRACT>.tact`
2. Wrappers will be automatically generated in `build/<CONTRACT>/tact_<CONTRACT>.ts`

### Testing contracts
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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",
Expand Down
12 changes: 10 additions & 2 deletions src/cli/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
23 changes: 2 additions & 21 deletions src/cli/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
10 changes: 9 additions & 1 deletion src/compile/CompilerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
47 changes: 45 additions & 2 deletions src/compile/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
const config = await getConfig();
Expand All @@ -35,11 +36,43 @@ async function getCompilerConfigForContract(name: string): Promise<CompilerConfi
return mod.compile;
}

export type SourceSnapshot = {
filename: string;
content: string;
};

export type TolkCompileResult = {
lang: 'tolk';
stderr: string;
code: Cell;
snapshot: SourceSnapshot[];
version: string;
};

async function doCompileTolk(config: TolkCompilerConfig): Promise<TolkCompileResult> {
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;
};

Expand Down Expand Up @@ -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<CompileResult> {
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()),
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
9 changes: 9 additions & 0 deletions src/templates/tolk/common/compilables/compile.ts.template
Original file line number Diff line number Diff line change
@@ -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
};
71 changes: 71 additions & 0 deletions src/templates/tolk/counter/contracts/contract.tolk.template
Original file line number Diff line number Diff line change
@@ -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;
}
22 changes: 22 additions & 0 deletions src/templates/tolk/counter/scripts/deploy.ts.template
Original file line number Diff line number Diff line change
@@ -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());
}
Loading

0 comments on commit 3733558

Please sign in to comment.