DEV Community

Cover image for TON Smart Contract Pipeline - write simple contract and compile it
Ivan Romanovich 🧐
Ivan Romanovich 🧐

Posted on • Updated on

TON Smart Contract Pipeline - write simple contract and compile it

Over the past year, many cool smart contract development tools have appeared on the TON blockchain. And I decided to update my open source development lessons on TON.

What will be discussed below

Writing a high-quality smart contract is a cyclical process - we write a piece of logic, then we test it, add logic, then we test it again.

The Blueprint development environment makes this process convenient. The first thing to start with is writing the smart contract code and compiling it.

Let's see how to compile a FunC smart contract using the Blueprint component - func-js.

Project initialization

Make a folder for your project and go into it.

// Windows example
mkdir test_folder
cd test_folder
Enter fullscreen mode Exit fullscreen mode

In this tutorial, we will use the yarn package manager

yarn init
Enter fullscreen mode Exit fullscreen mode

Let's initialize the yarn and just click on the questions in the console, as this is a test case. After that we should get the package.json file in the folder.

Now let's add the typescript and the necessary libraries. Install them as dev dependencies:

yarn add typescript ts-node @types/node @swc/core --dev
Enter fullscreen mode Exit fullscreen mode

Create a tsconfig.json file. We need the file for the project compilation configuration. Let's add to it:

{
    "compilerOptions": {
        "target" : "es2020",
        "module" : "commonjs",
        "esModuleInterop" : true,
        "forceConsistentCasingInFileNames": true,
        "strict" : true,
        "skipLibCheck" : true,
        "resolveJsonModule" : true

    },
    "ts-node": {
        "transpileOnly" : true,
        "transpile" : "ts-node/transpilers/swc"
    }
}
Enter fullscreen mode Exit fullscreen mode

In this tutorial, we will not dwell on what each line of configurations means, because this tutorial is about smart contracts. Now let's install the libraries necessary to work with TON:

yarn add ton-core ton-crypto @ton-community/func-js  --dev
Enter fullscreen mode Exit fullscreen mode

Now let's create a smart contract on FunC. Create contracts folder and main.fc file with minimal code:

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {

} 
Enter fullscreen mode Exit fullscreen mode

recv_internal is called when a smart contract receives an inbound internal message. There are some variables at the stack when TVM initiates, by setting arguments in recv_internal we give smart-contract code awareness about some of them. T

Now let's write a script that will compile our smart contract template. Let's create a scripts folder and a compile.ts file in it.

So that we can use this script, we need to register it as a parameter in the package manager, i.e. in the package.json file, it will look like this:

{
  "name": "test_folder",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@swc/core": "^1.3.63",
    "@ton-community/func-js": "^0.6.2",
    "@types/node": "^20.3.1",
    "ton-core": "^0.49.1",
    "ton-crypto": "^3.2.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.3"
  },
  "scripts": {
      "compile" : "ts-node ./scripts/compile.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's move on to writing the compilation script in the compile.ts file. Here we make a reservation that the result of compilation will be the representation of the bag of Cell in the format of the base64 encoded string. This result needs to be saved somewhere, so let's create a build folder.

Finally we get to the compilation file, the first thing we do is compile our code using the function compileFunc:

import * as fs from "fs";
import { readFileSync } from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";

async function compileScript() {

    const compileResult = await compileFunc({
        targets: ["./contracts/main.fc"], 
        sources: (path) => readFileSync(path).toString("utf8"),
    });

    if (compileResult.status ==="error") {
        console.log("Error happend");
        process.exit(1);
    }

}
compileScript();
Enter fullscreen mode Exit fullscreen mode

The resulting hexBoΠ‘ will be written to the folder:

import * as fs from "fs";
import { readFileSync } from "fs";
import process from "process";
import { Cell } from "ton-core";
import { compileFunc } from "@ton-community/func-js";

async function compileScript() {

    const compileResult = await compileFunc({
        targets: ["./contracts/main.fc"], 
        sources: (path) => readFileSync(path).toString("utf8"),
    });

    if (compileResult.status ==="error") {
        console.log("Error happend");
        process.exit(1);
    }

    const hexBoC = 'build/main.compiled.json';

    fs.writeFileSync(
        hexBoC,
        JSON.stringify({
            hex: Cell.fromBoc(Buffer.from(compileResult.codeBoc,"base64"))[0]
                .toBoc()
                .toString("hex"),
        })

    );

}

compileScript();
Enter fullscreen mode Exit fullscreen mode

For convenience, you can dilute the code with console.log() so that it is clear what worked and what did not when compiling, for example, you can add it to the end:

console.log("Compiled, hexBoC:"+hexBoC);
Enter fullscreen mode Exit fullscreen mode

Which will output the resulting hexBoC.

Let's go to the contract

To create contracts, we need the standard FunC function library. Create a folder imports inside folder contracts and add this file there.

Now go to the main.fc file and import the library, now the file looks like this:

#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {

} 
Enter fullscreen mode Exit fullscreen mode

Let's go over briefly on the contract, detailed analyzes and lessons on FunC starts here.

The smart contract that we will write will store the sender address of the internal message and also store the number one in the smart contract. It will also implement the Get method, which, when called, will return the address of the last sender of the message to the contract and one.

An internal message comes to our function, we will first get the service flags from there, and then the sender's address, which we will save:

#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
    slice cs = in_msg.begin_parse();
    int flags = cs~load_uint(4);
    slice sender_address = cs~load_msg_addr();

} 
Enter fullscreen mode Exit fullscreen mode

Let's save the address and one in the contract, i.e. write the data to register c4.

#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
    slice cs = in_msg.begin_parse();
    int flags = cs~load_uint(4);
    slice sender_address = cs~load_msg_addr();

    set_data(begin_cell().store_slice(sender_address).store_uint(1,32).end_cell());
} 
Enter fullscreen mode Exit fullscreen mode

It's time for the Get method, the method will return an address and a number, so let's start with (slice,int):

(slice,int) get_sender() method_id {

}
Enter fullscreen mode Exit fullscreen mode

In the method itself, we get the data from the register and return it to the user

#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
    slice cs = in_msg.begin_parse();
    int flags = cs~load_uint(4);
    slice sender_address = cs~load_msg_addr();

    set_data(begin_cell().store_slice(sender_address).store_uint(1,32).end_cell());
} 

(slice,int) get_sender() method_id {
    slice ds = get_data().begin_parse();
    return (ds~load_msg_addr(),ds~load_uint(32));
}
Enter fullscreen mode Exit fullscreen mode

Final contract:

#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
    slice cs = in_msg.begin_parse();
    int flags = cs~load_uint(4);
    slice sender_address = cs~load_msg_addr();

    set_data(begin_cell().store_slice(sender_address).store_uint(1,32).end_cell());
} 

(slice,int) get_sender() method_id {
    slice ds = get_data().begin_parse();
    return (ds~load_msg_addr(),ds~load_uint(32));
}
Enter fullscreen mode Exit fullscreen mode

We start compilation using the yarn compile command and get a file c main.compiled.json in the build folder:

{"hex":"b5ee9c72410104010035000114ff00f4a413f4bcf2c80b0102016203020015a1418bda89a1f481a63e610028d03031d0d30331fa403071c858cf16cb1fc9ed5474696b07"}
Enter fullscreen mode Exit fullscreen mode




Conclusion

The next step is to write tests for this smart contract, thanks for your attention.I publish tutorials here, if you liked the article, subscribe so as not to miss new ones.

Top comments (3)

Collapse
 
mbaneshi profile image
Mehdi Baneshi • Edited

how can add func syntax highlighting here?


#include "imports/stdlib.fc";

() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
    slice cs = in_msg.begin_parse();
    int flags = cs~load_uint(4);
    slice sender_address = cs~load_msg_addr();

    set_data(begin_cell().store_slice(sender_address).store_uint(1,32).end_cell());
} 

(slice,int) get_sender() method_id {
    slice ds = get_data().begin_parse();
    return (ds~load_msg_addr(),ds~load_uint(32));
}


Enter fullscreen mode Exit fullscreen mode

yeah, it works, I told him to consider it as C language

Collapse
 
vulcanwm profile image
Medea

This is a very information post!
You can provide the language in the code block so the syntax highlight is also shown, eg. 3 backticks + language short form (ie. js)

Collapse
 
reveloper profile image
AlexG

Is it possible to add link to second post here?
dev.to/roma_i_m/smart-contract-pip...