Reading smart contract and learning from it
Tags: blockchain, ton, smartcontract
Published: 2023-05-26 18:58:57

![FunC is it really fun?](https://res.cloudinary.com/djjd0wi5i/image/upload/v1685111559/photo_2023-05-26_16-32-02_sntbjm.jpg)

## The goal

Hi there folks 👋, today I'll be sharing with you my adventure on reading [tonwhales nominator smart contract](https://github.com/tonwhales/ton-nominators), and what I learned from it. Given the extensiveness of the smart contract, and that I'm completely new to [FunC](https://docs.ton.org/develop/func/overview), I'll only analyze in this post the deposit operation on this contract. I'll also share with you my attempt to craft a request by myself to interact with this smart contract, and what my mistakes were. Really a valuable lesson for someone who is starting in blockchain.

Let's see what I have for you, feel free to reach me out on [Linkedin](https://www.linkedin.com/in/gealber-morales-0a2732189/) for any question you might have.

## TL;DR

In order to save you time, you can go directly to this [part](#message-parts). If you are only interested in the crafted message.

## [FunC](https://docs.ton.org/develop/func/overview) is it really fun?

I hate to find myself saying this, but C after all wasn't so bad 🤐. To give you a little of context, FunC is the official language for writing smart contracts on the [Telegram Open Network(TON)](https://ton.org). It's kind of a pain in the ass to be honest, but it is the tool that we have, so we don't complain, instead we try to figure out how we can use it. Every language has its own drawbacks, there's no such a thing as a perfect programming language. Enough with the complains, let me show what I've learned reading [tonwhales nominator smart contract](https://github.com/tonwhales/ton-nominators).

## Basic concepts

### Validators and nominators

First of all, TON uses Proof of stake consensus, the validation of the blocks in the network are performed by the **validators**. These are just participants in the network that validate blocks and get Toncoin in return. To be a **validator**, requires access to a very nice hardware and not only that, also requires a lot of Toncoin. You can read more about it on the [official website](https://ton.org/en/validator).

While a **nominator** is a network participant who lends his/her assets to validators, and of course nothing of this is done for free, they also get their cut of the pie. 

## The smart contract code

As a curious person that I am, I decided that it was a good idea to know what is inside this smart contract, and tried to understand how this works. Previously I've managed to deploy two silly smart contracts, of which I'm not proud about it 😂, just to know how things work. It's like when you do the Hello World!! When you are learning a new language, you do it to learn how to compile your code, get familiar with error messages, etc…I suggest you do the same.

### My goal
Remember that my goal here is to be able to send a transaction to this smart contract, and that it accepts this transaction as a deposit for staking. I'm going to craft that message using [tonutils-go](https://github.com/xssnick/tonutils-go) library in Golang.

### On what to focus?
Given my little experience, I decided to focus on one particular operation in this smart contract, and only on this one. The operation that I decided to focus on was in the **deposit** operation. This operation is the one that handles the deposits from a wallet that wants to start staking under this contract.

### Deposit operation
As my goal is to craft a transaction that allows me to deposit tons for staking, I need to start reading the [recv_internal](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/nominators.fc#LL1C4-L1C17). This method in Ton's smart contracts is the one that processes inbound messages, so we need to look at it. Here is part of the code:

**file: sources/nominators.fc**

```c
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
    ;; ….
    ;; other code goes here
    ;; this is not important to us for the moment
    ;; ….

    ;; Nominators
    var address = parse_work_addr(s_addr);
    op_nominators(address, flags, msg_value, in_msg);
}
```

In FunC, [comments](https://docs.ton.org/develop/func/comments) are written with `;;`. Here we can notice that fortunately the developers leave us a useful comment that says `Nominators`, and it seems that the method we need to look at is `op_nominators`. So let's see what's inside of it. Here is part of the code:

**file: sources/modules/op-nominators.fc**

```c
() op_nominators(int member, int flags, int value, slice in_msg) impure {
    ;; Ignore bounced
    if (flags & 1) {
        return ();
    }

    ;; Check value
    throw_unless(error::invalid_message(), value >= params::min_op());

    ;; Parse operation
    int op = in_msg~load_uint(32);
    int is_text = op == 0;
    if (op == 0) {

        ;; Query ID
        ctx_query_id = 0;

        ;; Op
        op = in_msg~parse_text_command();
    } else {

        ;; Query ID
        int query_id = in_msg~load_uint(64);
        ctx_query_id = query_id;
        throw_unless(error::invalid_message(), ctx_query_id > 0);

        ;; Gas Limit
        int gas_limit = in_msg~load_coins();
        set_gas_limit(gas_limit);
    }

    ;; Deposit stake
    if (op == op::stake_deposit()) {
        op_deposit(member, value);
        return ();
    }

    ;; …
    ;; other operations as withdraw follows bellow this code
    ;; …

    ;; Unknown message
    throw(error::invalid_message());
}
```

### Message parts

Reading this code you can notice that there are three main parts to focus on:

1. The **value**, which is not other than the amount of toncoins in the transaction. Should be `value >= params::min_op()`, so we will need to know what `params::min_op()` is?
2. Will also need to add an `op`, which stands for operation. We have two options for doing so, one, if we provide `op = 0` then we should send a comment in our transaction, with the following text Deposit. This can be seen in the `parse_text_command()` method. The other option is to provide the op with the value of `op::stake_deposit()`. Which can be found in [sources/modules/constants.fc](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/constants.fc#L18). The value is `2077040623` or `0x7bcd1fef` in hex representation. In this case we should also provide a **query_id** different from 0. This is checked in [sources/modules/op-nominators.fc](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/op-nominators.fc#L26).

Only one thing is left to analyze in order to craft this message properly. Well, this is what I thought at first. We need to look into the code of [op_deposit](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/op-common.fc#L1). 


### op_deposit

The code:

```c

() op_deposit(int member, int value) impure {

    ;; Read extras
    var (enabled, udpates_enabled, min_stake, deposit_fee, withdraw_fee, pool_fee, receipt_price) = ctx_extras;
    throw_unless(error::invalid_message(), enabled);


    ;; Read stake value
    int fee = receipt_price + deposit_fee;
    int stake = value - fee;
    throw_unless(error::invalid_stake_value(), stake >= min_stake);

    ;; Load nominators
    load_member(member);

    ;; Add deposit
    member_stake_deposit(stake);

    ;; Resolve address
    var address = ctx_owner;
    if (member != owner_id()) {
        address = serialize_work_addr(member);
    }

    ;; Send receipt
    if (ctx_query_id == 0) {
        send_text_message(
            address,
            receipt_price,
            send_mode::default(),
            begin_cell()
                .store_accepted_stake(stake)
        );
    } else {
        send_empty_std_message(
            address,
            receipt_price,
            send_mode::default(),
            op::stake_deposit::response(),
            ctx_query_id
        );
    }

    ;; Persist
    store_member();
    store_base_data();
}
```

Here a quite important part of the code is this check

```c
    ;; Read stake value
    int fee = receipt_price + deposit_fee;
    int stake = value - fee;
    throw_unless(error::invalid_stake_value(), stake >= min_stake);
```

It's important to notice that the amount of Toncoins we are going to send in the crafted message, should be 
`value >= min_stake + receipt_price + deposit_fee`

Otherwise we will get the error `error::invalid_stake_value`, which is also defined in [sources/modules/constants.fc](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/constants.fc#LL11C6-L11C33).

The other important part here is that the smart contract will send us a **receipt**, if we send an **op different than 0** it will be an empty message without comment. Otherwise should be a message with the following comment:
**Stake \<amount sent\> accepted**

## Crafting the message

Now that we have everything to craft this message, here is where our code comes into play.

```go

package main

import (
	"context"
	"strings"
	"github.com/xssnick/tonutils-go/address"
	"github.com/xssnick/tonutils-go/tlb"
	"github.com/xssnick/tonutils-go/tvm/cell"
	"github.com/xssnick/tonutils-go/liteclient"
	"github.com/xssnick/tonutils-go/ton"
	"github.com/xssnick/tonutils-go/ton/wallet"
)

const (
	MainnetConfig = "https://ton-blockchain.github.io/global.config.json"
    TonKeeperQueue1 = "EQAA_5_dizuA1w6OpzTSYvXhvUwYTDNTW_MZDdZ0CGKeeper"
	WalletVersion = wallet.V4R2

)

func main() {
	client := liteclient.NewConnectionPool()
	ctx := context.Background()
	err := client.AddConnectionsFromConfigUrl(ctx, MainnetConfig)

	if err != nil {
		panic(err)
	}

	api := ton.NewAPIClient(client)
	seed := strings.Split("<YOUR SEED>", " ")
	w, err := wallet.FromSeed(api, seed, WalletVersion)
	if err != nil {
		panic(err)
	}

	// TonKeeper QUEUE #1	
	addr, err := address.ParseAddr(TonKeeperQueue1)
	if err != nil {
		panic(err)
	}

	// building the payload
	// here we specify the operation equal to zero
	payload := cell.BeginCell().
		MustStoreUInt(0, 32). // op = 0
        // given that we are sending op = 0, we must include the message 'Deposit'.
        // 'Deposit' converted into hex is '0x4465706f736974' and this into uint 19251831996901748
        MustStoreUInt(19251831996901748, 56). 
		EndCell()
	// impostant here to set bounce as false

	msg := &wallet.Message{
		Mode: 1,
		InternalMessage: &tlb.InternalMessage{
			IHRDisabled: true,
			Bounce:      true, // this was edited, in case we face an error, at least we recover back the funds.
			DstAddr:     addr,
			Amount:      tlb.MustFromTON("50"),
			Body:        payload,
		},
	}

	err = w.Send(ctx, msg, false) // not waiting for confirmation
	if err != nil {
		panic(err)
	}
}

```

The value for the transaction should be setted to **50** Toncoins, let's see why, to avoid the mistakes I made at first. So if you remember in the [op_deposit](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/op-common.fc#L7) method we had this:

```c
    ;; Read stake value
    int fee = receipt_price + deposit_fee;
    int stake = value - fee;
    throw_unless(error::invalid_stake_value(), stake >= min_stake);
```

The values for **receipt_price, deposit_fee and min_stake** are:

```bash
receipt_price = 0.1
deposit_fee = 0.1
min_stake = 49.8
```

These values can and should be obtained with the [get_params()](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/get.fc#L62). One of my terrible mistakes at first was to think that these values were the ones defined in the global variables. You can consult this method using [TON verifier](https://verifier.ton.org/EQDvvBmP3wUcjoXPY1jHfT4-fgb294imVYH5EHdLnAKeeper), on the getters section, look for this method and make a request.

## That's all

I think this is all folks, I really recommend you to read all the smart contracts that you interact with. At the end of the day it is your money that is involved in these smart contracts. You can also check this address [EQC9n6aFb2oxQPMTPrHOnZDFcvvC2YLYIgBUms2yAB_LcAtv](https://tonviewer.com/EQC9n6aFb2oxQPMTPrHOnZDFcvvC2YLYIgBUms2yAB_LcAtv) on Tonviewer, more specific to this [transaction](https://tonviewer.com/transaction/029131d91ffa6d44df7a9a387a24e43192c126fefe88454f3b4382f79ca5b2f1) that was made interacting with this contract. Feel free also to donate to this address 🙂, I just spent more than 50 TONs trying to make this workout.

## Lessons

1. FunC is not that fun, there's something quite annoying and is that you can declare variables using a wide variety of characters. Which can be quite inconvenient when you are reading variables like `error::invalid_stake_value`, where you can get the wrong idea that `::` is some kind operator, which is not. Or the case when a method is declared ending in `?`, like in `udict_get?` from [stdlib](https://docs.ton.org/develop/func/stdlib#dict_get), which gives you the idea that this is similar to JavaScript or C conditional operators. Which by the way is also allowed in FunC in this way:

`all ? op::stake_withdraw::response() : op::stake_withdraw::delayed()`

In general it is quite confusing, you have to adapt to the idea that variables and functions can contain this kind of character.

2. This lesson came from one terrible mistake on my part. Obviously because I do not have too much knowledge in blockchain yet. I assumed that the value for the variable `params::min_stake()` would be the one defined in [sources/modules/constants.go](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/constants.fc#LL95C5-L95C22). Nothing more far from the truth, the issue is that in smart contracts we use the blockchain as a storage. So its actual value is stored in the blockchain, and is loaded from it every time we need it. Taking a look at [sources/modules/store-base.fc](https://github.com/tonwhales/ton-nominators/blob/0553e1b6ddfc5c0b60505957505ce58d01bec3e7/sources/modules/store-base.fc#L57) you can notice that `params::min_stake()` comes from the blockchain.